UnrealEngine で C++ を触っていると、よく目にするワード WorldContextObject
。
主に静的関数で使われます。例えば UGameplayStatics::GetAllActorsOfClass
など。
void UGameplayStatics::GetAllActorsOfClass(const UObject* WorldContextObject, TSubclassOf<AActor> ActorClass, TArray<AActor*>& OutActors)
{
QUICK_SCOPE_CYCLE_COUNTER(UGameplayStatics_GetAllActorsOfClass);
OutActors.Reset();
// We do nothing if no is class provided, rather than giving ALL actors!
if (ActorClass)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
{
for (TActorIterator<AActor> It(World, ActorClass); It; ++It)
{
AActor* Actor = *It;
OutActors.Add(Actor);
}
}
}
}
静的関数は閉じられたスコープなので、それ単体では World を参照することができません。World を取得するには、例えば以下の関数を使います。
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)
では WorldContextObject
には何を渡せばいいのでしょうか?
WorldContextObject とは
WorldContextObject
は GetWorld()が実装されたUObject
、つまり World への参照アクセスを持ったオブジェクトのことです。
代表的なものは AActor
や UActorComponent
など、レベルに配置するオブジェクトがあります。これらは World
-> Level
-> Actor
-> ActorComponent
といった参照関係になっており、親を辿っていけば自身の World が取得できる、というわけです。
しかし Actor に紐づいていなくとも、GetWorld() で World が返せばそれは WorldContextObject として成立します。
WorldContextObject の使い方
void AMyActor::Hoge( const UObject* WorldContextObject )
{
TArray<AActor*> OtherActors;
UGameplayStatics::GetAllActorsOfClass( WorldContextObject, AOtherActor::StaticClass(), OtherActors );
}
UE4 触り始めのころは、↑のように WorldContextObject を外から渡していました。
void AMyActor::Hoge( const UObject* WorldContextObject )
{
TArray<AActor*> OtherActors;
UGameplayStatics::GetAllActorsOfClass( GetWorld(), AOtherActor::StaticClass(), OtherActors );
}
または GetWorld() で World を直接渡すなど。これらは誤りではないのですが、
void AMyActor::Hoge()
{
TArray<AActor*> OtherActors;
UGameplayStatics::GetAllActorsOfClass( this, AOtherActor::StaticClass(), OtherActors );
}
実のところ AMyActor
は Actor のサブクラスですから、自身 this
を渡せば成立します。
ではプロジェクト側で作成した UMyObject
を this として渡す場合はどうすればいいのでしょうか?
Outer に WorldContextObject を渡す
class UWorld* UObject::GetWorld() const
{
if (UObject* Outer = GetOuter())
{
return Outer->GetWorld();
}
#if DO_CHECK || WITH_EDITOR
bGetWorldOverridden = false;
#endif
return nullptr;
}
エンジンのコードでは、Outer を辿って World を取得するように実装されています。
自身の親オブジェクトが GetWorld を実装していれば World を取得できる、という流れです。
void AMyActor::Hoge()
{
// NewObjectの第一引数にActor自身を渡す
auto* MyObject = NewObject<UMyObject>( this );
// MyObjectがWorldContextObjectとして機能する
TArray<AActor*> OtherActors;
UGameplayStatics::GetAllActorsOfClass( MyObject, AOtherActor::StaticClass(), OtherActors );
}
Outer is 何
Outer はオブジェクトの親参照です。NewObject の引数で使われることが多く、オブジェクト自身を生成した親オブジェクトがそれになります。
UObject* UObjectBaseUtility::GetTypedOuter(UClass* Target) const
{
ensureMsgf(Target != UPackage::StaticClass(), TEXT("Calling GetTypedOuter to retrieve a package is now invalid, you should use GetPackage() instead."));
UObject* Result = NULL;
for ( UObject* NextOuter = GetOuter(); Result == NULL && NextOuter != NULL; NextOuter = NextOuter->GetOuter() )
{
if ( NextOuter->IsA(Target) )
{
Result = NextOuter;
}
}
return Result;
}
参照の親子関係が分かっていれば、孫関係から親の親を指定して取得するなどもできます。クラスを跨ぐと関係性は見えないので分かりにくいかもしれませんが、覚えておくと役に立つ機会もあるでしょう。
void UMyObject::Hoge()
{
// ObjectAはMyObjectをOuterに持つ
auto* ObjectA = NewObject<UObject>( this );
// ObjectBはObjectAをOuterに持つ
auto* ObjectB = NewObject<UObject>( ObjectA );
// ObjectBからOuterを辿って<UMyObject>の型Objectを取得する
auto* MyObject = ObjectB->GetTypedOuter<UMyObject>();
}
meta = (WorldContext = "WorldContextObject")
UFUNCTION(BlueprintPure, Category="Game", meta=(WorldContext="WorldContextObject"))
static class UGameInstance* GetGameInstance(const UObject* WorldContextObject);
WorldContextObject が必要な静的関数を作るときには、WorldContext
という メタデータ指定子を使用します。
これにより関数の定義上では WorldContextObject という引数が存在しますが、BP で使うときには WorldContextObject の入力ピンを省略することができます。
しかし自作の UObject を継承したブループリントでは、WorldContextObject の入力ピンが表示されてしまいます。
UCLASS( Blueprintable )
class UMyObject : public UObject
{
GENERATED_BODY()
protected:
virtual UWorld* GetWorld() const override
{
if ( HasAllFlags( RF_ClassDefaultObject ) )
{
// If we are a CDO, we must return nullptr instead of calling Outer->GetWorld() to fool UObject::ImplementsGetWorld.
return nullptr;
}
return Super::GetWorld();
}
};
入力ピンの判定を回避するためには、新たに GetWorld を実装する必要があります。