UE4: コンポーネントをアタッチする状況により潜む罠
今回紹介する「コンポーネントのアタッチ状況により潜む罠」は3つ以上のコンポーネントを"縦"にアタッチ(†)する場合に生じる可能性のある罠です。
例として、2つの USceneComponent
と1つの UStaticMeshComponent
を"縦"にアタッチする状況を幾つか挙げます。この様な状況は1つの AActor
の RootComponent
(=USceneComponent)と他の USceneComponent
と UAudioComponent
などでも同様です。
Note: この記事では煩雑を避ける目的でコード例から一般的な実装では組み込むがこの記事については直接関係しないエラーチェックなどを省きます。また、一般的には .cpp へ実装する cotr の実装も .h へ直接記述しています。
Case 1: すべて1つの ctor で CreateDefaultSubobject
で生成する場合
次のコード例のように関連するコンポーネントがすべて1つの ctor で生成されるような基礎的な実装では「罠」は生じません。
UCLASS() class AMyActor: AActor { GENERATED_BODY() public: AActor() { // Create; S2, M1 S2 = CreateDefaultSubobject< USceneComponent >( "S2" ); M1 = CreateDefaultSubobject< UStaticMeshComponent >( "M1" ); static ConstructorHelpers::FObjectFinder< UStaticMesh > m( TEXT( "/Game/MyMesh.MyMesh" ) ); M1->SetStaticMesh( m.Object ); // Attach; RootComponent, S2, M1 S2->SetupAttachment( RootComponent ); M1->SetupAttachment( S2 ); } USceneComponent* S2 = nullptr; UStaticMeshComponent* M1 = nullptr; };
ちなみに、 RootComponent
へ S2
をアタッチしてから S2
へ M1
をアタッチしようと、あるいは S2
へ M1
をアタッチしてから RootComponent
へ S2
をアタッチしようと、コンポーネントの SetStaticMesh
など準備を先に済ませてからアタッチしようと、アタッチした後でコンポーネントの準備を行おうと、何れも ctor を抜けた後の動作には変わりは生じません。それは罠にならないので1つ安心しましょう。
Case 2: すべて ctor 以外で NewObject
で生成する場合
次のコードのような場合も「罠」は"まだ"生じません。
// AMyActor.h UCLASS() class AMyActor: AActor { GENERATED_BODY() protected: void Tick( float ) override; public: USceneComponent* S2 = nullptr; UStaticMeshComponent* M1 = nullptr; };
// AMyActor.cpp void AMyActor::Tick( float ) { if ( something_trigger ) { // NewObject; S2, M1 S2 = NewObject< USceneComponent >( this ); S2->RegisterComponent(); M1 = NewObject< UStaticMeshComponent >( this ); M1->RegisterComponent(); M1->SetStaticMesh( Cast< UStaticMesh >( StaticLoadObject( UStaticMesh::StaticClass(), nullptr, TEXT( "/Game/MyMesh.MyMesh" ) ) ) ); // Attach; RootComponent, S2, M1 S2->SetupAttachment( RootComponent ); M1->SetupAttachment( S2 ); } };
Case 3: NewObject
で CreateDefaultSubobject
するコンポーネントを扱う場合
次の状況で「罠」が発生します。
- ctor で
CreateDefaultSubobject
によりUStaticMeshComponent
を生成し自らへアタッチするUSceneComponent
の派生クラスUMyScene
のようなものがある。 - ctor 以外で
NewObject
によりUMyScene
のオブジェクトS2
を生成し、S2
をRegisterComponent
し、RootComponent
へアタッチするAActor
の派生クラスAMyActor
のようなものがある。
言葉で書くとやや混乱するかもしれませんが、わりと簡単/単純で "ありそう" な状況です。
// UMyScene.h UCLASS() class UMyScene: USceneComponent { GENERATED_BODY() UMyScene() { // Create; M1 M1 = CreateDefaultSubobject< UStaticMeshComponent >( "M1" ); static ConstructorHelpers::FObjectFinder< UStaticMesh > m( TEXT( "/Game/MyMesh.MyMesh" ) ); M1->SetStaticMesh( m.Object ); // Attach; M1 -> this(≃S2) M1->SetupAttachment( this ); } // Create; this(≃S2) public: UStaticMeshComponent* M1 = nullptr; };
// UMyActor.h UCLASS() class AMyActor: AActor { GENERATED_BODY() protected: void Tick( float ) override; public: UMyScene* S2 = nullptr; UStaticMeshComponent* M1 = nullptr; };
// ****** TRAPPED ****** // AMyActor.cpp; void AMyActor::Tick( float ) { if ( something_trigger ) { // NewObject; S2, M1 S2 = NewObject< UMyScene >( this ); S2->RegisterComponent(); // Attach; RootComponent, S2, M1 S2->SetupAttachment( RootComponent ); } };
このように実装した AMyActor
の動作を確認すると、見えるはず、と意図したであろう UStaticMeshComponent
は見えません。存在するのに存在しません。
この罠を抜け出すための"ヒント"を示します。
// ****** AVOID TRAP ( DIRTY ) ****** // AMyActor.cpp; void AMyActor::Tick( float ) { if ( something_trigger ) { // NewObject; S2, M1 S2 = NewObject< UMyScene >( this ); S2->RegisterComponent(); // Attach; RootComponent, S2, M1 S2->SetupAttachment( RootComponent ); // ****** DIRTY IMPLEMENTATION ****** S2->M1->RegisterComponent(); } };
状況と必要な対応がわかりやすいように "DIRTY" な応急対処を示しました。
NewObject
で生成されるオブジェクトS2
が、- その
S2
の ctor においてCreateDefaultSubobject
で生成したオブジェクトM1
がある場合、 M1
はRegisterComponent
されていない。(ので、そうならない設計にするか、対処可能な実装を施す必要が生じる事がある)
という「罠」です・x・
もう少し複雑な状況でうっかりはまって気付かない事があると精神がすり減る事もありそうです。
Appendix
(†): 「"縦"にアタッチ」は Component0 へ Component1 をアタッチ、 Component1 へ Component2 をアタッチするような、1つのコンポーネントへ複数のコンポーネントをアタッチする状況を"横"、1つのコンポーネントへ1つのコンポーネントをアタッチし、アタッチしたコンポーネントへまた1つのコンポーネントをアタッチする状況を"縦"と模して表現したものです。
Component0 // コンポーネントの"横"の繋がり
- Component1
- Component2
- ...
Component0 // コンポーネントの"縦"の繋がり
- Component1
- Component2
- ...
- Component2
- Component1