UE4 Outerについて調査してみた
この記事はUnreal Engine 4 (UE4) Advent Calendar 2018の12日目の記事です。
Outer?
Unreal C++を書いていると、Outerというキーワードに遭遇します。これは NewObject
の引数に存在したり、かなりクラス基底部分の UObjectBase::GetOuter
関数にも存在します。
今回、このOuterについて調べてみました。
まずはコードのコメントを読んでみる
Outerのコメントを見ると、『Object thier object resides in.(このオブジェクトが存在するオブジェクト)』と説明されております。 つまりOuterは、俗にいうOwner、そのオブジェクトの親を示している……?でもActorにはOwnerというのが別に存在しているけど……?
では実際はどうなのか、例から実際の動作を見ていきましょう。
ActorのOuter
テストコードは次のような単純なものです。
void AMyActor::BeginPlay() { Super::BeginPlay(); UObject* CurrentOuter = GetOuter(); while (CurrentOuter != nullptr) { UE_LOG(LogTemp, Warning, TEXT("Outer:%s"), *CurrentOuter->GetName()) CurrentOuter = CurrentOuter->GetOuter(); } }
ThirdPersonExampleに自分で作ったActorを継承したBlueprintをレベルに置き、実行してみます。 結果、そのActorのOuterを辿っていくと次のようになります。
MyActor AActor
┗PersistentLevel(MyActor->GetOuter) == ULevel
┗ThirdPersonExampleMap(PersistentLevel->GetOuter) == UWorld
┗/Game/ThirdPersonBP/Maps/ThirdPersonExampleMap(PIEだと名前変わります) == UPackage
MyActorはPersistentLevelに作られ、PersistentLevelはThirdPersonExampleMapに作られ……といった流れになっています。 しかし、ThirdPersonExampleMapが/Game/ThirdPersonBP/Maps/ThirdPersonExampleMapに作られたと考えるのは少し違和感を感じます。 /Game/ThirdPersonBP/Maps/ThirdPersonExampleMapはUPackageなのでアセット(ファイル)なのだから、ファイル自体から作られるのではなく、他の関数から読み込まれて作られそうなイメージがあります。
なので、Outerは誰に作られたかというよりも、何によって作られたかと考えるのがしっくりくるかもしれません。
また、UObjectBaseUtilityには GetOutermost
という関数もありますが、これは存在する一番上の親を取得する関数です。
つまり、さきほどのMyActorで GetOutermost
関数を呼ぶと、/Game/ThirdPersonBP/Maps/ThirdPersonExampleMapのUPackageが取得できます。
LoadObjectのOuter
次に、 LoadObject<USkeletalMesh>(this, TEXT("/Game/Mannequin/Character/Mesh/SK_Mannequin.SK_Mannequin"))
したUObjectのOuterを辿った場合は以下のとおりです。
/Game/Mannequin/Character/Mesh/SK_Mannequin UPackage
というように、 LoadObject
したオブジェクトのOuterはUPackageのみでした。さきほどとは違って、 LoadObject
を呼んだときに引数に渡したUObjectのポインタ this
がOuterに含まれておりませんでした。
ソースコードを読んでみると、どうやら LoadObject
関数の引数のOuterは、オブジェクトの読み込み・既に読み込まれているかどうかの検索先の絞り込みの判断に使われるようです。
他にも、ActorとActorComponentは乗っているLevelのアセット、 LoadObject
したUObjectではそれの元となるアセットがOuterの終点になるようです。
NewObjectのOuter
では通常の NewObject
したUObjectの場合にはどうなるでしょうか。
NewObject
テンプレート関数は、最初の引数でOuterオブジェクトを指定します。
こちらにNULLを渡した場合、TransientPackageと呼ばれるものがOuterに設定されるようです。そして、オーバーロードされている実装には、デフォルト引数で GetTransientPackage
関数が指定されているものが存在します。
これによりグローバル変数で持っている静的なUPackageとして存在しているTransientPackageがOuterとしてデフォルト引数で渡されます。
実際に NewObject<UObject>()
として関数を呼び出したときのOuterは、/Engine/Transient(UPackage)が表示されます。
Transientとは一時的という意味で、プログラム内で一時的なものとして作られたという意味で指定されているものだと思われます。UPROPERTYにもオプションでありますね。
ちなみに、 NewObject
のOuter引数にNULLではなく、MyActorの this
を渡した場合には次のようになりました。
Object_0
┗MyActor(BuleprintでWorldに置いた場合には、Blueprint名になる)
┗PersistentLevel(MyActor->GetOuter)
┗ThirdPersonExampleMap(PersistentLevel->GetOuter)
┗/Game/ThirdPersonBP/Maps/ThirdPersonExampleMap(PIEだと名前変わります)
Outerのまとめ
Outerは派生クラスによって取得できるものが変わることがわかりました。
では最後にまとめてみると、Outerの終点(Outermost)は次のようになるようです。
- ActorとActorComponentはSpawnした先のLevelのアセット
- LoadObjectしたUObjectではロード先のデータアセット
- NewObject関数経由の場合は、引数Outerに渡したものにより変化します
- 実行中にしか作成されない一時的なものはTransientPackageがOuterに指定されます
Outerを理解して、さらにUnreal C++を便利に使っていきましょう。
・宣伝
最近はVTuberが熱い、ということでバ美肉しました。セルフ受肉です。
UE4の動画もやるかもしれませんので、よろしければよろしくお願いします。
というわけで、明日はVTuber繋がり(?)の水瀬ツバキちゃんおかずさんの『UE4 & iOS開発時のデバッグ・プロファイリング方法 まとめ 2018』です。
いつもお世話になっております。