【UE5】ウィジェットブループリント(UMG)の変数をバインドすることなく(UPROPERTYを使わずに) C++ で取得する方法【C++】

こちらの記事で、ウィジェットブループリントと C++ のソースコードをバインドする方法を紹介しましたが、バインドできない状況がまれにあります。
そんなときに、ブループリントの変数(ブループリント側で作成した変数)にアクセスする方法を紹介します。

以下の環境で動作確認しています。

Unreal Engine 5.1.1
Visual Studio 2022
Windows 11 Home

以下の状況では、UUserWidget を使って、ウィジェットブループリントの変数にアクセスする必要があります。

  • エディタで作成したウィジェットブループリント (UMG) を C++ 側で生成するときに、CreateWidgetInstance() や UWidgetBlueprintLibrary::Create() を使っている。
  • その UMG は親が UUserWidget なので、ブループリントの変数を UPROPERTY でバインドしていない。
  • いろんな事情で、その UMG の親クラスを自前のクラスに置き換えられない。

必要になる状況は稀だと思います。
初心者向けの内容ではないので、C++ とウィジェットブループリントの扱いが分かっている人向けに説明します。

動作確認のための準備

動作確認のために作成した UE プロジェクトの設定はこんな感じです。

小さくてみづらい場合は、クリックすることで拡大表示できます。

1. UMG を新規作成

親クラスを UUserWidget にして、ウィジェットブループリントを二種類、新規作成します。
※ひとつは参照用です。

作成した UMG

  1. WB_VariableTest1 … C++ で生成する UMG
  2. WB_VariableTest2 … WB_VariableTest1 に持たせる UMG
2. 作成したブループリントにウィジェットを追加

WB_VariableTest2(1. で新規作成した参照用のウィジェットブループリント)はキャンバス・パネルの下に追加しておきます。
C++ で生成するのが面倒なので。

3. 作成したブループリントに変数を追加

1. で新規作成したウィジェットブループリントに変数を追加します。
Integer, Float, String, Boolean, 参照用のウィジェットブループリント (UserWidget) の5つ。

公開したり編集可能にしたりする必要はありません。
変数名と型と初期値だけ設定します。

VariableUserWidget だけ、初期値をブループリントで設定します(後述)。
C++ で設定するのが面倒なので。

参照用のウィジェットブループリント (WB_VariableTest2) には、int 型の変数だけ追加。

4. WB_VariableTest1 のブループリントを編集

WB_VariableTest1 に追加した参照用のウィジェットブループリント変数に、初期値をセットする処理を追加します。

コンパイルして保存したら、ブループリント側の編集はこれで終わりです。
以降は C++ 側と動作確認だけです。

5. C++ に処理を追加

ウィジェットブループリントを読み込んで生成し、ブループリントの変数にアクセスする処理を C++ 側に追加します。
新規でソースコードを作るのが面倒だったので、UE が自動生成した GameMode に処理を追加しています。
プロジェクト名が UMGVariableTest なので、AUMGVariableTestGameMode クラスがあります。
ヘッダファイルはいじらず、cpp 側に処理を追加しています。

全コードは以下になります。

// Copyright Epic Games, Inc. All Rights Reserved.

#include "UMGVariableTestGameMode.h"
#include "UMGVariableTestCharacter.h"
#include "UObject/ConstructorHelpers.h"

//以下、追加したインクルード
#include "Editor.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
#include "UObject/UnrealType.h"
#include "UObject/UnrealTypePrivate.h"
//ここまで

