首页 > 解决方案 > 如何使用 Timers 使用动态计时器执行技能效果?

问题描述

目前,我正在使用计时器来执行我的技能效果。它会在所有技能效果下每 3 秒进行一次心跳。

这是我的代码:

public class EffectServiceImpl extends AbsServiceAdaptor<EffectConfig> {
    private static EffectServiceImpl instance;
    private FastList<Monster> existsEffectMonsterList;
    private Timer timer;
    public static EffectServiceImpl getInstance() {
        if(null == instance) {
            instance = new EffectServiceImpl();
        }
        return instance;
    }
    private EffectServiceImpl() {
        existsEffectMonsterList = new FastList<Monster>();
        timer = new Timer();
        timer.schedule(new MonsterEffectCheckTask(), 10000, 3000); // Heartbeat every 3 seconds
    }
    public class MonsterEffectCheckTask extends TimerTask {
        @Override
        public void run() {
            if(existsEffectMonsterList.size() > 0) {
                Monster monster;
                Effect effect;
                for(int i = 0; i < existsEffectMonsterList.size();) {
                    monster = existsEffectMonsterList.get(i);
                    if(monster.effectList.size() > 0) {
                        for(int j = 0; j < monster.effectList.size();) {
                            try {
                                effect = monster.effectList.get(j);
                                if(effect.heartbeat(monster)) {
                                    j++;
                                }
                            }
                            catch(Exception e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }
                    if(monster.effectList.size() == 0) {
                        existsEffectMonsterList.remove(i);
                    }
                    else {
                        i++;
                    }
                }
            }
        }
    }
}

但是,我不希望所有技能效果都进行 3 秒的心跳。会有小于3秒或大于3秒(即动态周期)的心跳技能。

所以我将时间段更改timer.schedule为 1:

    ...
        timer.schedule(new MonsterEffectCheckTask(), 10000, 1);
    ...

然后添加Thread.sleep到 TimerTask:

...
if(monster.effectList.size() > 0) {
    for(int j = 0; j < monster.effectList.size();) {
        try {
            effect = monster.effectList.get(j);
            if(effect.heartbeat(monster)) {
                j++;
            }
            Thread.sleep(effect.execTime); // This is dynamic time, each effect has an `execTime`. Code `public int execTime;`
        }
        catch(InterruptedException e) {
                e.printStackTrace();
        }
        catch(Exception e) {
            e.printStackTrace();
            break;
        }
    }
}
...

如果是上面的代码,我应该:使用schedule还是scheduleAtFixedRate其他?Thread.sleep如果想“暂停”计时器,是否有任何解决方案可以替换?我应该设置什么delayperiod我知道如果我将它设置为 0 我会得到一个IllegalArgumentException,但如果设置为 1 太短了)?我希望在“暂停”时间到期后立即产生心跳的效果。此外,可以有许多效果相同的心跳,但每个效果的“暂停”时间并不相同。

我会很感激你的回答。

标签: javagame-development

解决方案


执行者框架

Timer现在通常被 Executors 框架过时。请参阅Oracle 的教程

将您的任务定义为Runnable对象Callable

使用由适当数量的线程支持的计划执行器服务。然后安排每个任务以您选择的时间间隔重复运行。

要检查或取消每个任务,请捕获ScheduledFuture返回给您的对象。

如果您想改变任务执行之间的时间间隔,请通过其ScheduledFuture对象取消任务。然后使用执行器服务重新安排新的时间间隔。

始终在您的应用程序结束之前关闭执行程序服务。否则后备线程池可能会像僵尸一样无限期地继续下去。

并包装您的任务代码以捕获任何不经意间冒出的异常。否则,未来的工作将默默地停止。

所有这些都在 Stack Overflow 上多次介绍过。所以我很简短。搜索以了解更多信息。下一次,在发布之前彻底搜索 Stack Overflow。

示例代码

我制作了一个示例应用程序来展示使用 executors 框架来更简单但更有效地管理后台线程工作的基础知识。

我们定义了一个Monster接口,有两个实现类Demon& Witch。我创建了这些类的一些对象。这些类带有一个actOut方法。我们的后台任务将是运行此方法。

对于每个现有Monster实例,我们定义一个Runnable简单地调用该Monster::actOut实例上的方法的方法。我们通过一个对象安排每个可运行对象每隔几秒运行一次ScheduledExecutorService。该执行器服务返回一个ScheduledFuture对象来跟踪我们安排的每个可运行文件。我们将这些未来对象收集到一个列表中Demon和一个列表中Witch

我们让它运行一分钟。在那段时间过去之后,我们改变Witch任务的节奏。我们取消他们当前的任务,然后安排一个新的可运行对象以延长执行之间的间隔。我们让它再运行一分钟。在那之后,我们关闭了应用程序。

(顺便说一句,我们可以Runnable在重新调度时回收现有对象而不是实例化新对象。根据您的需要做任何有意义的事情。)

此代码使用 Java 15 中预览的一些即将推出的功能,例如密封类型记录。但这些对这里的概念并不重要;您可以使用以前版本的 Java 轻松地重新编写此代码。

样品运行

示例运行:

---------|  Starting  |--------------------------------------------
This monster Demon named Crowley buys a soul at 2020-12-08T02:32:39Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:32:39Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:41Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:41Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:46Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:46Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:51Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:51Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:56Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:56Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:32:59Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:32:59Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:01Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:01Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:06Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:06Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:11Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:11Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:16Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:16Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:33:19Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:33:19Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:21Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:21Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:26Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:26Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:31Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:31Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:36Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:36Z
---------|  Changing speeds  |--------------------------------------------
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:38Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:38Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:33:39Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:33:39Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:33:59Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:33:59Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:34:18Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:34:18Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:34:19Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:34:19Z
---------|  Ending  |--------------------------------------------

源代码

我们有四个班级:

