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