//以下、追加したコード
namespace UE { namespace GameMode {

const float DISPLAY_SECONDS = 30.f;
const FLinearColor DISPLAY_COLOR = FLinearColor(1.f, 0.f, 0.5f, 1.f);

bool IsInGame()
{
	return !GIsEditor;
}

bool IsInPIE()
{
	return GEditor && GEditor->PlayWorld && !GEditor->bIsSimulatingInEditor;
}

UClass* LoadWidgetClass()
{
	const FString WidgetClassPath = TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/WP_VariableTest1.WP_VariableTest1_C'");
	FSoftClassPath WidgeClasstPath(WidgetClassPath);
	return WidgeClasstPath.TryLoadClass<UUserWidget>();
}

UUserWidget* CreateWidget(UWorld* World)
{
	UClass* WidgetClass = UE::GameMode::LoadWidgetClass();
	if (!IsValid(WidgetClass))
	{
		return nullptr;
	}
	APlayerController* Player = UGameplayStatics::GetPlayerController(World, 0);
	UUserWidget* Widget = UWidgetBlueprintLibrary::Create(World, WidgetClass, Player);
	if (!IsValid(Widget))
	{
		return nullptr;
	}
	return Widget;
}

template <class PropertyType, typename ValueType>
ValueType* GetWidgetVariableValuePtr(UUserWidget* InWidget, const TCHAR* const InVariableName)
{
	UClass* WidgetClass = InWidget->GetClass();
	if (!IsValid(WidgetClass))
	{
		return nullptr;
	}
	PropertyType* Property = FindFProperty<PropertyType>(WidgetClass, InVariableName);
	if (Property)
	{
		ValueType* Value = Property->ContainerPtrToValuePtr<ValueType>(InWidget, 0);
		return Value;
	}
	return nullptr;
}

bool GetWidgetIntValue(UUserWidget* InWidget, const TCHAR* const InVariableName, int32& OutValue)
{
	int* Value = UE::GameMode::GetWidgetVariableValuePtr<FIntProperty, int>(InWidget, InVariableName);
	if (Value)
	{
		OutValue = *Value;
		return true;
	}
	int64* Value1 = UE::GameMode::GetWidgetVariableValuePtr<FInt64Property, int64>(InWidget, InVariableName);
	if (Value1)
	{
		OutValue = static_cast<int32>(*Value1);
		return true;
	}
	int16* Value2 = UE::GameMode::GetWidgetVariableValuePtr<FInt16Property, int16>(InWidget, InVariableName);
	if (Value2)
	{
		OutValue = *Value2;
		return true;
	}
	int8* Value3 = UE::GameMode::GetWidgetVariableValuePtr<FInt8Property, int8>(InWidget, InVariableName);
	if (Value3)
	{
		OutValue = *Value3;
		return true;
	}
	return false;
}

bool GetWidgetFloatValue(UUserWidget* InWidget, const TCHAR* const InVariableName, float& OutValue)
{
	float* Value = UE::GameMode::GetWidgetVariableValuePtr<FFloatProperty, float>(InWidget, InVariableName);
	if (Value)
	{
		OutValue = *Value;
		return true;
	}
	double* Value2 = UE::GameMode::GetWidgetVariableValuePtr<FDoubleProperty, double>(InWidget, InVariableName);
	if (Value2)
	{
		OutValue = static_cast<float>(*Value2);
		return true;
	}
	return false;
}

void ShowAllProperty(UUserWidget* InWidget)
{
	UClass* Class = InWidget->GetClass();
	for (TFieldIterator<FProperty> It(Class, EFieldIteratorFlags::IncludeSuper); It; ++It)
	{
		FProperty* Property = *It;
		if (Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
		{
			FString PrintString = FString::Printf(TEXT("Property=%s"), *Property->GetName());
			UKismetSystemLibrary::PrintString(GEngine->GetWorld(), PrintString, true, true, FLinearColor(0.f, 1.f, 0.f, 1.f), DISPLAY_SECONDS * 10.f);
		}
	}
}

void PrintTextIntValue(UUserWidget* InWidget)
{
	const TCHAR* const VariableName = TEXT("VariableInt");
	int32 IntValue = 0;
	if (UE::GameMode::GetWidgetIntValue(InWidget, VariableName, IntValue))
	{
		FString PrintString = FString::Printf(TEXT("%s=%d"), VariableName, IntValue);
		UKismetSystemLibrary::PrintString(GEngine->GetWorld(), PrintString, true, true, DISPLAY_COLOR, DISPLAY_SECONDS);
	}
}

void PrintTextFloatValue(UUserWidget* InWidget)
{
	const TCHAR* const VariableName = TEXT("VariableFloat");
	float FloatValue = 0.0f;
	if (UE::GameMode::GetWidgetFloatValue(InWidget, VariableName, FloatValue))
	{
		FString PrintString = FString::Printf(TEXT("%s=%f"), VariableName, FloatValue);
		UKismetSystemLibrary::PrintString(GEngine->GetWorld(), PrintString, true, true, DISPLAY_COLOR, DISPLAY_SECONDS);
	}
}

void PrintTextStringValue(UUserWidget* InWidget)
{
	const TCHAR* const VariableName = TEXT("VariableString");
	if (FString* StringValue = UE::GameMode::GetWidgetVariableValuePtr<FStrProperty, FString>(InWidget, VariableName))
	{
		FString PrintString = FString::Printf(TEXT("%s=%s"), VariableName, **StringValue);
		UKismetSystemLibrary::PrintString(GEngine->GetWorld(), PrintString, true, true, DISPLAY_COLOR, DISPLAY_SECONDS);
	}
}

void PrintTextBoolValue(UUserWidget* InWidget)
{
	const TCHAR* const VariableName = TEXT("bVariableBool");
	if (bool* BoolValue = UE::GameMode::GetWidgetVariableValuePtr<FBoolProperty, bool>(InWidget, VariableName))
	{
		FString PrintString = FString::Printf(TEXT("%s=%s"), VariableName, *BoolValue ? TEXT("true") : TEXT("false"));
		UKismetSystemLibrary::PrintString(GEngine->GetWorld(), PrintString, true, true, DISPLAY_COLOR, DISPLAY_SECONDS);
	}
}

void PrintTextUserWidgetIntValue(UUserWidget* InWidget)
{
	const TCHAR* const VariableName = TEXT("VariableUserWidget");
	if (TObjectPtr<UObject>* ObjectValue = UE::GameMode::GetWidgetVariableValuePtr<FObjectProperty, TObjectPtr<UObject>>(InWidget, VariableName))
	{
		if (UUserWidget* ChildWidget = CastChecked<UUserWidget>(ObjectValue->Get()))
		{
			const TCHAR* const WidgetVariableName = TEXT("NewVar");
			int32 IntValue = 0;
			if (UE::GameMode::GetWidgetIntValue(ChildWidget, WidgetVariableName, IntValue))
			{
				FString PrintString = FString::Printf(TEXT("%s %s=%d"), VariableName, WidgetVariableName, IntValue);
				UKismetSystemLibrary::PrintString(GEngine->GetWorld(), PrintString, true, true, DISPLAY_COLOR, DISPLAY_SECONDS);
			}
		}
	}
}

}}
//ここまで

AUMGVariableTestGameMode::AUMGVariableTestGameMode()
{
	// set default pawn class to our Blueprinted character
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"));
	if (PlayerPawnBPClass.Class != NULL)
	{
		DefaultPawnClass = PlayerPawnBPClass.Class;
	}

	//以下、ブループリントの変数を取得するサンプルコード

	const bool bIsInGame = UE::GameMode::IsInGame();
	const bool bIsInPIE = UE::GameMode::IsInPIE();
	if (!bIsInGame && !bIsInPIE)
	{
		//	ゲーム中でも PIE 中でもないときは、何もしない
		return;
	}

	UWorld* World = GetWorld();
	if (!IsValid(World))
	{
		//	World が無効なときは、何もしない
		return;
	}

	UUserWidget* Widget = UE::GameMode::CreateWidget(World);
	if (!IsValid(Widget))
	{
		//	Widget が無効なときは、何もしない
		return;
	}

	Widget->AddToViewport();

	//	ブループリントの変数を取得して、ゲーム画面の左上に表示する
	UE::GameMode::PrintTextIntValue(Widget);
	UE::GameMode::PrintTextFloatValue(Widget);
	UE::GameMode::PrintTextStringValue(Widget);
	UE::GameMode::PrintTextBoolValue(Widget);
	UE::GameMode::PrintTextUserWidgetIntValue(Widget);

	//	値をうまく取得できないときには、以下の関数を使ってみると良い
	//	ステップ実行してオブジェクトのパラメータを確認すると解決するヒントになる
	//UE::GameMode::ShowAllProperty(Widget);

	//ここまで
}
6. Build.cs にモジュールを追加
// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class UMGVariableTest : ModuleRules
{
	public UMGVariableTest(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "EnhancedInput" });
		//以下を追加
		PrivateDependencyModuleNames.AddRange(new string[] { "UnrealEd", "UMG" });
		//ここまで
	}
}

