目次
※この記事で使用しているUnrealのVersionは05.2.1です。
※この記事のサンプルプロジェクトは以下URLにアップされています。
サンプルプロジェクト
Object継承BPのWorldContextをどうにかしたい レベル【★★★☆】
みなさんは、
「レベル上に配置したりする必要はないんだけど、BPを使いたい」
みたいな場面に遭遇したことはありませんか?
ActorやComponentは高機能で様々な事ができますが、必ず「レベル(World)」に依存してしまいます。
そんなときはObject(UObject)
を継承したBlueprintはとても便利です。
ただ、このObject継承のBPには一つ制約があります。
それは、「WorldContextが必要なノードが使えない」というところです。
WorldContextって何?については以下を参照してください。
【UE5】World Context Object って何?
WorldContextが必要なノードには、GetAllActorsOfClass
Delay
SpawnActor
等、BPの便利な機能がたくさんあります。
これらの機能もObjectBPで使えたら便利ですよね。
あ、ちなみに使えないと書きましたが、そのままの状態でも無理すれば使えます🐟
昔から禁忌の技として言い伝えられている「CollapseExpand術」にてできます。
→一度Actor等のBPで使いたいノードを出し、それを「Collapse Nodes」にてコラプスノード化します。
それをコピーしてからObjectBPの方にペーストし、「Expand Node」をすることでノードを召喚できます。
ただし、この方法でBPを使うと、「Unsafe to call from blueprints of class」みたいなWarningが出てきて気持ち悪いです。というかこんな使い方したらプログラマさんに干物にされます🐟
じゃあどうやったらWarningなくつかえるんでしょう?
このWarningの出処をみると、K2Node_CallFunction.cpp
の2227行目あたりにありそうです。
if (ParentClass && !FBlueprintEditorUtils::ImplementsGetWorld(Blueprint) && !ParentClass->HasMetaDataHierarchical(FBlueprintMetadata::MD_ShowWorldContextPin))
{
MessageLog.Warning(*LOCTEXT("FunctionUnsafeInContext", "Function '@@' is unsafe to call from blueprints of class '@@'.").ToString(), this, ParentClass);
}
これを見ると、打開策は2パターンほどあるかなと思います(C++が必要になります…)
1.UCLASS(meta = (ShowWorldContextPin))
を使う
UObjectを継承するクラスには、UCLASSというマクロをつけることで様々な拡張ができますが、そのうちの機能の一つにShowWorldContextPin
というものがあります。
(↓の説明がわかりやすいかなと思います)
[UE4] meta情報を活用しエディタでの動作を制御しよう!
これによって、明示的にWorldContextPinを表示することができるようになり、これによるWarningが出なくなります。
試しに作成してみましょう。
Editor上部のToolから「New C++ Class…」を選択し、Objectを親にしたクラスを作成します。
中身は適当にこんな感じで書きます。
KinnajiObject.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "KinnajiObject.generated.h"
/**
*
*/
UCLASS(Blueprintable,meta = (ShowWorldContextPin))
class OBJECTBASEBPSCONTEXT_API UKinnajiObject : public UObject
{
GENERATED_BODY()
};
↓結果
WorldContextピンは表示されるものの、BPをコンパイルしてもエラーは起こりませんね。
ただ、毎回WorldContextPinをつなげる必要が出てきます。
これは、UFUNCTION(BlueprintCallable,meta=(WorldContext="WorldContextObject")
となっていても強制的に表示され繋げる必要があります。
それが面倒な場合は次の方法を試してください。
2.virtual class UWorld* GetWorld() const
をオーバーライドする
Blueprintをコンパイルする際のValidation(先程↑で説明したEdGraphSchema_K2.cpp
のやつ)時やBlueprintのGraphEditorで右クリックをした際に表示される、BPノード一覧のリストを作成する際(EdGraphSchema_K2.cp
p867行目ぐらい)などを見ると、
bool FBlueprintEditorUtils::ImplementsGetWorld()
という処理が行われています。
これは、BPのNativeParent(C++クラス側)のデフォルトオブジェクトにあるUObject::GetWorld
を叩いているのですが、その際大本のUObject側のGetWorldを叩いているかどうかを判断しています。
どういうことかと言うと、UObjectを継承した先に自信から辿れる範囲からWorldが取得できない場合は、所属Worldが不確定でありWorldに依存した動作が安定しないということを明示しています(おそらく)
ので、継承先のクラスでGetWorldをオーバーライドし、Super::GetWorld()
をたたかずにWorldを取得することで、FBlueprintEditorUtils::ImplementsGetWorld()
はtrueとなり、定まったWorldが取得可能であると判断しているようです(多分)
普段ActorなどのBPノードでは、WorldContextのPinが隠れていますが、それはActorのGetWorld()関数が所属Worldを取得するようにかかれており、そのWorldを自動的にWorldContextに使っているからになります。
AActorのGetWorld(親のGetWorldを呼び出さずにWorldを取得している)
UWorld* AActor::GetWorld() const
{
// CDO objects do not belong to a world
// If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr
if (!HasAnyFlags(RF_ClassDefaultObject) && ensureMsgf(GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *GetFullName())
&& !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable())
{
if (ULevel* Level = GetLevel())
{
return Level->OwningWorld;
}
}
return nullptr;
}
(ぶっちゃけ言うと、BPノード検索時やコンパイル時にはGetWorldでかえすのはnullptrでも動作はするのですが)一応BPノード編集時とそうでないときで取得方法を分けるのが無難かなと思います。
(GWorldを必ず返すようなのでも動きはしますが、いくらか不便な場合があるので限定的にするほうが後々いいかなと思います)
作ったクラス
KinnajiObject.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "KinnajiObject.generated.h"
/**
*
*/
UCLASS(Blueprintable,meta = (ShowWorldContextPin))
class OBJECTBASEBPSCONTEXT_API UKinnajiObject : public UObject
{
GENERATED_BODY()
virtual class UWorld* GetWorld() const override;
};
KinnajiObject.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "KinnajiObject.h"
UWorld* UKinnajiObject::GetWorld() const
{
//OuterがUPackage(Asset)ならBPノード編集時と判断
if (UPackage* PkgObj = Cast<UPackage>(GetOuter()))
{
UE_LOG(LogTemp, Log, TEXT("BP編集時なのでGWorldを返します"));
return GWorld;
}
return Super::GetWorld();
}
↓結果
これでいちいちWorldContextPinに代入する手間が省けました。
ちなみに、GetWorldで返すものをnullptrにすると、実行時の動作が担保されませんのでご注意を。
毎度煩雑ですが以上。
※この記事のサンプルプロジェクトは以下URLにアップされています。
サンプルプロジェクト