BLOGブログ

2020.02.19UE4UE/ C++

[UE4] UE 4.24 の新機能!Sparse Class Data について

執筆バージョン: Unreal Engine 4.24

エンジニアの加藤です。

今回は、UE 4.24 にて新しく実装された Sparse Class Data について簡単に解説します。

※ こちらの機能はベータ版となっていますので、今後仕様が変更される恐れがあります。ご注意ください。

Sparse Class Data の意義

例えば、ステージ上に大量に出現するリンゴをひたすらゲットして、一定時間以内に獲得した得点を競うゲームを作るとします。

リンゴを取ったときの得点について、以下のことが決まっています。

① リンゴの得点はゲーム中に変化しない

② どのリンゴも同じ得点である

③ リンゴの得点はエンジニアでない人が簡単に調整できるようにしたい

この要件を満たすためには、リンゴの Class に得点の値を保持するためのインスタンス変数を作成し、BlueprintReadOnly, EditDefaultsOnly な UProperty として設定すればよさそうです。

  • BlueprintReadOnly のため、Blueprint 上でゲーム中に値を変更してしまう恐れがない → ① を満たす
  • EditDefaultsOnly のため、インスタンス毎ではなくクラス毎の値設定となる → ② を満たす
  • EditDefaultsOnly のため、Blueprint の Class Defaults にて簡単に値の変更ができる → ③ を満たす

しかし、これではリンゴのインスタンスそれぞれが同一かつ不変な値を一つずつ保持することになるため、メモリの無駄だといえます。図にするとこんなイメージです。

このメモリの無駄をなくすには、リンゴの得点を保持する入れ物を 1 つだけ用意して、得点が必要となる場合はここから値を取得することにするとよさそうです。

このようなデータ構造を構築するために利用できるのが、Sparse Class Data システムです。

『リンゴの得点を保持する入れ物』を 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 ができるため、ピンの繋ぎ直しなどを行う必要がない

Sparse Class Data の最小サンプル

公式ドキュメント では、既に C++ で宣言して利用していた通常のインスタンス変数を Sparse Class Data 構造体に移行する方法が解説されていますが、Blueprint 側に影響が出ないように色々な約束事を守って記述されたコードとなっているため、若干面倒そうに見えます。

しかし、途中からの移行ではなく、最初から Sparse Class Data 構造体に新規で不変定数を作成するのであれば、下記のような簡単なコードで済みます。

ASparseApple を Parent とする Blueprint Class “BP_SparseApple” を作成すると、Class Defaults に Sparse Class Data 構造体のメンバである Point が表示され、値を設定できるようになっていることが確認できます。

また、値の Get も通常の変数と同様に行えます。よって、Blueprint で作業する人は Sparse Class Data を利用していることを意識する必要がありません。

Sparse Class Data の利用によるメモリ使用量削減効果について

メモリ使用量削減効果を確認するために、『ASparseApple の Sparse Class Data を利用しない版』である ARedundantApple と、これを Parent とする Blueprint Class “BP_RedundantApple” を作成します。

“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 では採用を検討してみるとよいかもしれません。