Sparse Class Data

Sparse Class Data システムを使用すると、冗長なプロパティによるメモリの無駄が取り除かれます。

コンテンツ

Sparse Class Data システムを使用すると、よく使用される アクタ 型の冗長なデータを取り除くことで、メモリが節約されます。ゲーム開発中に、

[ブループリントに公開されたプロパティ](programming-and-scripting/programming-language-implementation/cpp-in-unreal-engine/Properties)
を使用すると、デザイナーが非常に容易にアクタの動作をイテレーションできるようになります。しかし、ゲームの出荷時に、そのようなプロパティの値がアクタ インスタンスによって変化しない、またはゲームプレイ中に変化しない場合、そのプロパティの多くは実質的に定数であるといえます。Sparse Class Data を使用すると、そのようなプロパティを 1 つの共有構造体インスタンスに転送し、メモリ内に保持するプロパティのコピー数を 1 つだけに抑えながらも、引き続きデザイナーがブループリント グラフ内の値にアクセスして [Class Defaults (クラスのデフォルト)] で編集可能にできます。あるプロパティが Sparse Class Data の対象にふさわしいかどうかを判断するには、次の 3 要件基準を使用します。以下に当てはまる場合、そのプロパティは対象として妥当です。

  1. ゲーム内に同時に多数のインスタンスを持つ (冗長なコピーによって大量のメモリが消費される) アクタ クラスのメンバーである。

  2. 配置済みのアクタ インスタンスで変化しない。つまり、基本値をオーバーライド (変更) するアクタ インスタンスが存在しないため、プロパティに EditInstanceOnly または EditAnywhere UProperty 指定子が必要ない。

  3. C++ コードで変更されない。変数に直接アクセスする C++ コードは、アクセサ関数への呼び出しで置き換える必要がある。

Sparse Class Data 機能を実装するには、ネイティブ (C++) コードが必要です。ブループリントで宣言された変数は、このプロセスの対象とするために、C++ コードに移行する必要があります。

実装例

クラスの 1 つに Sparse Class Data を使用すると決めたら、候補となるプロパティを特定する必要があります。EditAnywhereEditInstanceOnly、または BlueprintReadWrite でタグ付けされたプロパティは、Sparse Class Data の候補にはなりません。同様に、ネイティブ C++ コードで変更されるプロパティも、Sparse Class Data システムで処理する対象とはなりません。というのも、そのようなプロパティはアクタ インスタンスごとに異なる値を持つ可能性があります。レベル エディタでインスタンスごとに編集されたり、ブループリント スクリプティングや、ネイティブ コードによってゲーム セッション中に 1 つのアクタ インスタンスの値が変更されたりするからです。次のサンプル クラスにはいくつかのプロパティが含まれており、一部は Sparse Class Data の候補です。

// このクラスのプロパティはすべてエディタで変更できるが、クラスごとの変更であり、インスタンスごとの変更はできない。
UCLASS(BlueprintType)
class AMyActor : public AActor
{
    GENERATED_UCLASS_BODY()

public:
    // このプロパティはエディタで変更できるが、クラスごとの変更しかできない。
    // ブループリント グラフではアクセスや変更ができない。
    // Sparse Class Data の有力な候補である。
    UPROPERTY(EditDefaultsOnly)
    float MyFloatProperty;

    // このプロパティはエディタで変更できない。
    // ブループリント グラフでアクセスできるが、変更はできない。
    // Sparse Class Data の有力な候補である。
    UPROPERTY(BlueprintReadOnly)
    int32 MyIntProperty;

    // このプロパティはエディタでクラスごとに編集できる。
    // ブループリント グラフでアクセスできるが、変更はできない。
    // Sparse Class Data の有力な候補である。
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    FName MyNameProperty;

    // このプロパティは配置済みの MyActor インスタンスで編集できる。
    // Sparse Class Data の有力な候補ではない。
    UPROPERTY(EditAnywhere)
    FVector MyVectorProperty;

    // このプロパティはブループリント グラフで変更できる。
    // Sparse Class Data の有力な候補ではない。
    UPROPERTY(BlueprintReadWrite)
    FVector2D MyVector2DProperty;
};

候補となる変数を特定したら、それらの変数を含めた構造体を作成し、BlueprintType

[UStruct 指定子](programming-and-scripting/programming-language-implementation/cpp-in-unreal-engine/Structs/Specifiers)
でマークします。構造体の各プロパティは EditDefaultsOnly 指定子を含んでいる必要があります。

USTRUCT(BlueprintType)
struct FMySparseClassData
{
    GENERATED_BODY()

    FMySparseClassData() 
    : MyFloatProperty(0.f)
    , MyIntProperty(0)
    , MyNameProperty(NAME_None)
    { }

    // エディタでこのプロパティのデフォルト値を設定できる。
    // ブループリント グラフではアクセスできない。
    UPROPERTY(EditDefaultsOnly)
    float MyFloatProperty;

    // このプロパティの値は C++ コードで設定される。
    // ブループリント グラフでアクセスできる (が、変更できない)。
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    int32 MyIntProperty;

