9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WorldContextObject についての理解

Last updated at Posted at 2022-12-19

UnrealEngine で C++ を触っていると、よく目にするワード WorldContextObject
主に静的関数で使われます。例えば UGameplayStatics::GetAllActorsOfClass など。

GameplayStatics.cpp
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 とは

WorldContextObjectGetWorld()が実装されたUObject、つまり World への参照アクセスを持ったオブジェクトのことです。
代表的なものは AActorUActorComponent など、レベルに配置するオブジェクトがあります。これらは 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 を渡す

Obj.cpp
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 の引数で使われることが多く、オブジェクト自身を生成した親オブジェクトがそれになります。

UObjectBaseUtility.cpp
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")

GameplayStatics.h
UFUNCTION(BlueprintPure, Category="Game", meta=(WorldContext="WorldContextObject"))
static class UGameInstance* GetGameInstance(const UObject* WorldContextObject);

WorldContextObject が必要な静的関数を作るときには、WorldContext という メタデータ指定子を使用します。
cfaf381805bf0f9e06d26dccaf33935c.png
これにより関数の定義上では WorldContextObject という引数が存在しますが、BP で使うときには WorldContextObject の入力ピンを省略することができます。
dc8c61bb681037821e0c11fd83467ef0.png
しかし自作の 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 を実装する必要があります。

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?