【UE5】UENUMの独特な記述方法と注意点について
プログラマーの尾関です。
Unreal Engineの列挙型である UENUM は通常のC++における enum といくつか異なった特徴があります。今回は UENUM としての特徴や注意点などをまとめたいと思います。
UENUMの概要
UENUMの基本構造
UENUM(BlueprintType)
enum class ECharacterState : uint8
{
Idle UMETA(DisplayName="アイドル状態"),
Walking UMETA(DisplayName="歩行中"),
Running UMETA(Hidden) // ブループリント非表示
};
ENUMを宣言するには、UENUM
マクロで宣言し、基底型をuint8に指定するという構造を持っています。
また UPROPERTYやUFUNCTIONマクロなどと同様に UMETA で特殊な情報を付与することが可能です。
通常のenumとの主な違い
通常のenumと UENUM との違いをまとめた表は以下のとおりです。

ブループリントや Unreal エディタとの連携機能が強力なのが利点です。
UENUMの注意点
UENUM(BlueprintType) は uint8 しかサポートしていない
概要で説明した通り、UENUM(BlueprintType) でブループリントで公開する際、以下のように UENUM のデータ型を uint8 以外にするとコンパイルエラーとなります。
// キャラクターID.(リソースと共通)
UENUM(BlueprintType)
enum class ECharacterId : int32 {
None UMETA(DisplayName = "ダミーデータ") = 0,
...
UENUMは enum よりも型の制約が厳しい
UENUMは enun と異なり、暗黙の数値変換が行われません。例えば以下の UENUM の定義があるとします。
UENUM()
enum class EHUD3DHand : uint8 {
None = 255, // uint8 なので "-1" は使えません
Left = 0, // 左手.
Right = 1, // 右手.
Max = 2,
};
これをC++で扱う場合、以下のコードはコンパイルエラーとなります。
// WICの取得.
UWidgetInteractionComponent* UCPP_WidgetAim::GetWidgetInteractionComponent(EHUD3DHand Hand)
{
if(0 <= Hand && Hand < EHUD3DHand::Max) { // 暗黙の数値変換はできないのでコンパイルエラー.
return m_pMCCList[Hand]; // 暗黙の数値変換はできないのでコンパイルエラー
}
return nullptr;
}
そこで、static_cast<uint8> を使用して、明示的に型変換を行う必要があります。
// WICの取得.
UWidgetInteractionComponent* UCPP_WidgetAim::GetWidgetInteractionComponent(EHUD3DHand Hand)
{
uint8 tmp = static_cast<uint8>(Hand); // 明示的な型変換.
if(0 <= tmp && tmp < static_cast<uint8>(EHUD3DHand::Max)) { // 明示的な型変換.
return m_pMCCList[tmp];
}
return nullptr;
}
比較演算子でも cast が必要なので、cast を減らすために switch~case文で書くというのも一つの方法です。
// WICの取得.
UWidgetInteractionComponent* UCPP_WidgetAim::GetWidgetInteractionComponent(EHUD3DHand Hand)
{
switch(Hand) {
case EHUD3DHand::Left:
case EHUD3DHand::Right:
return m_pWICList[static_cast<uint8>(Hand)];
default:
return nullptr;
}
}
プログラムでは、列挙型の最大値を配列の要素数の最大としたいことがよくあると思いますが、これも明示的なcastが必要なので注意が必要ですね。
UPROPERTY()
UWidgetInteractionComponent* m_pWICList[static_cast<uint8>(EHUD3DHand::Max)];
UFUNCTIONには通常のenumは渡せない
UFUNCTION を使うときには、通常の enum は渡せません。
enum class eHUD3DHand {
None = 255,
Left = 0, // 左手.
Right = 1, // 右手.
Max = 2,
};
// WICの取得.
UFUNCTION()
UWidgetInteractionComponent* GetWidgetInteractionComponent(eHUD3DHand Hand); // ←コンパイルエラー
UFUNCITON の引数に enum を渡したい場合には UENUM を渡す必要があります。
UENUM()
enum class EHUD3DHand {
None = 255,
Left = 0, // 左手.
Right = 1, // 右手.
Max = 2,
};
// WICの取得.
UFUNCTION()
UWidgetInteractionComponent* GetWidgetInteractionComponent(EHUD3DHand Hand);
UENUM(BlueprintType)のUMETA(Hidden)はデフォルト引数にできない
UENUMでの値に対するメタ要素、UMETA(Hidden)
値はBlueprintで非表示になります。それをデフォルト値として非表示の列挙値を設定すると、Blueprint側で整合性が取れなくなるため、コンパイルエラーが発生します。
// イベント種別.
UENUM(BlueprintType)
enum class EEventType : uint8 {
None UMETA(Hidden),
Cutscene UMETA(DisplayName = "カットシーン"),
Talk UMETA(DisplayName = "会話"),
Other UMETA(DisplayName = "その他"),
Test UMETA(DisplayName = "テスト"),
Max UMETA(Hidden)
};
例えば上記の列挙値では「None」や「Max」は Hidden属性がついているので、これをデフォルト値として使用できません。
UENUM を文字列に変換する
これはちょっとしたTipsですが、UENUM は内部的に定義名を文字列として持っているため、定義名を文字列として取得することが可能です。
UEnum::GetValueAsString()を使用して文字列にする (名前空間を含める場合)
UEnum::GetValueAsString() を使うと UENUM を FString に変換できます。ただしこの方法は「名前空間」が含まれた文字列となります。
UEnum::GetNameStringByIndex() を使用する (名前空間を除外した定義名のみ欲しい場合)
UEnum::GetNameStringByIndex()を使うと名前空間を除外した定義名のみ取得できます。
少し処理がややこしいので以下のような関数を作っておくと良いと思います。
// UENUMに対応する文字列を取得する.
FString EnumToFString(FString pEnumType, uint32 EnumValue)
{
// 列挙型の定義名.
FString aEnumTypeString(pEnumType);
// 列挙型の定義名から UEnum を取得.
const TCHAR* pEnumChar = *aEnumTypeString;
const UEnum* const pEnum = FindObject<UEnum>(ANY_PACKAGE, pEnumChar);
return *(pEnum ? pEnum->GetNameStringByIndex( static_cast<uint8>(EnumValue) ) : "null");
}
この関数は以下のように呼び出して使います。
auto e = EEventType::Cutscene;
FString s = EnumToFString( TEXT("EEventType"), e);