デフォルトの設定だと読み込むモジュールが足りないので、追加します。

7. 何故か無関係のソースコードでエラーが出るかも知れない

AUMGVariableTestCharacter.cpp でエラーが出るかも知れません。

1>F:\UMGVariableTest\Source\UMGVariableTest\UMGVariableTestCharacter.cpp(66): error C2027: 認識できない型 'ULocalPlayer' が使われています。
1>F:\UE_5.1\Engine\Source\Runtime\Engine\Public\Subsystems\LocalPlayerSubsystem.h(9): note: 'ULocalPlayer' の宣言を確認してください
1>F:\UMGVariableTest\Source\UMGVariableTest\UMGVariableTestCharacter.cpp(66): error C2065: 'GetSubsystem': 定義されていない識別子です。
1>F:\UMGVariableTest\Source\UMGVariableTest\UMGVariableTestCharacter.cpp(66): error C2275: 'UEnhancedInputLocalPlayerSubsystem': 型の代わりに式が必要です
1>F:\UMGVariableTest\Source\UMGVariableTest\UMGVariableTestCharacter.cpp(66): error C2027: 認識できない型 'APlayerController' が使われています。
1>F:\UE_5.1\Engine\Plugins\EnhancedInput\Source\EnhancedInput\Public\EnhancedInputSubsystemInterface.h(14): note: 'APlayerController' の宣言を確認してください

