【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 エディタとの連携機能が強力なのが利点です。

Information

後述しますがUnreal Engineにおいて、列挙型を使う場合は基本的には UENUM を使用した方が良いです。

ただ uint8 制限があるため、255を超える項目数を使用する場合には通常の enum を使った方が良い場合もあります。

UENUMの注意点

UENUM(BlueprintType) は uint8 しかサポートしていない

概要で説明した通り、UENUM(BlueprintType) でブループリントで公開する際、以下のように UENUM のデータ型を uint8 以外にするとコンパイルエラーとなります。

// キャラクターID.(リソースと共通)
UENUM(BlueprintType)
enum class ECharacterId : int32 {
  None		UMETA(DisplayName = "ダミーデータ")	= 0,
  ...
Information

BlueprintTypeの指定がない場合は、UENUM()でuint8以外の型を基底型として定義することができます。

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属性がついているので、これをデフォルト値として使用できません。

Information

これについても BlueprintType がない UENUM (ブループリントに公開しない) であれば、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);

\ 最新情報をチェック /