概要
UnrealEngine の EditorUtilityWidget を C++クラスを継承する形にしてアクセスしてみた記録です。
UE4 WidgetをC++クラス継承してアクセスする
内容的には上の記事の EditorUtilityWidget 版です。
エディタ用モジュールを分割していないため、パッケージ化時に問題が発生しています。
解決にはエディタ用モジュールを作成し、UEditorUtilityクラスを継承したクラスをそちらへ移動させないといけません。
→モジュール作成メモ
環境
Windows10
Visual Studio 2017
UnrealEngine 4.22
参考
以下を参考にさせて頂きました、ありがとうございます。
[UE4]エディタ上で動作するツール・エディタ拡張をUMGで簡単に作れる Editor Utility Widget について
実装手順
EditorUtilityWidgetの準備
コンテンツブラウザから[新規追加] -> [Editor Utilities] -> [Editor Widget] を選択し、名前を付ける。(例:TestEditorWidget.uasset)
アクターの座標を表示させるためのテキストブロックを配置しています。それぞれ名前を Pos_X, Pos_Y, Pos_Z とつけています。
親にするC++クラスの準備
右クリック -> [新規C++クラス...] を選択、UEditorUtilityWidgetを継承して新規クラスを作成する。(例:TestEditorUtilityWidget.h, TestEditorUtilityWidget.cpp)
備考:継承クラスUEditorUtilityWidget
継承するC++クラスソースは
Engine/Source/Editor/Blutility/Classes/EditorUtilityWidget.h
Engine/Source/Editor/Blutility/Private/EditorUtilityWidget.cpp
にあります。
このクラス UEditorUtilityWidget は UUserWidget を継承しています。
他にはウィンドウメニューに追加するかの設定とウィジェットを実行した際のアクションについてのメソッドがあるようです。
親クラスを切り替える
ウィジェットのエディターウィンドウから[ファイル] -> [ブループリントの親を変更]を選択し、先ほどのC++クラスを指定する。
右上の表示が[親クラス:Test Editor Utility Widget]に切り替わります。
親C++クラスにコードを追加する
先ほど作成した widget のテキストブロックを更新する処理を追加します。
コード例では tick()で全て行っていますが、呼ばれるタイミング違いの初期化処理がありますので、適宜そちらへ移すべきかと思います。(再実行時など)
UCLASS()
class CHICKENRACE_API UTestEditorUtilityWidget : public UEditorUtilityWidget
{
GENERATED_BODY()
public:
// コンストラクタ
UTestEditorUtilityWidget(const FObjectInitializer& ObjectInitializer);
protected:
// 初期化
virtual void NativeOnInitialized() override;
virtual void NativePreConstruct() override;
virtual void NativeConstruct() override;
// 終了化
virtual void NativeDestruct() override;
// tick処理
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
};
アクターATestActorを検索して、その座標を更新する例です。
実際は tick() で全検索を行うのは処理がかかると思いますので別途処理をすべきです。
// コンストラクタ
UTestEditorUtilityWidget::UTestEditorUtilityWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
// 初期化処理(生成時)
void UTestEditorUtilityWidget::NativeOnInitialized()
{
Super::NativeOnInitialized();
}
// 開始前処理
void UTestEditorUtilityWidget::NativePreConstruct()
{
Super::NativePreConstruct();
}
// 開始処理(出現時)
void UTestEditorUtilityWidget::NativeConstruct()
{
Super::NativeConstruct();
}
// 終了処理(消去時)
void UTestEditorUtilityWidget::NativeDestruct()
{
Super::NativeDestruct();
}
// tick処理
void UTestEditorUtilityWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
// 継承元処理
Super::NativeTick(MyGeometry, InDeltaTime);
if (GEngine->GameViewport == nullptr)return;
// アクターを検索
for (TActorIterator<ATestActor>ActItr(GEngine->GameViewport->GetWorld()); ActItr; ++ActItr)
{
ATestActor* Actr = *ActItr;
FVector _Loc;
if (Actr) {
_Loc = Actr->GetActorLocation();
// 各テキストブロックに座標をセット
UTextBlock* _pTextX = Cast<UTextBlock>(this->GetWidgetFromName("Pos_X"));
if (_pTextX) {
_pTextX->SetText(FText::FromString(FString::Printf(TEXT("%.1f"), _Loc.X)));
}
UTextBlock* _pTextY = Cast<UTextBlock>(this->GetWidgetFromName("Pos_Y"));
if (_pTextY) {
_pTextY->SetText(FText::FromString(FString::Printf(TEXT("%.1f"), _Loc.Y)));
}
UTextBlock* _pTextZ = Cast<UTextBlock>(this->GetWidgetFromName("Pos_Z"));
if (_pTextZ) {
_pTextZ->SetText(FText::FromString(FString::Printf(TEXT("%.1f"), _Loc.Z)));
}
}
}
}
実行結果
TestEditorWidgetを右クリックから実行するか、[ウィンドウ] -> [Editor Utility Widget] から選択して実行をします。
上記のように座標が更新されました。
注意点
EditorUtilityWidget は当たり前なのですがランタイム中ではなくても動くため、最初にレベルに存在しない(再生開始時にスポーンされるような)場合のオブジェクトがある場合、決め打ちでアクセスを行うとエディターがクラッシュしてしまいます。
上記のサンプルコードだと
if (GEngine->GameViewport == nullptr)return;
で再生中のみアクターを検索するようにしています。
問題点
パッケージ化すると以下のようなエラーがでます。
Missing precompiled manifest for 'EditorWidgets'.
This module was most likely not flagged for being included in a precompiled build - set 'PrecompileForTargets = PrecompileTargetsType.Any;' in EditorWidgets.build.cs to override.
エンジンのEditorWidgetモジュールの Build.cs を書き換える必要があるのでしょうか?
とりあえずモジュールが見当たらない感じなのでエディター時のみのモジュール追加をプロジェクトのBuild.csに以下のように書き加えてみました。
if(Target.bBuildEditor==true)
{
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Blutility",
"EditorWidgets"
}
);
}
次は以下のようなエラーになりました。
Couldn't find parent type for 'TestEditorUtilityWidget' named 'UEditorUtilityWidget' in current module or any other module parsed so far.
親クラス UEditorUtilityWidget が見つからないようです。
恐らく、ランタイム時とエディター時でのモジュールの切り分けができていないせいと思われます。
そのため、エディター時のみビルド対象となるように TestEditorUtilityWidgetクラスごと [#if WITH_EDITORONLY_DATA ~ #endif] か [#if WITH_EDITOR ~ #endif] で囲んでエディター時のビルドから外そうとしたのですが通常のC++マクロと違い、できませんでした。(WITH_EDITORONLY_DATAが効いていない?)
対処方法がわかる方は教えていただきたいです。
自分は結局、エディタ用モジュールを作成してそちらへC++コードを移行させて解決させました。
まとめ
サンプルではエディタ実行中のみでしたが、EditorUtilityWidgetはエディターのアセットにもアクセスでき、UIウィンドウはWidgetでつくれるため気軽にツールが作れる仕組みだと思います。(Editor Scripting Utilitiesプラグインを使うと機能が更に追加されるようです。)
今回のようにC++コードから継承する形にすると、ある程度の処理のまとまりはC++にてプログラマーが行って、それを使いプログラマー以外のエンジニアなどが独自にツールを作成することができると思います。