Property Bagを使ってみよう

# 最初に
多少の使用方法に注意すべき点はあるものの、色々と使い道がありそうな機能でしたので
この機会に紹介したいと思います。

# 環境
Windows 11
UE 5.5.4

# PropertyBagとは
InstancedStructなどが含まれている、StructUtilsプラグインの機能の一つです。
この機能は**パラメーターを好きな型かつ名前で追加、変更、管理することができます**
例：StateTreeのParameter

![mage.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276335/d98af08b-2ef5-449d-8f50-4ddde53b2cf8.gif)

2025/05/08時点で既にStructUtilsプラグインはエンジンに組み込まれており、
PropertyBagはDeformerやChooserなど様々なコンテンツで幅広く使われています

# 使い方
### 1. 宣言
FInstancedPropertyBagを型とした変数を宣言します

```cpp
UPROERTY()
FInstancedPropertyBag Bag;
```

ちなみに、エディタ側で値だけ編集を許可したい場合は
以下の様にmeta を設定することでUI上での編集を制限することが可能です。

```cpp
UPROERTY(meta = FixedLayout)
FInstancedPropertyBag Bag;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276335/40dbf68b-d648-47c0-8840-ef0593665389.png)

下がFixedLayout指定したものですが、
プロパティの変更が出来なくなっているのが分かります

### 2. パラメーターの追加
#### エディタの場合
上に貼られている、StateTreeの例を参照ください。
※ちなみに、ランタイム中の操作に関してはBP用の関数がありません
この理由に関しては後述する注意点を参照してください。

#### コードの場合
プロパティの名前と型を指定してAddPropertyするだけです、とても簡単です。

```cpp
static const FName PropertyName1(TEXT("Distance"));
static const FName PropertyName2(TEXT("Num"));

Bag.AddProperties({
	{ PropertyName1, EPropertyBagPropertyType::Float },
	{ PropertyName2, EPropertyBagPropertyType::Int32 }
});
```

### 3. パラメーターの中身の取得、設定
#### エディタの場合
PropertyBagはBPにおけるサポートはされていません
![image 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276335/5c4a59a9-7add-41e8-b2b1-5d6b65444551.png)

#### コードの場合

追加したパラメーターの名前と型に応じた関数を使用します。

```cpp
// 値の取得.
Bag.GetValueInt32(PropertyName2);

// 値の設定.
Bag.SetValueInt32(PropertyName2, 10);
```

# 注意点
PropertyBagを使う上で、注意しなければいけないポイントがあります。
それは**ランタイム中にパラメーターの追加、削除を行うのは厳禁**な点です
これに関してはPropertyBagの構成に理由があります。

以下簡単なFInstancedPropertyBagの構成
```mermaid
classDiagram
	class FInstancedPropertyBag{
		FInstancedStruct Value;
	}
	
	class UPropertyBag{
	}
	

  FInstancedPropertyBag --> UPropertyBag : FInstancedStructで使用する構造体の型として、UPropertyBagを生成
  UPropertyBag-->FPropertyBagPropertyDesc : TArrayでパラメーターの名前、中身、型のタイプを保持
```

### Instanced Structについて

簡単にいうとUEのリフレクション機能を利用して、

- 構造体の型(`UScriptStruct`)
- 中身のメモリ

を持つことで異なる構造体を同じ型(`FInstancedStruct`)で共通して扱うことができる構造体です。
※イメージし辛い方はヒストリアさんの使い方記事を参照頂くと分かりやすいかもしれません

https://historia.co.jp/archives/38415/

### UPropertyBagの生成とメモリ確保

`FInstancedPropertyBag`では、上記Instanced Structで使用する構造体の型(`UPropertyBag`)を
**AddPropertyされたプロパティの名前や型のタイプなどをもとにハッシュ値を生成して、**
**それぞれ別のものとしてUPropertyBagのインスタンスを生成します。**

```mermaid
classDiagram

	class FInstancedPropertyBag_1{
		float Distance;
	}
	class FInstancedPropertyBag_2{
		float Distance;
		int32 Num;
	}

	FInstancedPropertyBag_1<--> FInstancedPropertyBag_2: 内部的には別のUPropertyBagのインスタンスが生成される
```

つまり、
**ランタイム中にAddPropertyをすると、UPropertyBagをその場で生成してしまいます**
(詳細が知りたい方は`UPropertyBag::GetOrCreateFromDescs`関数をご参照ください)

**便利な動的型としてランタイム中に好きなもの詰めよ～！**
とかすると呼び出しの度にメモリ確保されます。
ループで一個ずつAddPropertyとか追加される度に別の型扱いなので
全部その場でメモリ確保されます、地獄です。

### UPropertyBagの使いまわし

ここで、プロパティが異なると、それぞれ別のUPropertyBagになるといいましたが
逆にプロパティの名前や型など中身が同じ構成ならば
**既に生成されているUPropertyBagのインスタンスが使いまわされるため、動的なメモリ確保は行われません。**
そのためアセット内に定義したInstancedBagの受け渡しなどは基本的に問題ありません。

# 最後に
ランタイム中の注意点だけ覚えておけば、アセットごとに動的な要素を持ちたい場合など、
色んな所で活用できる可能性がある機能思います。
もし、何か間違えている場合はご連絡いただけますと幸いです。