enumをインデックスとした便利な配列の作り方

# はじめに
ゲームを作っていると難易度ごとで敵の体力や攻撃力などを設定できるようにしたいという状況に出くわすことがあると思います。
敵のデータテーブルを難易度ごとに作るなど色々と方法があると思いますが、今回はプロパティ上でできる方法をご紹介します。

例として難易度ごとに敵のデータを設定できるようにするため、適当に敵のデータ構造体と難易度の列挙型を作ります。

``` EnemyData.h
USTRUCT()
struct FEnemyData
{
	GENERATED_BODY()
	
public:
	UPROPERTY(EditAnywhere)
	int32 Health;

	UPROPERTY(EditAnywhere)
	int32 Attack;

	UPROPERTY(EditAnywhere)
	FName RewardId;
};
```
``` Difficulty.h
UENUM()
enum class EDifficulty : uint8
{
	Easy,
	Normal,
	Hard,

	Max
};
ENUM_RANGE_BY_COUNT(EDifficulty, EDifficulty::Max);
```
`ENUM_RANGE_BY_COUNT`マクロは`UENUM`を`foreach`で簡単に回せる`TEnumRange`というクラステンプレートを使うために定義しています。
[Iterating over UENUM with TEnumRange - ben ui](https://benui.ca/unreal/iterate-over-enum-tenumrange/)

---

まずすぐに思いつく方法としては以下の様に難易度の列挙型を`key`で敵データの構造体を`value`にした`TMap`を作るというのがあるかと思います。

``` Something.h
UPROPERTY(EditAnywhere, EditFixedSize)
TMap<EDifficulty, FEnemyData> EnumMap;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/28ff1e23-09a5-358c-679f-2354e1e3dff6.png)

Detailsパネルでの見た目は後で紹介する`enum`をインデックスとした配列とほとんど変わりませんが、普通の`TMap`だと難易度分の要素を自分で追加しないといけない上に、特定の難易度のデータがない状態などが起こり得ます。
そこで、`TArray`や`TMap`の要素数を編集できないようにする`UPROPERTY`のメタ指定子の`EditFixedSize`を使い、Detailsパネルから要素数の編集をできないようにして、以下の様にコンストラクタかどこかで難易度の種類分の要素を追加しておく必要があります。

``` Something.h
ATest()
{
	if (EnumMap.Num() == 0)
	{
		for (const auto& ArrayIndex : TEnumRange<EDifficulty>())
		{
			EnumMap.Add(ArrayIndex);
		}
	}
}
```
色々と面倒くさいですね...

# 作り方
それでは本題に入ります。
`enum`をインデックスとした配列は以下の様にして定義できます。

``` Something.h
UPROPERTY(EditAnywhere)
FEnemyData EnumArray[(int32)EDifficulty::Max];
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/e1035668-01ad-1492-af79-6e1ff23075fc.png)

この方法で定義すると`EditFixedSize`を設定しなくても要素数の変更ができないようになっています。
ちなみにC++で扱う際は`TArray`ではなく`C++`標準の固定長配列になっているため、要素を足したりはできません。
また、普通の固定長配列なので`C++`で要素を取得する際は以下の様に整数にキャストしてインデックスを指定する必要があります。

``` Something.h
EnumArray[static_cast<int32>(EDifficulty::Easy)].Attack;
```

# 余談
`EnumArray`の定義を見た時に「あれ？C++なのにCタイプのキャスト使ってない？」と思った方も多いと思います。
とりあえず、`static_cast`に直してみてみましょう。

``` Something.h
UPROPERTY(EditAnywhere)
FEnemyData EnumArray[static_cast<int32>(EDifficulty::Max)];
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/f353c8f8-4d45-c012-a0cb-b95575d34875.png)

難易度の表示名があるところがなぜか、`Index[N]`の表示になってしまっています。
エンジンコードでこの方法を使っているところを見るといずれもCタイプのキャストを使っていたため、私も`static_cast`に直したのですがどうやらCタイプキャストでないといけない理由があるようでした。
おそらく`UnrealHeaderTool`で`static_cast`を使った構文に対応していないことが原因だと思います。

---

また、`UnrealHeaderTool`関連でもう一つ問題があります。
難易度ごとに何かの有効無効を設定するために`bool`で同じように定義するとビルドできません。

``` Something.h
UPROPERTY(EditAnywhere)
bool EnumArray[static_cast<int32>(EDifficulty::Max)];
```
どうやら`bool`の固定長配列が`UnrealHeaderTool`でサポートされていないのが原因のようです。(なぜなのか...)
固定長配列ではサポートされていないだけで、`TArray<bool>`はサポートされています。
[Bool arrays in c++? - Forums](https://forums.unrealengine.com/t/bool-arrays-in-c/53008)

# おわりに
`[(int32)`で全体検索してみるとエンジンコードで結構使われているのが伺えます。（なのにこれに関する記事が全然ない不思議...）
余談で書いたこと以外にも`UPROPERTY`のメタ指定子の`TitleProperty`が使えないなどの制約もありますが、便利なのでぜひ`TMap`の代わりに使って頂ければと思います。

`TitleProperty`については以下の記事が参考になります。
[エディター上で配列の要素の横に文字列を表示する](https://takahashimichael.hatenablog.com/entry/2020/05/30/200037)