そんなときに、ブループリントの変数(ブループリント側で作成した変数)にアクセスする方法を紹介します。
以下の環境で動作確認しています。
Unreal Engine 5.1.1
Visual Studio 2022
Windows 11 Home
以下の状況では、UUserWidget を使って、ウィジェットブループリントの変数にアクセスする必要があります。
- エディタで作成したウィジェットブループリント (UMG) を C++ 側で生成するときに、CreateWidgetInstance() や UWidgetBlueprintLibrary::Create() を使っている。
- その UMG は親が UUserWidget なので、ブループリントの変数を UPROPERTY でバインドしていない。
- いろんな事情で、その UMG の親クラスを自前のクラスに置き換えられない。
必要になる状況は稀だと思います。
初心者向けの内容ではないので、C++ とウィジェットブループリントの扱いが分かっている人向けに説明します。
動作確認のために作成した UE プロジェクトの設定はこんな感じです。
小さくてみづらい場合は、クリックすることで拡大表示できます。
親クラスを UUserWidget にして、ウィジェットブループリントを二種類、新規作成します。
※ひとつは参照用です。
作成した UMG
- WB_VariableTest1 … C++ で生成する UMG
- WB_VariableTest2 … WB_VariableTest1 に持たせる UMG
WB_VariableTest2(1. で新規作成した参照用のウィジェットブループリント)はキャンバス・パネルの下に追加しておきます。
C++ で生成するのが面倒なので。
1. で新規作成したウィジェットブループリントに変数を追加します。
Integer, Float, String, Boolean, 参照用のウィジェットブループリント (UserWidget) の5つ。
公開したり編集可能にしたりする必要はありません。
変数名と型と初期値だけ設定します。
VariableUserWidget だけ、初期値をブループリントで設定します(後述)。
C++ で設定するのが面倒なので。
参照用のウィジェットブループリント (WB_VariableTest2) には、int 型の変数だけ追加。
WB_VariableTest1 に追加した参照用のウィジェットブループリント変数に、初期値をセットする処理を追加します。
コンパイルして保存したら、ブループリント側の編集はこれで終わりです。
以降は 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); //ここまで }
// 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" }); //ここまで } }
デフォルトの設定だと読み込むモジュールが足りないので、追加します。
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”
ビルドして 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; }
ブループリントが持つ変数の値がポインタで返って来るので、このテンプレート関数を作っておくと使いまわせて便利です。
ポインタで返って来るので、代入すれば値の変更もできます。
UE4 では FindProperty や GetPropertyValue_InContainer が使えましたが、UE5 では非推奨になっています。
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 で取得する汎用関数を作ることで対応しています。
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 が存在しないので、対応する必要はありません。
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 と同様に変数の値を取得しています。