PaperSloth’s diary

主にゲーム開発関連についての記事を書きます。

UE4 C++でTimerを使用した処理の書き方

環境

Visual Studio Community 2017
・Unreal Engine4.20.3

SetTimer by Eventについて

指定時間の経過後に処理を呼び出せる便利なノードです。
f:id:PaperSloth:20180924153029p:plain

こういったLatentノードではDelayなんかをよく使うと思います。
SetTimer系の便利なとこはループさせたり
任意のタイミングでループを終了させたりできることですね。

似た関数でSet Timer by Function Nameというのがあります。
今回はこちらの解説は端折ります。
個人的に関数名を文字列で渡したりするのはあんまり好きではないですね。
f:id:PaperSloth:20180924153036p:plain

Unreal C++での実装について

FTimerManagerについて

こういったタイマー系の処理は素直にC++で組むと結構面倒です。
ですが、Unreal C++は色々と便利な関数を用意してくれています。

先ずはSetTimer関数を呼び出すためにFTimerManagerを取得します。
TimerManagerはWorldが保持しており、このように取得できます。

FTimerManager& timerManager = GetWorld()->GetTimerManager();

また、Actorを継承したクラスであれば下記のように取得できます。

FTimerManager& timerManager = GetWorldTimerManager();

SetTimerについて

TimerManagerの中にSetTimer関数があるのでこちらを使用します。
今回は例としてActorからの呼び出しを行っています。

FTimerHandle handle;

// Function:デリゲートとして渡す関数
// Duration:遅延させる秒数
// InLoop  :処理をループさせるか
GetWorldTimerManager().SetTimer(handle, this, /*Function*/, /*Duration*/, /*InLoop*/);

簡単な例として0.2秒後にLogを出す処理を書いてみます。

void AExampleActor::BeginPlay()
{
    Super::BeginPlay();
    FTimerHandle handle;
    GetWorldTimerManager().SetTimer(handle, this, &AExampleActor::OutputLog, 0.2f, false);
}

void AExampleActor::OutputLog()
{
    UE_LOG(LogTemp, Log, TEXT("Call SetTimer"));
}

また、SetTimerはいくつか種類があり、ラムダ式を使用することもできるため
簡単な処理であれば直接書いてしまってもいいかもしれません。
この場合は引数としてthisポインタを渡しておらず、引数が違う点に注意です。

void AExampleActor::BeginPlay()
{
    Super::BeginPlay();
    FTimerHandle handle;
    GetWorldTimerManager().SetTimer(handle, []()
        {
            UE_LOG(LogTemp, Error, TEXT("Log message"));
        } , 0.2f, false
    );
}

タイマーの停止、再開について

処理をループさせている場合のタイマーの停止、再開は下記のように行います。

FTimerHandle handle;
FTimerManager& timerManager = GetWorldTimerManager();

// タイマー停止
timerManager.PauseTimer(handle);
// タイマー再開
timerManager.UnPauseTimer(handle);

これで任意のタイミングでタイマーを操作できます。

タイマーの終了処理について

タイマーを使用した場合は終了処理も合わせて書くようにしましょう。

void AExampleActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);

    FTimerManager& timerManager = GetWorldTimerManager();

    // Handleに登録されたTimerの解放
    timerManager.ClearTimer(handle);
 
    // このActorが所有するタイマーの解放
    timerManager.ClearAllTimersForObject(this);
}

引数付きの関数を渡す場合について

関数にパラメーターを渡す場合はFTimerDelegateのBindUFunctionを使用します。
このBindUFunctionを使用する場合はUFUNCTIONマクロがないとエラーになるので注意です。
UFUNCTIONの説明の際に軽く触れています。
UE4 UFUNCTIONの種類について - PaperSloth’s diary

// Header
// BindUFunctionに渡す関数はUFUNCTIONの定義がないとエラーになる
UFUNCTION()
void InitializeTransform(const int32 Id, const FVector& Location, const FQuat& Quat);

// cpp
void AExampleActor::InitializeTransform(const int32 Id, const FVector& Location, const FQuat& Quat)
{
    UE_LOG(LogTemp, Error, TEXT("Call InitializeTransform"));
}
FTimerHandle handle;
FTimerManager& TimerManager = GetWorldTimerManager();

FTimerDelegate TimerDelegate;
// BindUFunctionで使用する関数名、引数に渡す値を入れる
TimerDelegate.BindUFunction(this, FName("InitializeTransform"), 100, FVector::ZeroVector, FQuat::Identity);
TimerManager.SetTimer(handle, TimerDelegate, 1.0f, false);

BindUFunctionは文字列で関数名を渡すため、関数名や引数を変更する際は漏れが発生しやすく注意が必要です。

参考資料

Delay in C++ - UE4 AnswerHub
Using SetTimer() on a Function with Parameters - UE4 AnswerHub


とりあえずはこれでC++側でも簡単に時間を遅らせて処理をすることができますね。
最後にコードを全て載せておきます。