    // エディタでこのプロパティのデフォルト値を設定できる。
    // ブループリント グラフでアクセスできる (が、変更できない)。
    //「GetByRef」は、ブループリント グラフがコピーではなく定数の参照にアクセスすることを意味する。
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(GetByRef))
    FName MyNameProperty;
};

FMySparseClassData

この構造体を使用するには、元のクラスに何らかの変更が必要です。プロセスに必要な変更は、具体的には次の 3 つです。

  • この構造体を Sparse Class Data 型として使用するようにクラスに指示する。

  • 新しい構造体に移動するプロパティに #if WITH_EDITORONLY_DATA プリコンパイラ ディレクティブ ブロックを追加し、元のクラスでは _DEPRECATED サフィックスでマークする。さらに、UProperty 指定子をすべて削除し、プロパティを private アクセスに設定します。_DEPRECATED の名前を使用するうえでコードの他の部分は変更しないようにします。これらの行は、Sparse Class Data 構造体へのアクセサ呼び出しで置き換えられます。

  • エディタ ビルドで (#if WITH_EDITOR)、MoveDataToSparseClassDataStruct 関数をオーバーライドします。この関数は、元のクラスから Sparse Class Data 構造体へのワンタイム コピーを実行することで既存のデータ値を保持します。

これらの変更を加えると、クラスは次のようになります。

UCLASS(BlueprintType, SparseClassDataTypes = MySparseClassData)
class AMyActor : public AActor
{
    GENERATED_BODY()

#if WITH_EDITOR
public:
    // ~ この関数は既存のデータを FMySparseClassData に移動する。
    virtual void MoveDataToSparseClassDataStruct() const override;
#endif // WITH_EDITOR

#if WITH_EDITORONLY_DATA
    //~ これらのプロパティは FMySparseClassData 構造体に移動する。
private:
    // このプロパティはエディタで変更できるが、クラスごとの変更しかできない。
    // ブループリント グラフではアクセスや変更ができない。
    // Sparse Class Data の有力な候補である。
    UPROPERTY()
    float MyFloatProperty_DEPRECATED;

    // このプロパティはエディタで変更できない。
    // ブループリント グラフでアクセスできるが、変更はできない。
    // Sparse Class Data の有力な候補である。
    UPROPERTY()
    int32 MyIntProperty_DEPRECATED;

    // このプロパティはエディタでクラスごとに編集できる。
    // ブループリント グラフでアクセスできるが、変更はできない。
    // Sparse Class Data の有力な候補である。
    UPROPERTY()
    FName MyNameProperty_DEPRECATED;
#endif // WITH_EDITORONLY_DATA

    //~ 残りのプロパティはインスタンスごとに変更できる。
    //~ したがって、Sparse Class Data 実装には含まない。
public:
    // このプロパティは配置済みの MyActor インスタンスで編集できる。
    // Sparse Class Data の有力な候補ではない。
    UPROPERTY(EditAnywhere)
    FVector MyVectorProperty;

    // このプロパティはブループリント グラフで変更できる。
    // Sparse Class Data の有力な候補ではない。
    UPROPERTY(BlueprintReadWrite)
    FVector2D MyVector2DProperty;
};

次の関数は、共有プロパティすべての既存の値をコピーします。

#if WITH_EDITOR
void AMyActor::MoveDataToSparseClassDataStruct() const
{
    // スパース データが保存済みの場合は上書きしないようにする。
    UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass());
    if (BPClass == nullptr || BPClass->bIsSparseClassDataSerializable == true)
    {
        return;
    }

    Super::MoveDataToSparseClassDataStruct();
    #if WITH_EDITORONLY_DATA
    // Unreal Header Tool (UHT) によって GetMySparseClassData が自動的に作成される。
    FMySparseClassData* SparseClassData = GetMySparseClassData();

    // すべての Sparse Class Data プロパティを含めるにはこれらの行を変更する。
    SparseClassData->MyFloatProperty = MyFloatProperty_DEPRECATED;
    SparseClassData->MyIntProperty = MyIntProperty_DEPRECATED;
    SparseClassData->MyNameProperty = MyNameProperty_DEPRECATED;
    #endif // WITH_EDITORONLY_DATA
}
#endif // WITH_EDITOR

Engine/BlueprintGeneratedClass.h

この時点で、Sparse Class Data が実装できました。影響を受けるプロパティをユーザーがエディタで編集、アクセスしても、気がつくような動作の違いはありませんが、出荷ビルドのメモリ使用量は削減されます。プロパティを C++ コードで参照する場合は、その変数にアクセスする試行を getter 関数の呼び出しに置き換えます。例えば、MyFloatProperty 変数へのアクセスに使用していたコードでは、代わりに GetMyFloatProperty を呼び出すようにします。この関数は、UHT によって自動的に生成されます。重要な getter 関数があり、その動作を維持する必要がある場合は、UHT によって getter 関数が生成されないよう NoGetter UProperty メタデータ指定子で指示します。値ではなく定数の参照によってアクセスする必要のある変数には、GetByRef UProperty メタデータ指定子を使用します。

Unreal Engine のドキュメントを改善するために協力をお願いします!どのような改善を望んでいるかご意見をお聞かせください。
調査に参加する
キャンセル