UE4 Outerについて調査してみた

この記事はUnreal Engine 4 (UE4) Advent Calendar 2018の12日目の記事です。

qiita.com

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』です。

いつもお世話になっております。