お世話になっております。
<br/>
UE 5.6.1 → UE 5.7.2 への移行をしたところ、
FSequencerUtilities::CreateOrReplaceBinding の処理内容が変更されていることを確認しております。
<br/>
5.6.1 では CreateOrReplaceBinding を呼び出した際、引数で渡したコンポーネント(以下 ArgsComp) に対応する
FMovieSceneBinding、FMovieScenePossessable が UMovieScene 以下に追加されますが、
5.7.2 においては 5.6.1 の場合と同様のオブジェクトの他に ArgsComp の親となる Actor の
FMovieSceneBinding、 FMovieScenePossessable についても追加が行われるようです。
<br/>
5.7.2 でこの関数を用いた処理を行った LevelSequence を開くと
Possessable な Actor として追加されている事を確認しているのですが、
この Actor については GetPackage() で nullptr 参照を起こしてしまい、
SequenceEditor 上で選択すると UnrealEditor 自体がクラッシュしてしまうという現象が発生しています。
<br/>
ソースコードを拝見する限り 5.7.2 にて
> Engine/Source/Editor/Sequencer/Private/SequencerUtilities.cpp
に IteratePathToObject という関数が追加されており、
“// Add the parent first” というコメントがあることから、意図して親オブジェクトの追加が行われているように見受けられます。
<br/>
取り急ぎ、5.6.1 での挙動に戻すために MovieScene の GetBindings で用いて増加分を検出し、
AActor 継承であれば今回の件で自動追加された Binding と見なして削除、
それ以外のクラスだった場合も親が変わっているため、
FMovieScenePossessable::SetParent を利用して親 Binding の再指定、といった形で回避を行っています。
こちら、今回の変更の意図と、エンジン改造を行わずに以前の挙動に戻すための推奨の方法をご教示頂くことは可能でしょうか?
<br/>
お手数ですが、宜しくお願い致します。
[Attachment Removed]
お世話になっております。
今回の変更は、SceneGraphシステムやVerse統合に向けた改修作業の一環として行われており、「子オブジェクトの解決に親オブジェクトのコンテキストが必須」であるという構造(FUniversalObjectLocator )への対応として追加されております。トータルデザインとしてこの方向に進んでいるということもあり、エンジン改造を行わずにUE5.6.1準拠の挙動に戻すことは難しいという状況です。
> この Actor については GetPackage() で nullptr 参照を起こしてしまい、SequenceEditor 上で選択すると UnrealEditor 自体がクラッシュしてしまう
こちらにつきまして、コールスタックとクラッシュ前後のログを頂戴することはできますでしょうか?
また、CreateOrReplaceBinding を呼び出しているコードの詳細と、対象のコンポーネントの種類、独自のコードで実施されようとしていることの処理内容・意図なども可能な範囲で共有いただけますと助かります(この Actor について GetPackage() が nullptr を起こしてしまう構造について、なにかお心当たりはお持ちでしょうか?)。
プロジェクト固有の情報が含まれる場合は適切な加工をお願いしたいところなのですが、必要であれば本チケットをプライベートチケット(機密質問用のチケット)にコンバートいたしますので、お申し付けください。
以上、よろしくお願いいたします。
[Attachment Removed]
ご確認ありがとうございます。
> コールスタックとクラッシュ前後のログ
> CreateOrReplaceBinding を呼び出しているコードの詳細と、対象のコンポーネントの種類、独自のコードで実施されようとしていることの処理内容・意図なども可能な範囲で共有
・Epic Games Launcher から DL した UE 5.7.3 での再現プロジェクト
・説明用の image
・コールスタック
をまとめたものを添付致しました。
再現プロジェクトの Content 直下に Spawnable 指定された CineCameraActor を含んだ NewLevelSequence というファイルがあり、
Component の設定が無くなっている状態ではありますが、プロジェクト側ではこのような状態の Actor に対して、Component の設定を自動的に行うツールを作成しています。
サンプルでは Editor 拡張にて上部メニューにサンプル処理実行用ボタンを配置しており、
こちらを押下すると “MovieSceneHelpers::GetObjectTemplate” で検索し Spawnable な Actor が持つ Component を
“FSequencerUtilities::CreateOrReplaceBinding” で追加する処理が実行されます。
※zip 内の image 参照
この時、5.6 環境ではこの方法で Spawnable な Actor の子として各 Component が追加されますが、
5.7 環境では CineCameraActor が増殖し、そちらの子として各 Component が追加され、
この増殖した CineCameraActor 及び 追加された各 Component を選択すると GetPackage() で null アクセスが発生する状態となります。
設計の変更に伴い、 各 Component の追加先が変わる可能性については承知致しましたが、
ここで追加された Component については利用する事が出来ない物となるため、何かしらの追加対応が必要なのでは? と考えております。
もし、 API の利用方法が間違っている場合はご指摘頂けますと幸いです。
お手数ですが、宜しくお願い致します。
[Attachment Removed]
情報ありがとうございます。
頂いた内容を元に対応を進めているのですが、1 点追加の質問となります。
> BindingParams.ReplacementGuid = Binding.GetObjectGuid();
> BindingParams.bSpawnable = true;
上記設定を追加し、 Actor 単位で FSequencerUtilities::CreateOrReplaceBinding を呼び出すと一部のコンポーネントが含まれた状態となる事は確認出来ました。
ただ、提供させていただいたサンプルプロジェクトでは CineCameraActor に対して処理していましたが、
変更後の処理では CineCameraComponent の Possessable な Binding が追加されており、SceneComponent の Binding は追加されない状態となっていました。
こちらは Template として渡している Actor に依存した挙動かと思うのですが、
プロジェクトで運用しているツールの必要要件に「特定 Component の Binding のみが追加された状態にする」という物があります。
サンプル内では処理簡略化のために AActor::GetComponents の結果全てを for で処理していましたが、
実際には各コンポーネントの型や名前等でフィルタリングし、任意のコンポーネントのみを追加した状態にする事が目標となります。
CineCameraActor の様に何かしらのテンプレート(?)を定義する形でも構わないのですが、
可能であれば UE 5.6.1 の時と同様にツール側でのフィルタリングのみで実現できると大変助かります。
サンプルプロジェクト上で「CineCameraActor に SceneComponent の Binding を追加」という状況が出来れば要件を達成できる見込みですので、
こちらが実現可能な手段について、情報がありましたらご提供いただけないでしょうか。
なお、 各 PropertyTrack や CameraCutTrack の様なトラック操作については、別途対応出来ていますので、こちらは省かれてしまっても構いません。
引き続き、宜しくお願い致します。
[Attachment Removed]
追加情報ありがとうございます。
TWeakObjectPtr<AActor> Actor = Cast<AActor>(MovieSceneHelpers::GetObjectTemplate(Sequence.Get(), Binding.GetObjectGuid(), TransientPlaybackState));
if (Actor.IsValid() == false)
{
continue;
}
こちらの処理で LevelSequence にバインドされた Actor のインスタンスが取得できている物と誤解しておりました。
TArrayView<TWeakObjectPtr<>> BoundObjectsView = GetSequencer()->FindObjectsInCurrentSequence(Binding.GetObjectGuid()); AActor* BoundActor = nullptr;
if (BoundObjectsView.Num() > 0 && BoundObjectsView[0].IsValid())
{
BoundActor = Cast<AActor>(BoundObjectsView[0].Get());
}
ご教示頂いたこちらの処理を用い、取得した Actor が持つ Component のバインドを追加することで 5.6 時点での動作に近い挙動となる事を確認することができております。
ACineCameraActor を対象に実行した場合、GetComponents(); で取得できるコンポーネントに差異がありましたが、
こちらは ObjectFlag 等の判定でフィルタリングできそうでしたので、アプリケーション側でカバーできる範囲かなと考えております。
この度はご対応ありがとうございました。
解決済みでクローズして頂ければと思います。
[Attachment Removed]
お世話になっております。
再現プロジェクトの提供をありがとうございます!
おかげさまで状況をよく把握することができました。
オブジェクトテンプレートを取得して新たにトラックおよびバインディングとして登録される場合、Spawnableとして登録する必要がありますので、 FCreateBindingParams のメンバ bSpawnable を true にしていただくと、対応する存在がない Possible な親アクタトラックの誤った作成が行われなくなり、クラッシュの問題を解消できます。
ただし、現実装にありますように、コンポーネント単位で CreateOrReplaceBinding() を bSpawnable = true で呼び出すと、その数だけ Spawnable な親アクタが作成されますので、今回の場合はアクタ単位で CreateOrReplaceBinding() を実行していただければと思います。
また、今回のツールの場合、新規にトラックを追加するというより、オブジェクトテンプレートのコンポーネントを参考にしつつ、既存のトラックのサブトラックを整備する(≒ Replace )のが狙いとなりますので、 FCreateBindingParams の ReplacementGuid に対象アクタのトラックのGUIDを明示的に指定してください。
まとめますと、バインディングのループ内が以下のようなコードになるかと思います。
const TArray<FMovieSceneBinding>& Bindings = const_cast<const UMovieScene*>(MovieScene.Get())->GetBindings();
for (int32 i = 0; i < Bindings.Num(); i++)
{
const FMovieSceneBinding& Binding = Bindings[i];
// 中略
const TSharedRef<UE::MovieScene::FSharedPlaybackState> TransientPlaybackState = MovieSceneHelpers::CreateTransientSharedPlaybackState(GWorld, Sequence.Get());
- const TWeakObjectPtr<AActor> Actor = Cast<AActor>(MovieSceneHelpers::GetObjectTemplate(Sequence.Get(), Binding.GetObjectGuid(), TransientPlaybackState));
+ TWeakObjectPtr<AActor> Actor = Cast<AActor>(MovieSceneHelpers::GetObjectTemplate(Sequence.Get(), Binding.GetObjectGuid(), TransientPlaybackState));
if (Actor.IsValid() == false)
{
continue;
}
// コンポーネントループは削除
+ UE::Sequencer::FCreateBindingParams BindingParams = UE::Sequencer::FCreateBindingParams();
+ BindingParams.ReplacementGuid = Binding.GetObjectGuid();
+ BindingParams.bSpawnable = true;
+ FSequencerUtilities::CreateOrReplaceBinding(GetSequencer(), Sequence.Get(), Actor.Get(), BindingParams);
}
念のため、修正した.cppも添付いたします(.cppファイルの直添付がシステム上許されていないため、あえて.zipに圧縮してあります)。
ご要件を満たせそうか、ご確認いただけますと幸いです。
以上、よろしくお願いいたします。
[Attachment Removed]
お世話になっております。
大変恐縮ながら、現状は、ひな型あるいはリストのようなものを「参考」として利用し、一括でサブトラックを作成するようなユーティリティ関数を用意しておりません。コンポーネントのトラックとバインディングを正しい階層関係で作成するには、正しくそのアクタのコンポーネントを使用してトラックを作成していただく必要があります。今回の場合ですと、テンプレートアクタのSceneComponentを使ってトラックを作るのではなく、トラックのCineCameraActor側がもつコンポーネントを使うことがポイントになります。
そのため、CineCameraActorがもつコンポーネントをイテレートし、「何らかのテンプレート」が同じコンポーネントを持っているかをチェックして成功した場合に、前者のコンポーネントオブジェクトをFSequencerUtilities::CreateOrReplaceBinding()なりGetSequencer()->GetHandleToObject(Component, true)なりに渡す、という処理を手で書いていただくことになりそうです。
//ここまで省略
const TArray<FMovieSceneBinding>& Bindings = const_cast<const UMovieScene*>(MovieScene.Get())->GetBindings();
for (int32 i = 0; i < Bindings.Num(); i++)
{
const FMovieSceneBinding& Binding = Bindings[i];
const FMovieScenePossessable* Possessable = MovieScene->FindPossessable(Binding.GetObjectGuid());
if (Possessable == nullptr)
{
continue;
}
if (Possessable->GetPossessedObjectClass()->GetDefaultObject()->IsA<AActor>() == false)
{
continue;
}
const TSharedRef<UE::MovieScene::FSharedPlaybackState> TransientPlaybackState = MovieSceneHelpers::CreateTransientSharedPlaybackState(GWorld, Sequence.Get());
TWeakObjectPtr<AActor> Actor = Cast<AActor>(MovieSceneHelpers::GetObjectTemplate(Sequence.Get(), Binding.GetObjectGuid(), TransientPlaybackState));
if (Actor.IsValid() == false)
{
continue;
}
TArrayView<TWeakObjectPtr<>> BoundObjectsView = GetSequencer()->FindObjectsInCurrentSequence(Binding.GetObjectGuid());
AActor* BoundActor = nullptr;
if (BoundObjectsView.Num() > 0 && BoundObjectsView[0].IsValid())
{
BoundActor = Cast<AActor>(BoundObjectsView[0].Get());
}
if (BoundActor && BoundActor->GetClass() == Actor->GetClass())
{
// アクタのカスタムバインディングを使用するために一度アクタでCreateOrReplaceBinding()を実行
// 単にコンポーネントを登録したいだけであれば、下記のコードブロックはコメントアウトできます
UE::Sequencer::FCreateBindingParams BindingParams = UE::Sequencer::FCreateBindingParams();
BindingParams.ReplacementGuid = Binding.GetObjectGuid();
BindingParams.bSpawnable = true;
FSequencerUtilities::CreateOrReplaceBinding(GetSequencer(), Sequence.Get(), Actor.Get(), BindingParams);
// ここまで
// 不足しているコンポーネントトラックを追加
for (auto Component : BoundActor->GetComponents())
{
if (!GetSequencer()->GetHandleToObject(Component, false).IsValid()
&& Actor->GetComponentByClass(Component->GetClass()))
{
FSequencerUtilities::CreateOrReplaceBinding(GetSequencer(), Sequence.Get(), Component, UE::Sequencer::FCreateBindingParams());
}
}
}
}
このような方針でご要件を満たせそうか、ご確認いただけますと幸いです。
以上、よろしくお願いいたします。
[Attachment Removed]
お世話になっております。
回答のご確認ありがとうございました。
解決できそうで安心いたしました。
MovieSceneHelpers::GetObjectTemplate()はSpawnableオブジェクトのテンプレートを取得するための関数となります。新規のオブジェクトを獲得した操作にあたり、これをFSequencerUtilities::CreateOrReplaceBinding()などを通じてSpawnableなバインドとして新規登録すれば、Unreal Editor のシーケンサーウィンドウ上でのGUI操作で特定のアクタクラスをSpawnableとして登録した結果とほぼ同等になります。
そのため、UE5.6以前のバージョンで、このオブジェクトテンプレートのコンポーネントをCreateOrReplaceBinding()するだけで、自動的にシーケンサー上の既存のSpawnableアクタトラックのサブトラックとして登録される動きを見せたことは、意図された挙動ではなかった可能性があります。
(エンジンコードでは、アクタオブジェクトを渡すことが多く、コンポーネントオブジェクトを渡すケースはあまり検証されておりませんでした)
諸々アプリケーション側のカバーが必要となってしまい恐縮ですが、より確実性の高い実装になると思いますので、よろしくお願いいたします。
それでは、本件は対応済みとして一旦Closeさせていただきます。
また何かご不明点や不具合など出てきましたら、お気軽にお問い合わせください。
以上、よろしくお願いいたします。
[Attachment Removed]