特定条件下において、シーケンサーのカメラカットでプレイヤーカメラに戻す際、正しくブレンドされない

お世話になっております。

シーケンサーのカメラカットでプレイヤーカメラに戻す際、正しくブレンドされない場合がありました。

  • SampleSequence1
    • シーケンサーのカメラカットの最終フレームにプレイヤーカメラを追加し、シーケンサーのカメラとブレンド
    • プレイヤーカメラのWhen FinishedをKeep Stateにする
  • SampleSequence2
    • シーケンサーのカメラカットで対象を設定せず、シーケンサーのカメラをブレンド
    • シーケンサーのカメラのWhen FinishedをRestore Stateにする
  • SampleSequence3
    • 中身は空​

以上のようなシーケンサーを作成し、再現手順を行ったところ、​SampleSequence2のカメラブレンドがPlayerContorollerに行われておりました。

調査行ったところ、​レベルにシーケンサーが残っているまま、When FinishedをKeep Stateのシーケンサーを再生後に、

リスポーン処理を行うと、下記コードの該当箇所でオブジェクトが見つからず、nullになっておりました。

こちらについて、意図した挙動なんでしょうか?

また、​改善方法はありますでしょうか?

void FCameraCutGameHandler::SetCameraCut(
        UObject* CameraObject,
        const FMovieSceneCameraCutParams& CameraCutParams)
{


    //省略

    // CameraObject is null if we need to release control, which can happen here (instead of via pre-animated 
    // state restore) if we are *blending* back to gameplay, and not cutting back to it at the end of a camera 
    // cut section. Let's get the pre-animated value and blend back towards it.
    if (CameraObject == nullptr)
    {
        TSharedPtr<FPreAnimatedCameraCutStorage> PreAnimatedStorage = Linker->PreAnimatedState.FindStorage(FPreAnimatedCameraCutStorage::StorageID);
        FPreAnimatedStorageIndex StorageIndex = PreAnimatedStorage->FindStorageIndex(0);
        if (ensureMsgf(StorageIndex.IsValid(), TEXT("Blending camera back to gameplay but can't find pre-animated camera info!")))
        {
            FPreAnimatedCameraCutState CachedValue = PreAnimatedStorage->GetCachedValue(StorageIndex);
            //該当箇所
            CameraObject = CachedValue.LastViewTarget.ResolveObjectPtr();
            OverrideAspectRatioAxisConstraint = CachedValue.LastAspectRatioAxisConstraint;
        }
    }

    //省略
}



[Attachment Removed]

再現手順
①添付したSampleProjectを起動

②Lvl_ThirdPersonを開く

③レベルにSampleSequence3が配置されていることを確認(シーケンサーの中身は空)

④PIEでレベルをプレイ

⑤1キー押して、SampleSequence1を再生し、カメラブレンドが正常に行われていることを確認する​

⑥2キーを押して、SampleSequence2を再生し、カメラブレンドが正常に行われていることを確認する​

⑦3キーを押して、プレイヤーをリスポーンする

⑧​4と5を再度行う

​SampleSequence2のカメラブレンドに変化が発生

[Attachment Removed]

ご質問ありがとうございます。

Epic Gamesでは昨年12/20から本年1/4まで冬期休暇を頂戴していたため、本件は本日からの対応となります。お待たせしてしまい、たいへん申し訳ございません。

確認が取れ次第、回答申し上げますので、今しばらくお待ちください。

よろしくお願いいたします。

[Attachment Removed]

お世話になっております。

大変お待たせいたしました。

結論から申し上げますと、本件は意図した挙動ではなく不具合となります。KeepStateでシーケンサーを再生すると、「元状態」がTPreAnimatedStateStorageのPreAnimatedStorage配列に永続的に退避され、次回の再生からは退避先としてTransientPreAnimatedStorage配列が使われるようになるのですが、ゲームカメラとのブレンド戻しの処理の際は前者のみを参照(無関係の状態を「元状態」として参照)してしまっており、結果として不適切なRestoreStateが実施されておりました。

準備ができ次第、社内のバグトラッカーに報告を行います。

当面の問題回避のために、エンジンコードに適用する暫定パッチを作成しましたので、ご確認ください。

