関連ブログ
- [UE4][UE5]開発環境の容量を少しでも減らす 2024.08.14UE
- [UE5] PushModel型のReplicationを使い、ネットワーク最適化を図る 2024.05.29UE
- [UE5]マテリアルでメッシュをスケールする方法 2024.01.17UE
CATEGORY
2020.02.19UE4UE/ C++
執筆バージョン: Unreal Engine 4.24 |
エンジニアの加藤です。
今回は、UE 4.24 にて新しく実装された Sparse Class Data について簡単に解説します。
※ こちらの機能はベータ版となっていますので、今後仕様が変更される恐れがあります。ご注意ください。
例えば、ステージ上に大量に出現するリンゴをひたすらゲットして、一定時間以内に獲得した得点を競うゲームを作るとします。
① リンゴの得点はゲーム中に変化しない
② どのリンゴも同じ得点である
③ リンゴの得点はエンジニアでない人が簡単に調整できるようにしたい
この要件を満たすためには、リンゴの Class に得点の値を保持するためのインスタンス変数を作成し、BlueprintReadOnly, EditDefaultsOnly な UProperty として設定すればよさそうです。
しかし、これではリンゴのインスタンスそれぞれが同一かつ不変な値を一つずつ保持することになるため、メモリの無駄だといえます。図にするとこんなイメージです。
このメモリの無駄をなくすには、リンゴの得点を保持する入れ物を 1 つだけ用意して、得点が必要となる場合はここから値を取得することにするとよさそうです。
『リンゴの得点を保持する入れ物』を Sparse Class Data 構造体として作成し、リンゴの Class と紐付けることで、2 番目の図のようなデータ構造を実現でき、メモリ使用量の削減が可能となります。
また、下記のような特徴があるため、Sparse Class Data を利用するための C++ コード変更を適切な手順で行えば、Blueprint で作業する人に影響が発生しないようにすることができます。
特徴 ① : Sparse Class Data 構造体に逃した変数も、Sparse Class Data 構造体を利用するクラスの Class Defaults から値の編集ができる
→ Sparse Class Data を利用するようになっても、値を編集する場所は変わらない
特徴 ② : C++ で宣言している Blueprint 公開変数を後から Sparse Class Data 構造体に逃がすように変更するための仕組みが整備されており、移行に伴う Blueprint 側の変更作業を必要としない
→ Sparse Class Data を利用する前に変数に設定していた値をワンタイムコピーによって引き継ぐようなコードを記述できるため、値を設定し直す必要がない
→ Sparse Class Data への移行後も通常の変数と同じ方法で値の Get ができるため、ピンの繋ぎ直しなどを行う必要がない
公式ドキュメント では、既に C++ で宣言して利用していた通常のインスタンス変数を Sparse Class Data 構造体に移行する方法が解説されていますが、Blueprint 側に影響が出ないように色々な約束事を守って記述されたコードとなっているため、若干面倒そうに見えます。
しかし、途中からの移行ではなく、最初から Sparse Class Data 構造体に新規で不変定数を作成するのであれば、下記のような簡単なコードで済みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "SparseApple.generated.h" // Sparse Class Data 構造体 (リンゴの得点を保持する入れ物) USTRUCT(BlueprintType) // BlueprintType が必要 struct FAppleSparseClassData { GENERATED_BODY() UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sparse Class Data") int32 Point; }; // Sparse Class Data を利用するクラス (リンゴ) UCLASS(SparseClassDataTypes = AppleSparseClassData) // SparseClassData として利用する構造体の名前を (F 抜きで) 指定 class ASparseApple : public AActor { GENERATED_BODY() }; |
ASparseApple を Parent とする Blueprint Class “BP_SparseApple” を作成すると、Class Defaults に Sparse Class Data 構造体のメンバである Point が表示され、値を設定できるようになっていることが確認できます。
また、値の Get も通常の変数と同様に行えます。よって、Blueprint で作業する人は Sparse Class Data を利用していることを意識する必要がありません。
メモリ使用量削減効果を確認するために、『ASparseApple の Sparse Class Data を利用しない版』である ARedundantApple と、これを Parent とする Blueprint Class “BP_RedundantApple” を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "RedundantApple.generated.h" UCLASS() class ARedundantApple : public AActor { GENERATED_BODY() public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Redundant Data") int32 Point; // 普通にインスタンス変数として保持する }; |
“BP_SparseApple” と “BP_RedundantApple” を 1000 個生成し、memreport でメモリ使用量を出力した結果 (NumKB) は以下のとおりです。
Class | NumKB |
BP_RedundantApple_C | 742.19 |
BP_SparseApple_C | 734.38 |
BP_SparseApple の方がメモリ使用量が少なくなっています。BP_SparseApple の実質的なメモリ使用量はこの値に Sparse Class Data 構造体 1 つ分のメモリ使用量を加えた結果であるといえますが、ほぼ誤差となります。
また、Point を int32 から FString に変更し、空文字にしたときとアルファベット 100 文字を設定したときの memreport の結果も見てみます (こちらも各 1000 個ずつ生成しています)。
Class | NumKB (空文字) | NumKB (100 文字) |
BP_RedundantApple_C | 790.80 | 988.06 |
BP_SparseApple_C | 769.31 | 769.31 |
BP_SparseApple の方は FString を Sparse Class Data に逃しているため、FString の長さは BP_SparseApple のメモリ使用量に影響を与えていません。一方、BP_RedundantApple の方は各インスタンスが FString を保持しているため、FString が長くなるほどメモリ使用量が増大するという結果となっています。
とっつきにくい機能だとは思いますが、インスタンスを大量に Spawn する Class では採用を検討してみるとよいかもしれません。