UE4 列挙型Enumについてのメモ

# 概要
UnrealEngine4のEnumについてのメモです。
主にC++での扱い方となります。

## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2023/05/15| UE5.1でANY_PACKAGEが非推奨となった事の追記 |
|2024/09/11| Enumの文字列化について追記 |

## 参考
以下の記事を参考にいたしました、ありがとうございます。

[UE4 における enum class の「フラグ」化](https://usagi.hatenablog.jp/entry/2017/06/20/225459)
[UE4 C++とUnreal C++の列挙型の扱い](https://papersloth.hatenablog.com/entry/2017/07/07/005225)
[UE4 C++ BitFlags について](https://finap.hateblo.jp/entry/2020/12/08/000000)


## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.26, 5.3


# 関連ソース
"Engine\Source\Runtime\Core\Public\Misc\EnumClassFlags.h"
"Engine\Source\Runtime\Core\Public\Misc\EnumRange.h"


# Enumのフラグ化
以下のようにビットの値を入れて、`ENUM_CLASS_FLAGS`マクロを指定します。

```cpp:.h
#include "Misc/EnumClassFlags.h"

UENUM( BlueprintType, meta=( Bitflags ) )
enum class ETestDamageFlag : uint8
{
	None				= 0			UMETA(ToolTip="なし"),
	Magic				= (1<<0)	UMETA(ToolTip="魔法"),
	Physics				= (1<<1)	UMETA(ToolTip="物理"),
	Poison				= (1<<2)	UMETA(ToolTip="毒"),
	Fainting			= (1<<3)	UMETA(ToolTip="気絶"),
	NoDefended			= (1<<4)	UMETA(ToolTip="防御不可"),
};
ENUM_CLASS_FLAGS( ETestDamageFlag )
```

フラグのチェックは指定フラグのどれかが立っていると`true`を返す `EnumHasAnyFlags` と、指定フラグ全てが立っていると`true`を返す `EnumHasAllFlags` があります。
以下コード例。

```cpp:.cpp
// チェック対象のフラグ
auto _Flag = ETestDamageFlag::Magic | ETestDamageFlag::Poison;

// Anyでチェック
if( EnumHasAnyFlags(_Flag, ETestDamageFlag::Magic|ETestDamageFlag::Physics)){
	// Magic か Physics のいずれかのフラグが立っている
	UE_LOG(LogTemp, Log, TEXT("Any"));
}

// Allでチェック
if( EnumHasAllFlags(_Flag, ETestDamageFlag::Magic|ETestDamageFlag::Physics)){
	// Magic か Physics のどちらのフラグも立っている
	UE_LOG(LogTemp, Log, TEXT("All"));
}

```


# Enumのループ

## 範囲ループ1
`enum class`の定義の後、 `ENUM_RANGE_BY_COUNT`マクロを定義しておくことで範囲ループが可能です。
以下コード例。

```cpp:.h
#include "Misc/EnumRange.h"

UENUM(BlueprintType)
enum class EZodiacSignId : uint8 {
	ZID_NONE		UMETA(DisplayName = "None",			ToolTip = "00:未定義"),
	ZID_ARIES		UMETA(DisplayName = "Aries",		ToolTip = "01:牡羊座"),
	ZID_TAURUS		UMETA(DisplayName = "Taurus",		ToolTip = "02:牡牛座"),
	ZID_GEMINI		UMETA(DisplayName = "Gemini",		ToolTip = "03:双子座"),
	ZID_CANCER		UMETA(DisplayName = "Cancer",		ToolTip = "04:蟹座"),
	ZID_LEO			UMETA(DisplayName = "Leo",			ToolTip = "05:獅子座"),
	ZID_VIRGO		UMETA(DisplayName = "Virgo",		ToolTip = "06:乙女座"),
	ZID_LIBRA		UMETA(DisplayName = "Libra",		ToolTip = "07:天秤座"),
	ZID_SCORPIO		UMETA(DisplayName = "Scorpio",		ToolTip = "08:蠍座"),
	ZID_SAGITTARIUS	UMETA(DisplayName = "Sagittarius",	ToolTip = "09:射手座"), 
	ZID_CAPPRICORN	UMETA(DisplayName = "Capricorn",	ToolTip = "10:山羊座"),
	ZID_AQUARIUS	UMETA(DisplayName = "Aquarius",		ToolTip = "11:水瓶座"),
	ZID_PISCES		UMETA(DisplayName = "Pisces",		ToolTip = "12:魚座"),

	Num				UMETA(Hidden)
};
ENUM_RANGE_BY_COUNT(EZodiacSignId, EZodiacSignId::Num);
```

上記のenum定義を範囲ループさせます。`UEnum::GetDisplayValueAsText`にてテキスト型を取得し表示します。

```cpp:.cpp
for (EZodiacSignId _EnumId : TEnumRange<EZodiacSignId>()){
	// Enum定義を文字列として出力
	UE_LOG(LogTemp, Log, TEXT("%s"), *UEnum::GetDisplayValueAsText(_EnumId).ToString() );
}
```

以下出力結果です。
`enum class`定義に `DisplayName` のメタ情報の有無で結果が変わるようです。

```text:出力結果1(DisplayNameがある場合)
LogTemp: None
LogTemp: Aries
LogTemp: Taurus
LogTemp: Gemini
LogTemp: Cancer
LogTemp: Leo
LogTemp: Virgo
LogTemp: Libra
LogTemp: Scorpio
LogTemp: Sagittarius
LogTemp: Capricorn
LogTemp: Aquarius
LogTemp: Pisces
```

```text:出力結果2（DisplayNameがない場合）
LogTemp: ZID NONE
LogTemp: ZID ARIES
LogTemp: ZID TAURUS
...以下省略...
```


## 範囲ループ2
`ENUM_RANGE_BY_COUNT`は頭から指定数分ループしていますが、`ENUM_RANGE_BY_FIRST_AND_LAST` は最初と最後を指定することでこの範囲をループできます。

```cpp:.h
// Aries から Virgoまで指定
ENUM_RANGE_BY_FIRST_AND_LAST(EZodiacSignId, EZodiacSignId::ZID_ARIES, EZodiacSignId::ZID_VIRGO);
```

## 範囲ループ3
`ENUM_RANGE_BY_VALUES` はループするリストを指定するようです。
連番になっていない場合に使えると思われます。

```cpp:.h
#include "Misc/EnumRange.h"

UENUM()
enum class ETestNo : uint8 {
	APPLE = 10,
	STRAWBERRY = 20,
	GRAPE = 30,
};
ENUM_RANGE_BY_VALUES(ETestNo, ETestNo::APPLE, ETestNo::STRAWBERRY, ETestNo::GRAPE);
```

以下、出力結果。

```text:出力結果
LogTemp: APPLE
LogTemp: STRAWBERRY
LogTemp: GRAPE
```


## FindObjectを使ったループ
`FindObject` を使ってEnum定義を取得してループする方法です。
以下コード例。

```cpp:.cpp
const UEnum* _EnumPtr = FindObject<UEnum>(ANY_PACKAGE, TEXT("EZodiacSignId"), true);
if (_EnumPtr != nullptr){
	for (int32 _Lp = 0; _Lp < _EnumPtr->NumEnums()-1; _Lp++){
		EZodiacSignId _EnumValue = (EZodiacSignId)(_EnumPtr->GetValueByIndex(_Lp));
			
		UE_LOG(LogTemp, Log, TEXT("%s"), *UEnum::GetDisplayValueAsText(_EnumValue).ToString() );
	}
}
```

この場合、定義されているものが全て出力されるため、最後の`Num`も出力されます。for文 でのループ数を変えるか、if文で不要な定義を抜くなどの対応が必要になるかもしれません。


# その他
## Enum定義のC++での文字列取得
`UEnum::GetDisplayValueAsText()` にてテキスト型を取得するか、以下のようなマクロを使うのが良いみたいです。

```cpp:Enum文字列取得マクロ
#define GETENUMSTRING(etype, evalue) ( (FindObject<UEnum>(ANY_PACKAGE, TEXT(etype), true) != nullptr) ? FindObject<UEnum>(ANY_PACKAGE, TEXT(etype), true)->GetNameStringByIndex((int32)evalue) : FString("Invalid - are you sure enum uses UENUM() macro?") )

```


```cpp:GetNameStringByValue()を使った場合
#include "UObject/UObjectGlobals.h"
#include "UObject/UnrealType.h"

FString GetEnumValueAsString(const EMyEnum _EnumValue)
{
	// StaticEnumで列挙型の情報を取得し、その値を文字列に変換
	const UEnum* _EnumPtr = StaticEnum<EMyEnum>();
	if (!_EnumPtr)
	{
		return( FString("Invalid") );
	}

	// EnumValueから対応する文字列を取得
	return( _EnumPtr->GetNameStringByValue(static_cast<int64>(_EnumValue)) );
}

// 表示する
void Print()
{
	EMyEnum _EnumValue = /*定義したEnum値*/;
	FString _ENumStr = GetEnumValueAsString(_EnumValue);
	UE_LOG(LogTemp, Warning, TEXT("%s"), *_EnumStr);
}
```


## Enumのリダイレクタ
Enum定義毎に旧名と新名を指定します。
以下コード例。

```ini:Config/DefaultEngine.ini
[CoreRedirects]
+EnumRedirects=(OldName="EItemKind", ValueChanges=(("ITEM_KIND_RINGO","ITEM_KIND_APPLE"),("ITEM_KIND_ICHIGO","ITEM_KIND_STRAWBERRY"),("ITEM_KIND_BUDOU","ITEM_KIND_GRAPE")))

```


# 追記
UE5.1から `ANY_PACKAGE`が非推奨になったようです。
以下、変更コード例。

```cpp:
// 変更前
const UEnum* _EnumObj = FindObject(ANY_PACKAGE, TEXT("EMyEnum"));

// 変更後 
const UEnum* _EnumObj = FindObject(nullptr, TEXT("/Script/MyProject.EMyEnum"));
```

# まとめ
使いたいときによく忘れているのでメモ書きとしてまとめています。