PaperSloth’s diary

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

UE4 1フレーム処理をスキップさせる方法

環境

UE4.23.0

処理を1フレームスキップさせる方法

結論からいきましょう。
DelayノードをDuration 0で配置するだけで実装ができます。

実験その1 Sequence

Sequenceノードは上から順に実行されていくものでしたね。
ここではThen 0で1フレーム後に"Delay"と出力するのと
Then 1でDelayなしで"Execute"と出力される2つのノードがあります。

結果としてはThen 0の実行ピンが先に実行されますが
Delayを挟んでいるため
出力結果としては"Execute"が表示されてから"Delay"が表示されます。

実験その2 複数フレームの遅延は可能か


続いての実験はDelayノードを2つ挟んだ場合にどうなるかです。

この場合は"Execute" -> "Delay" -> "DelayDelay"の順で表示されます。

先程同様にSequenceノードは上から処理していきますが
Delayを挟んでいるため、最初の処理はこのフレームでは実行されないためです。
複数使用した場合はその個数分のフレームを遅らせることができるようです。
※2つまでしかテストしていないため最大幾つかは不明です

ただし、複数フレーム遅延させてノードを組むのは極力避けてほしいです。
処理が大変追いにくくなり、開発終盤になってからバグの原因だったということにもなりかねません。
もしも、このフレームスキップを使用するのであれば1フレーム以下として
使用する際も分かりやすいような命名やコメント等に色を付けるなど工夫してもらえればと思います。

Delayの仕組み

Delayノードの中身は下記に処理が書かれています。
Engine\Source\Runtime\Engine\Private\KismetSystemLibrary.cpp
以下にDelayのコードを載せておきます。

void UKismetSystemLibrary::Delay(UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo )
{
    if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
    {
        FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
        if (LatentActionManager.FindExistingAction<FDelayAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
        {
            LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FDelayAction(Duration, LatentInfo));
        }
    }
}

Delayノードが呼び出されると
1. FLatentActionManagerを取得
2. FLatentActionManager内に重複したFObjectActionsが登録されていないかチェック
3. FDelayActionを作成
3. FLatentActionManagerにFDelayActionを登録
というような流れになっています。

Delayノードが呼び出されると処理が直ぐに実行されるわけではなく
FLatentActionManagerに登録だけを行っているということが分かりましたね。


ここがちゃんと追えていなくて申し訳ないのですが
FLatentActionManager内に登録された処理がこのフレームで呼び出されていないため
1フレーム遅延して実行されるのではないかと考えています。

そのため、厳密には1フレームの実行ではなく
「1フレーム遅延して実行されているように見える」というのが正しい可能性もあります。
この辺りは後日確認をしてから追記しようと思います。

また、余談ですが
「RetriggerableDelay」の実装は「Delay」とほぼ同一の実装になっています。
Delayではアクションが登録されていれば何も処理を行わず終了しますが
RetriggerableDelayでは登録されている場合はDurationを更新するだけです。

FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
FDelayAction* Action = LatentActionManager.FindExistingAction<FDelayAction>(LatentInfo.CallbackTarget, LatentInfo.UUID);
if (Action == nullptr)
{
    LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FDelayAction(Duration, LatentInfo));
}
else
{
    // アクションが登録されていた場合はタイマーを上書きする
    Action->TimeRemaining = Duration;
}

以上です!

追記
UE5ではこっちを使った方がいいかも?という記事を書きました。
papersloth.hatenablog.com