  • Monster.java(我们的业务对象的接口)
    • Demon.java(具体类)
    • Witch.java(具体类)
  • MonsterMash.java(应用类)

代码:

package work.basil.example;

public sealed interface Monster
        permits Demon, Witch
{
    String name ( ); // This getter method implemented implicitly by `record` in our concrete classes `Demon` & `Witch`.

    public void actOut ( );
}
package work.basil.example;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public record Demon(String name) implements Monster
{
    @Override
    public void actOut ( )
    {
        System.out.println( "This monster " + this.getClass().getSimpleName() + " named " + this.name + " buys a soul at " + Instant.now().truncatedTo( ChronoUnit.SECONDS ) );
    }
}
package work.basil.example;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public record Witch(String name) implements Monster
{
    @Override
    public void actOut ( )
    {
        System.out.println( "This monster " + this.getClass().getSimpleName() + " named " + this.name + " casts a spell at " + Instant.now().truncatedTo( ChronoUnit.SECONDS ) );
    }
}
package work.basil.example;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Example for showing a change in cadence to scheduled tasks.
 */
public class MonsterMash
{
    public static void main ( String[] args )
    {
        System.out.println( "Hello World!" );
        MonsterMash app = new MonsterMash();
        app.demo();
    }

    private void demo ( )
    {
        // Instantiate Monster objects.
        List < Monster > monsters = List.of(
                new Demon( "Crowley" ) ,
                new Demon( "Barthamus" ) ,
                new Witch( "Rowena" ) ,
                new Witch( "Tasha" )
        );
        System.out.println( "monsters = " + monsters );

        // Schedule each monster to do some work periodically.
        System.out.println( "---------|  Starting  |--------------------------------------------" );
        ScheduledExecutorService ses = null;
        try
        {
            ses = Executors.newSingleThreadScheduledExecutor();

            final List < ScheduledFuture > demonFutures = new ArrayList <>();
            final List < ScheduledFuture > witchFutures = new ArrayList <>();
            for ( Monster monster : monsters )
            {
                Runnable runnable = monster :: actOut;  // Method reference used here for shorter syntax. 

                // Remember these futures.
                if ( monster instanceof Demon )
                {
                    ScheduledFuture scheduledFuture = ses.scheduleAtFixedRate( runnable , 1 , 20 , TimeUnit.SECONDS );
                    demonFutures.add( scheduledFuture );
                }
                if ( monster instanceof Witch )
                {
                    ScheduledFuture scheduledFuture = ses.scheduleAtFixedRate( runnable , 3 , 5 , TimeUnit.SECONDS );
                    witchFutures.add( scheduledFuture );
                }
            }

            // Wait a while to let the scheduled tasks do their work on background thread(s).
            // Then alter the cadence of the tasks’ execution.
            try
            {
                Thread.sleep( TimeUnit.MINUTES.toMillis( 1 ) );
                System.out.println( "---------|  Changing speeds  |--------------------------------------------" );
                for ( ScheduledFuture witchFuture : witchFutures )
                {
                    witchFuture.cancel( false );
                }
                witchFutures.clear();
                for ( Monster monster : monsters )
                {
                    if ( monster instanceof Witch )
                    {
                        Runnable runnable = monster :: actOut;
                        ScheduledFuture scheduledFuture = ses.scheduleAtFixedRate( runnable , 0 , 40 , TimeUnit.SECONDS );
                        witchFutures.add( scheduledFuture );
                    }
                }
                Thread.sleep( TimeUnit.MINUTES.toMillis( 1 ) );

                System.out.println( "---------|  Ending  |--------------------------------------------" );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace();
            }
        }
        finally
        {
            if ( Objects.nonNull( ses ) ) { ses.shutdown(); }
        }
    }
}

推荐阅读