# 时间管理

定时器是在短暂延迟或一段时间后计划执行一些操作。比如,你可能想让玩家获取一些东西后变无敌,在十秒后变回原状态,又或者你想让玩家在一个充满毒气的屋子里移动时每秒掉多少血。这样的行为都是通过定时器来实现的。

定时器是被全局Timer Manager(FTimerManager类)中管理的,一个全局的定时器管理存在于Game Instance对象,也存在于每个World中。两个用来使用TimerManager来建立定时器的主要函数是SetTimer和SetTimerForNextTick,每个都有几种重载形式,并且被依附到任何Object类型或函数委托上,SetTimer常用来在固定的时间间隔来重复某些事件。

如果对象被调用那定时器将会被自动取消,比如一个Actor,在时间用完前被摧毁,在这种情况下,定时器会变的无效,而函数将不会被调用。

如何获取TimerManager呢?

  • 在AActor中调用GetWorldTimerManager。
  • 在UWorld中调用GetTimerManager。
  • 在UGameInstance中调用GetTimerManager。

UGameInstance中的GetTimerManager常在这两种情况下使用:

  • 一个World中因为某些原因没有自己的TimerManager。
  • 一些函数调用不应该依赖于任何World时。

# 设置和清除定时器

FTimerManager的SetTimer函数会在一段延迟后调用一个函数,然后会无限地重复调用函数。这些函数被用来填充一个定时器句柄(FTimerHandle类),这个类用来暂停或恢复倒计时,查询或者改变剩余时间的量,甚至是取消定时器。使用定时器的一个函数来设置定时器也是安全的,甚至是重复使用定时器句柄来调用函数也没问题。这样用的一种场景是当一个Actor的延迟初始化需要另一个还没生成但是不会便会生成的Actor时,依赖另一方的Actor的初始化函数可以用来设置一个在一段时间(1s)后调用它自身的定时器,可选地,初始化函数可通过一个循环的定时器在成功初始化后清除它自身。

定时器可以被设置为在下一帧运行,而不是使用时间间隔,这是通过SetTimerForNextTick来调用的,注意这个函数不会填充一个定时器句柄。

要清除一个定时器,传递SetTimer中调用的FTimerHandle来调用FTimerManager的函数ClearTimer,定时器句柄会在这个时候变的无效,然后可以被用来管理一个新的定时器,这时使用SetTimer设置即可。注意,在调用SetTimer时设置的值小于等于0时等同于调用ClearTimer。

最后所有与某个对象有关联的所有定时器可通过ClearAllTimersForObject来清除。看例子:

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    // Call RepeatingFunction once per second, starting two seconds from now.
    GetWorldTimerManager().SetTimer(MemberTimerHandle, this, &AMyActor::RepeatingFunction, 1.0f, true, 2.0f);
}

void AMyActor::RepeatingFunction()
{
    // Once we've called this function enough times, clear the Timer.
    if (--RepeatingCallsRemaining <= 0)
    {
        GetWorldTimerManager().ClearTimer(MemberTimerHandle);
        // MemberTimerHandle can now be reused for any other Timer.
    }
    // Do something here...
}

//在GameInstance中,头文件为:
FTimerHandle myTimerHandle;
//cpp文件中:
GetTimerManager().SetTimer(myTimerHandle, this, &UMyGameInstance::DelayFunction, 1.0f, false, 2.0f);
void UMyGameInstance::DelayFunction()
{
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 暂停和恢复定时器

通过定时器句柄来调用FTimerManager的PauseTimer来实现暂停定时器,这会阻止定时器不执行它的函数调用,但是已调用和剩余的时间是不变的。使用UnPauseTimer来恢复定时器。

# 定时器信息

为了管理定时器,TimerManager也提供了获取某个定时器信息的函数,比如调用速度,已调用时间,剩余时间。

//定时器是否Active
// Is this weapon waiting to be able to fire again?
GetWorldTimerManager().IsTimerActive(this, &AUTWeapon::RefireCheckTimer);

//定时器调用速率
// This weapon's rate of fire changes as it warms up. Is it currently waiting to fire, and if so, how long is the current delay between shots?
GetWorldTimerManager().GetTimerRate(this, &AUTWeapon::RefireCheckTimer);

//定时器已运行和剩余时间
// How long will it be until this weapon is ready to fire again? If the answer comes back as -1, it is ready now.
GetWorldTimerManager().GetTimerElapsed(this, &AUTWeapon::RefireCheckTimer);
GetWorldTimerManager().GetTimerRemaining(this, &AUTWeapon::RemainedTimer);
1
2
3
4
5
6
7
8
9
10
11
12

一个定时器的调用速率不能被直接改变,但可以使用SetTimer来创建新的调用速率的定时器,如果定时器句柄无效,GetTimerRate会返回-1。一个定时器的已运行时间和剩余时间的和应该等于它的调用速率。