情報は力ではない

Unreal Engine 5 とかのメモ。

FScopedMovementUpdate についてのメモ

移動コンポーネントを自作しているときに FScopedMovementUpdate について調べたので、その時のメモ。

概要

FScopedMovementUpdateUSceneComponent の Transform 変更に伴う、バウンディングボックスや子コンポーネントの Transform 更新処理、Overlap 情報更新や Overlap 関連イベント発火、Hit イベントの発火などを遅延させるために使用される構造体。
主に移動処理におけるパフォーマンス最適化や、移動位置のロールバックに利用される(はず)。

Definition/Implementation

Source/Runtime/Engine/Public/Engine/ScopedMovementUpdate.h Source/Runtime/Engine/Private/Engine/ScopedMovementUpdate.cpp

使用方法

FScopedMovementUpdate 適用対象の USceneComponent と、遅延を行うかどうかのフラグ( EScopedUpdate )、Overlap イベントを行うかのフラグ( bool )をコンストラクタに指定して使用する。

引数 デフォルト値 説明
Component USceneComponent* - FScopedMovementUpdate を適用する USceneComponent
ScopeBehavior EScopedUpdate::Type EScopedUpdate::DeferredUpdates 遅延を行うかどうかのフラグ値。デフォルト設定では遅延を行う。
遅延ではなく即時実行したい場合は EScoedUpdate::ImmediateUpdates を設定する
bRequireOverlapsEventFlagToQueueOverlaps bool true Overlap イベントを行うかのフラグ。デフォルトでは Overlap イベントを行う

例えば、遅延を行う場合は以下のように定義する。
この場合、処理が ScopedMovementUpdate を定義したスコープからでたとき UpdatedComponent に対して遅延していた処理が行われる。

// USceneComponent* UpdatedComponent;
{
  FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
  // UpdatedComponent の Transform を更新する処理
}
// ScopedMovementUpdate のスコープから出たとき、遅延していた処理が行われる

同じ USceneComponent に対し、複数の FScopedMovementUpdate を適用することもできる。この場合、一番最初に適用した FScopedMovementUpdate のスコープを抜けるときに遅延していた処理が実行される。

// USceneComponent* UpdatedComponent;
{
  FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
  // UpdatedComponent の Transform を更新する処理
  {
    FScopedMovementUpdate InnerScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
    // UpdatedComponent の Transform を更新する処理
  } // InnerScopedMovementUpdate のスコープから出ても遅延処理は実行されない
}
// ScopedMovementUpdate のスコープから出たとき、遅延していた処理が行われる

同じ USceneComponent に対し、複数の FScopedMovementUpdate を適用するときの注意

定義した FScopedMovementUpdate の外側の FScopedMovementUpdate が遅延設定の場合、即時実行( EScopedUpdate::ImmediateUpdates )を指定しても遅延設定となる。

// USceneComponent* UpdatedComponent;
{
  FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
  // UpdatedComponent の Transform を更新する処理
  {
    // 即時実行で設定しているが、外側の FScopedMovementUpdate が遅延実行設定なので
    // この FScopedMovementUpdate も遅延実行となる
    FScopedMovementUpdate InnerScopedMovementUpdate(UpdatedComponent, EScopedUpdate::ImmediateUpdates);
    // UpdatedComponent の Transform を更新する処理 (Overlap イベントなど即時実行されない)
  }
}
// ScopedMovementUpdate のスコープから出たとき、遅延していた処理が行われる

移動処理のロールバック

FScopedMovementUpdate::RevertMove() を使用することで、それまでに更新していた USceneComponent の Transform を FScopedMovementUpdate 定義時までロールバックできる。

// USceneComponent* UpdatedComponent;
{
  FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
  // UpdatedComponent の Transform を更新する処理
  
  if (ShouldRevertMove()) { // 移動をロールバックしたい条件
    ScopedMovementUpdate.RevertMove(); // UpdatedComponent の Transform を ScopedMovementUpdate 定義時の値に戻す
  }
}

FScopedMovementUpdate は、コンストラクタの引数で渡した USceneComonentTransform や Relative Location/Rotation/Scale を内部に保存しているので、このメソッドを呼ぶことでロールバックできる。

動作の仕組みメモ

FScopedMovementUpdate のコンストラクタで EScopedUpdate::DeferredUpdates を設定したときの動作についてのメモ。

FScopedMovementUpdate 定義時

引数に設定した EScopedUpdate::TypeEScopedUpdate::DeferredUpdates のとき USceneComponent::BeginScopedMovementUpdate(FScopedMovementUpdate&) を呼び出す。
これにより USceneComponent は自身に FSopedMovementUpdate が適用されたことを知る。内部的には USceneComponent のメンバ変数 ScopedMovementStack に引数の FScopedMovementUpdate を push する。

USceneComponent の Transform 更新時

USceneComponent の Transform や Relative Location/Rotation/Scale 更新処理が呼ばれるとき、コンポーネント側の責任でそれら更新を行う。
自身に FScopedMovementUpdate が適用されているとき、更新処理により発生するバウンディングボックスや子コンポーネントの Transform 更新処理、Overlap 更新処理、Hit イベント発火処理などが遅延されるように処理が実装されている。 基本的には FScopedMovementUpdate 適用時は遅延時に処理する FOverlapInfoFHitResultFScopedMovementUpdate に保存するような処理を行っている。

例えば以下参照。

  • UsceneComponent::PropagateTransformUpdate(bool, EUpdateTransformFlags, ETeleportType)
  • USceneComponent::UpdateOverlaps(const TOverlapArrayView*, bool, const TOverlapArrayView*)
  • USceneComponent::SetRelativeScale3D(FVector)
  • UPrimitiveComponent::MoveComponentImpl(const FVector&, const FQuat&, bool, FHitResult*, EMoveComponentFlags, ETeleportType)

スコープを抜けたとき

FScopedMovementUpdate を定義したスコープを抜けることにより FScopedMovementUpdate のデストラクタにて USceneComponent::EndScopedMovementUpdate(FScopedMovementUpdate&) が呼び出される。

USceneComponent::EndScopedMovementUpdate(FScopedMovementUpdate&) では USceneComponent に適用された FScopedMovementUpdate が他にいなければ、遅延処理を実行する。

他にいた場合は ScopedMovementStack の Top の要素に引数で渡された FScopedMovementUpdate の遅延した Overlap や Hit を渡す。内部的には USceneComponent は Top の要素の FScopedMovementUpdate::OnInnerScopeComplete(FScopedMovementUpdate*) を呼ぶ。

遅延処理

遅延処理では以下のような処理を行う。

  • FScopedMovementUpdate 定義時の USceneComponent の初期 Transform と、現在の Transform が変わっている場合 Transform 更新時の処理を行う。 内部的には USceneComponent::PropagateTransformUpdate(bool, EUpdateTransformFlags, ETeleportType) を呼び出し、バウンディングボックス更新や Transform 変更に伴う子コンポーネントの Transform 更新を行う。
  • FScopedMovementUpdate に登録していた Overlap を利用し Overlap 情報更新処理を行う。 内部的には USceneComponent::UpdateOverlaps(const TOverlapArrayView*, bool, const TOverlapArrayView*) を呼び出し Overlap 情報の更新や Overlap 関連イベントを発火する
  • FScopedMovementUpdate に登録していた Hit イベントをすべて発火する。

参考資料