まず、Engine\Source\Runtime\MovieScene\Public\Evaluation\PreAnimatedState\MovieScenePreAnimatedStateStorage.h にGetCachedValue関数の亜種を暫定追加します。位置はオリジナルのGetCachedValue関数の直下あたりがよろしいかと存じます。

	const StorageType& GetCachedValueForCameraBlend(FPreAnimatedStorageIndex StorageIndex) const
	{
		static const StorageType DefaultValue = StorageType();
 
		if (ensure(PreAnimatedStorage.IsValidIndex(StorageIndex.Value)))
		{
			if (const StorageType* Data = TransientPreAnimatedStorage.Find(StorageIndex.Value))
			{
				return *Data;
			}
 
			const FCachedData& CachedData = PreAnimatedStorage[StorageIndex.Value];
			if (CachedData.bInitialized)
			{
				return CachedData.Value;
			}
		}
		return DefaultValue;
	}

次に、Engine\Source\Runtime\MovieSceneTracks\Private\TrackInstances\MovieSceneCameraCutGameHandler.cpp 300行付近で呼び出す関数を変更します。

	// CameraObject is null if we need to release control, which can happen here (instead of via pre-animated 
	// state restore) if we are *blending* back to gameplay, and not cutting back to it at the end of a camera 
	// cut section. Let's get the pre-animated value and blend back towards it.
	if (CameraObject == nullptr)
	{
		TSharedPtr<FPreAnimatedCameraCutStorage> PreAnimatedStorage = Linker->PreAnimatedState.FindStorage(FPreAnimatedCameraCutStorage::StorageID);
		FPreAnimatedStorageIndex StorageIndex = PreAnimatedStorage->FindStorageIndex(0);
		if (ensureMsgf(StorageIndex.IsValid(), TEXT("Blending camera back to gameplay but can't find pre-animated camera info!")))
		{
-			FPreAnimatedCameraCutState CachedValue = PreAnimatedStorage->GetCachedValue(StorageIndex);
+			FPreAnimatedCameraCutState CachedValue = PreAnimatedStorage->GetCachedValueForCameraBlend(StorageIndex);
			CameraObject = CachedValue.LastViewTarget.ResolveObjectPtr();
			OverrideAspectRatioAxisConstraint = CachedValue.LastAspectRatioAxisConstraint;
		}
	}

ややラフなパッチで大変恐縮なのですが、そのぶん副作用は少ないと考えております。

また、このたびは非常に使いやすい再現プロジェクトをご提供いただき、誠にありがとうございました。

おかげさまで短時間で問題を確認することができました。

バグトラッカーへの登録が終わりましたら、修正追跡用のURLなどを改めてご連絡いたします。

以上、よろしくお願いいたします。

[Attachment Removed]

ご回答ありがとうございます。

サンプルコード大変参考になりました。

こちら活用させていただきます。

1点確認したい点があるのですが、こちらの現象について、再現手順に記載いたしました

SampleSequence3を配置していない場合は同様の手順でも不具合発生しませんでした。

こちらの現象について、シーケンサーの仕様としてアクティブなシーケンサーが

残っていない場合は「元状態」がTPreAnimatedStateStorageのPreAnimatedStorageから

削除されるようになっているんでしょうか?

[Attachment Removed]

回答のご確認および追加のご質問ありがとうございます。

> こちらの現象について、シーケンサーの仕様としてアクティブなシーケンサーが

> 残っていない場合は「元状態」がTPreAnimatedStateStorageのPreAnimatedStorageから

> 削除されるようになっているんでしょうか?

ご認識の通りです。厳密には、TPreAnimatedStateStorageを保持するUMovieSceneEntitySystemLinkerオブジェクトが、シーン上からシーケンスアクタがすべてなくなった際に削除されるようになっており、結果的にTPreAnimatedStateStorageが刷新される(次に別のオブジェクトでシーケンサーを稼働させたときに、UMovieSceneEntitySystemLinkerごと新作される)という挙動となっております。

※厳密にはUMovieSceneEntitySystemLinkerオブジェクトはUMovieSceneSequenceTickManagerの内部管理グループ単位で「作成/再利用または破棄」の判断が行われるため、場にシーケンスアクタが残っていてもTPreAnimatedStateStorageの再利用を回避できる場合もありますが、概ね上記のような理解で問題ないかと存じます。

以上、よろしくお願いいたします。

[Attachment Removed]

ご回答いただきありがとうございます。

仕様について理解できました。

ご対応ありがとうございました。​

[Attachment Removed]

回答のご確認ありがとうございます。

遅くなりましたが、本件を社内のバグトラッカーに報告いたしました。

Public Issue 化の承認が通り次第、下記URLよりエンジン側の正式な対応状況を追跡していただけます。

それでは以上を持ちまして、本件を回答済みとして一旦チケットをCloseさせていただきたいと思います。

以上、よろしくお願いいたします。

[Attachment Removed]