出た場合は、以下のインクルードを追加します。

#include “Engine/LocalPlayer.h”
#include “GameFramework/PlayerController.h”

8. ビルドして動作確認

ビルドして UE5 エディタを起動し、PIE で確認します。

追加したコードの説明
ウィジェットブループリントの読み込み
const FString WidgetClassPath = TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/WP_VariableTest1.WP_VariableTest1_C'");
FSoftClassPath WidgeClasstPath(WidgetClassPath);
return WidgeClasstPath.TryLoadClass<UUserWidget>();

WidgetClassPath に指定しているアセットリファレンスの末尾に _C を付ける必要があります。
UE5 エディタのコンテンツブラウザでアセットを右クリックすると、アセットリファレンスをクリップボードにコピーできますが、そちらには _C が付いていません。
付け忘れてロードに失敗しがちなので注意が必要です。

ウィジェットブループリントの生成

UUserWidget* Widget = UWidgetBlueprintLibrary::Create(World, WidgetClass, Player);

Player が nullptr でも生成できます。

ブループリントの変数にアクセスするための汎用関数
template <class PropertyType, typename ValueType>
ValueType* GetWidgetVariableValuePtr(UUserWidget* InWidget, const TCHAR* const InVariableName)
{
	UClass* WidgetClass = InWidget->GetClass();
	if (!IsValid(WidgetClass))
	{
		return nullptr;
	}
	PropertyType* Property = FindFProperty<PropertyType>(WidgetClass, InVariableName);
	if (Property)
	{
		ValueType* Value = Property->ContainerPtrToValuePtr<ValueType>(InWidget, 0);
		return Value;
	}
	return nullptr;
}

ブループリントが持つ変数の値がポインタで返って来るので、このテンプレート関数を作っておくと使いまわせて便利です。
ポインタで返って来るので、代入すれば値の変更もできます。

