お世話になります、不具合について伺います
パッケージでの最初のTickがスキップされます
パッケージについては触れられておりませんが、こちらの問題になります
(スタンドアロンについては触れているのでそれが同じ意図かもしれません)
[Content removed]
問題の実例が望まれていたようなので、
改めて問題の指摘とこちらでの修正案とともにあげておきます
アドバイスや修正プランがあればお聞かせいただけると助かります
TickTaskManager.cppでのFInternalDataのTickVisitedGFrameCounterが0で初期化されています
これと比較されるゲームのフレームカウンタGFrameCounterも0から開始されます
5.5では、
void FTickFunction::QueueTickFunction(FTickTaskSequencer& TTS, const struct FTickContext& TickContext)
の冒頭でこの二つが比較されて異なる場合はTickに進むようになっています
エディタからの起動時は、レベル起動前からGFrameCounterが動いているため、
これが32ビット一周して0になる瞬間でもなければ0で比較されませんが(実際問題なかろう)、
パッケージでは最初に起動したレベルの最初のTickで必ず0になっており、
FTickFunctionを使う全てのアクター・コンポーネントのTickが初回にスキップされています
FTickFunctionのコンストラクタを次のように必ず作成時より過去のカウントにすることで、
例えカウンタが一周するタイミングでも、パッケージの初回でも確実にTickが実行されます
FTickFunction::FInternalData::FInternalData()
: bRegistered(false)
, bWasInterval(false)
, TaskState(ETickTaskState::NotQueued)
, ActualStartTickGroup(TG_PrePhysics)
, ActualEndTickGroup(TG_PrePhysics)
#if 1 // Fixed
, TickVisitedGFrameCounter((uint32)(GFrameCounter-1))
, TickQueuedGFrameCounter((uint32)(GFrameCounter-1))
#else
, TickVisitedGFrameCounter(0)
, TickQueuedGFrameCounter(0)
#endif
, TaskPointer(nullptr)
, Next(nullptr)
, RelativeTickCooldown(0.f)
, LastTickGameTimeSeconds(-1.f)
, TickTaskLevel(nullptr)
{
}
弊社では5.6に移行するまでまだしばらくあり、5.6での再現は確認できていません
もしかしたら初期化から最初のTickに至る経路の改善で発生しないのかもしれません
5.6では、このFTickFunction::QueueTickFunction冒頭が少し変更されており、
カウンタの比較がifによる起動条件からcheckによるアサートになっていました
もし実行経路が従来と同じ場合、checkが無効なパッケージでは問題が発生しないでしょうが、
根本的な問題解決はされてないことになります
弊社では5.5にて上記のような暫定の修正でしのぎ、
5.6移行後にcheckが問題になる場合も引き続き同様の対策を入れようと思います
TickQueuedGFrameCounterもこのようにすべきかははっきりわかっていませんが、
同様のカウンタなので念のため同じ修正を入れています
これらの改変による悪影響が考えられるかアドバイスあればお願いします
この問題による問題点としては次のようなケースが確認されています
GameplayCameraを使ってワールドパーティションのストリーミングソースを更新しているとき、
カメラリグの指定(ActivateCameraRigs)をするのにGameplayCamera(をもつPlayerCharacter)と、
PlayerControllerが必要ですが、これらの初期化順序はエディタとパッケージで異なるので、
簡易な安全策としてGameplayCameraコンポーネントを持つPlayerCharacterのPossessedで、
DelayUntilNextTickによってActivateCameraForPlayreController,ActivatePersistentGlobalCameraRig,ActivatePersistentBaseCameraRigなど行います
エディタでは
ワールド初期化直後LevelTick.cppのUWorld::Tickに入ると、
DelayされたActivateCameraRigsが、TG_PrePhysicsで実行されて、
カメラのコンテキストが作成され(void UControllerGameplayCameraEvaluationComponent::EnsureEvaluationContext())、
次いでUGameplayCameraComponent::TickComponentで、そのコンテキストにコンポーネント(PC)の位置が渡されます
主だったTickの後に実行されるストリーミングの更新(InternalUpdateStreamingState())において、
そのカメラコンテキストの位置(カメラの設定でオフセットが乗ったPC付近)がストリーミングソースとして参照されるので、
PCがいるセルがそのまま使われます
パッケージでは
ワールド初期化直後、UWorld::Tickにて各Tickが上記の問題でスキップされてしまうため、
DelayされたActivateCameraRigsまわりの処理もTG_PrePhysicsでは実行されません
コンテキストが未作成なので呼ばれても意味はないですが、
UGameplayCameraComponent::TickComponentもスキップされています
そのあと同フレームの(UWorld::Tickの)CurrentLatentActionManager.ProcessLatentActions(nullptr, DeltaSeconds);
でDelayされたActivateCameraRigsが実行されてカメラコンテキストが作られます
そして同フレームのストリーミングの更新が行われるため、
カメラコンテキストが原点付近で処理されます
(TickComponentでコンテキストにコンポーネントの位置が与えられてないので、原点にキャラがいるかのようなカメラ配置になる)
これによってPCの開始位置がワールドパーティションのレベルの原点付近で無い場合、
開始と同時にPC(カメラ)が本来いる位置のセルがストリームアウトしてしまいます
そのためすぐにPC周辺のアクターのEndPlayが呼ばれます
しかし次のフレームには正常にTickが動作するためカメラコンテキストが指す位置は本来のPC付近になるため、
同じセルがストリームインします
このように本来のBeginPlayのあと、一度すぐにEndPlayされていたアクターが、またBeginPlayされています
これはGFrameCounterが0であることからくる問題のため、
同じレベルであってもOpenLevelで入り直しても発生しません
(逆に言えば同じパッケージ内であってもそのレベルの初回と2回目で挙動が異なります)
また実際の製品では起動直後には、警告やクレジットを行うシーケンス、タイトル画面などが来るため、
この報告例は実害を及ぼすことはないとは思っています
しかし開発時や検証時に原因不明な症状として問題になることは考えられ、
実際弊社でも原因調査に時間を取られたように、
コミュニティ全体としての損失は大きいように思われます
とくにもし5.6でも起きうる問題でしたら、根本的な修正を検討いただければと思います
以上、よろしくお願いします