FindFProperty() と ContainerPtrToValuePtr() を使っていますが、これらの関数は UE5 用です。
UE4 では FindProperty や GetPropertyValue_InContainer が使えましたが、UE5 では非推奨になっています。
float 型変数を扱うときの注意点
bool GetWidgetFloatValue(UUserWidget* InWidget, const TCHAR* const InVariableName, float& OutValue)
{
	float* Value = UE::GameMode::GetWidgetVariableValuePtr<FFloatProperty, float>(InWidget, InVariableName);
	if (Value)
	{
		OutValue = *Value;
		return true;
	}
	double* Value2 = UE::GameMode::GetWidgetVariableValuePtr<FDoubleProperty, double>(InWidget, InVariableName);
	if (Value2)
	{
		OutValue = static_cast<float>(*Value2);
		return true;
	}
	return false;
}

ブループリントには float 型しかありませんが、内部では double で持っているときと、float で持っているときの2パターンあります。
どういうときに double になったり、float になるのかは不明です。
float で取得できなかったら、double で取得する汎用関数を作ることで対応しています。

int 型変数を扱うときの注意点
bool GetWidgetIntValue(UUserWidget* InWidget, const TCHAR* const InVariableName, int32& OutValue)
{
	int* Value = UE::GameMode::GetWidgetVariableValuePtr<FIntProperty, int>(InWidget, InVariableName);
	if (Value)
	{
		OutValue = *Value;
		return true;
	}
	int64* Value1 = UE::GameMode::GetWidgetVariableValuePtr<FInt64Property, int64>(InWidget, InVariableName);
	if (Value1)
	{
		OutValue = static_cast<int32>(*Value1);
		return true;
	}
	int16* Value2 = UE::GameMode::GetWidgetVariableValuePtr<FInt16Property, int16>(InWidget, InVariableName);
	if (Value2)
	{
		OutValue = *Value2;
		return true;
	}
	int8* Value3 = UE::GameMode::GetWidgetVariableValuePtr<FInt8Property, int8>(InWidget, InVariableName);
	if (Value3)
	{
		OutValue = *Value3;
		return true;
	}
	return false;
}

int 型の変数はブループリントだと Integer と Ingeter64 の2種類ありますが、内部でどう扱われるのか分からないので、どの型で扱われていても取得できるようにしています。
int128 は FInt128Property が存在しないので、対応する必要はありません。

UUserWidget の値を取得する方法
void PrintTextUserWidgetIntValue(UUserWidget* InWidget)
{
	const TCHAR* const VariableName = TEXT("VariableUserWidget");
	if (TObjectPtr<UObject>* ObjectValue = UE::GameMode::GetWidgetVariableValuePtr<FObjectProperty, TObjectPtr<UObject>>(InWidget, VariableName))
	{
		if (UUserWidget* ChildWidget = CastChecked<UUserWidget>(ObjectValue->Get()))
		{
			const TCHAR* const WidgetVariableName = TEXT("NewVar");
			int32 IntValue = 0;
			if (UE::GameMode::GetWidgetIntValue(ChildWidget, WidgetVariableName, IntValue))
			{
				FString PrintString = FString::Printf(TEXT("%s %s=%d"), VariableName, WidgetVariableName, IntValue);
				UKismetSystemLibrary::PrintString(GEngine->GetWorld(), PrintString, true, true, DISPLAY_COLOR, DISPLAY_SECONDS);
			}
		}
	}
}

UObject しか対応していないので、UObject で取得します(UObject は UUserWidget の親クラスです)。

TObjectPtr<UObject>* ObjectValue = UE::GameMode::GetWidgetVariableValuePtr<FObjectProperty, TObjectPtr<UObject>>(InWidget, VariableName)

返って来るのは TObjectPtr<UObject>* です。
TObjectPtr は、ほぼ生ポインタと同じものですが、生ポインタでは取得できません。

UUserWidget* ChildWidget = CastChecked<UUserWidget>(ObjectValue->Get())

取得できたら、UUserWidget にダウンキャストします。

この ChildWidget が WB_VariableTest2 の情報を持っています。
あとは WB_VariableTest1 と同様に変数の値を取得しています。

参考にした Web ページ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です