희소 클래스 데이터

희소 클래스 데이터 시스템은 중복된 프로퍼티에서 낭비되는 메모리를 제거합니다.

목차

희소 클래스 데이터(Sparse Class Data) 시스템은 자주 사용되는 액터 타입의 중복 데이터를 제거하여 메모리를 절약합니다. 게임을 개발할 때

[블루프린트에 노출된 프로퍼티](programming-and-scripting/programming-language-implementation/cpp-in-unreal-engine/Properties)
는 디자이너가 액터 비헤이비어에서 반복작업을 수행할 수 있는 좋은 방법을 제공합니다. 그러나 게임을 출시할 때는 이러한 프로퍼티의 값이 액터 인스턴스 전반에 걸쳐 바뀌거나 게임플레이 도중에 변경되지 않을 경우 프로퍼티 대다수가 실질적으로 상수가 될 수 있습니다. 희소 클래스 데이터를 사용하면 이러한 프로퍼티를 단일 공유 구조체 인스턴스로 전송하여 메모리에 프로퍼티의 사본 단 하나만을 유지하는 동시에, 디자이너가 블루프린트 그래프에서 프로퍼티 값에 액세스하고 클래스 디폴트에서 편집하도록 할 수 있습니다. 프로퍼티에 희소 클래스 데이터를 사용해도 좋은지 결정하려면 다음의 세 갈래 테스트를 활용해 보세요. 다음과 같은 프로퍼티에는 희소 클래스를 사용해도 좋습니다.

  1. 인게임에서 동시에 여러 개의 인스턴스를 가지는 액터 클래스의 멤버인 프로퍼티. 이는 상당량의 메모리가 중복된 사본에 사용되고 있음을 의미합니다.

  2. 배치된 액터 인스턴스를 변경하지 않는 프로퍼티. 즉 액터의 인스턴스가 베이스 값을 오버라이드하거나 변경하지 않으므로 프로퍼티에 EditInstanceOnly 또는 EditAnywhere UProperty 지정자가 필요하지 않은 경우입니다.

  3. C++ 코드로 수정되지 않는 프로퍼티. 변수에 직접 액세스하는 모든 C++ 코드는 접근자 함수에 대한 호출로 대체되어야 합니다.

희소 클래스 데이터 기능을 구현하려면 네이티브(C++) 코드가 필요합니다. 이 프로세스에 사용할 수 있으려면 모든 블루프린트 선언 변수는 C++ 코드로 이동해야 합니다.

예제 구현

클래스 중 하나에 희소 클래스 데이터를 사용하려고 결정했다면, 사용 가능한 프로퍼티를 파악해야 합니다. EditAnywhere , EditInstanceOnly , 또는 BlueprintReadWrite 가 태그된 프로퍼티는 희소 클래스 데이터에 적합한 후보가 아닙니다. 이와 유사하게, 네이티브 C++ 코드에서 변경되는 프로퍼티도 희소 클래스 데이터 시스템과 함께 사용할 수 없습니다. 이는 프로퍼티가 레벨 에디터에서의 인스턴스별 편집이나, 게임 세션 동안 단일 액터 인스턴스에서 그 값을 변경하는 블루프린트 스크립팅 또는 네이티브 코드에 의해 액터 인스턴스마다 다른 값을 가질 수 있기 때문입니다. 다음 예시 클래스에는 희소 클래스 데이터에 적합한 몇 가지 프로퍼티가 포함되어 있습니다.

// 이 클래스의 프로퍼티는 에디터에서 모두 변경할 수 있지만, 인스턴스별이 아니라 클래스별로 변경됩니다.
UCLASS(BlueprintType)
class AMyActor : public AActor
{
    GENERATED_UCLASS_BODY()

public:
    // 이 프로퍼티는 에디터에서 변경할 수 있지만, 클래스별로만 변경됩니다.
    // 블루프린트 그래프에서는 액세스하거나 변경할 수 없습니다.
    // 희소 클래스 데이터의 잠재적 후보입니다.
    UPROPERTY(EditDefaultsOnly)
    float MyFloatProperty;

    // 이 프로퍼티는 에디터에서 변경할 수 없습니다.
    // 블루프린트 그래프에서 액세스할 수 있지만, 변경할 수는 없습니다.
    // 희소 클래스 데이터의 잠재적 후보입니다.
    UPROPERTY(BlueprintReadOnly)
    int32 MyIntProperty;

    // 이 프로퍼티는 에디터에서 클래스별로 편집할 수 있습니다.
    // 블루프린트 그래프에서 액세스할 수 있지만, 변경할 수는 없습니다.
    // 희소 클래스 데이터의 잠재적 후보입니다.
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    FName MyNameProperty;

    // 이 프로퍼티는 배치된 MyActor 인스턴스에서 편집할 수 있습니다.
    // 희소 클래스 데이터의 잠재적 후보가 아닙니다.
    UPROPERTY(EditAnywhere)
    FVector MyVectorProperty;

    // 이 프로퍼티는 블루프린트 그래프에서 변경할 수 있습니다.
    // 희소 클래스 데이터의 잠재적 후보가 아닙니다.
    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'는 블루프린트 그래프가 사본 대신 const 참조에 액세스함을 의미합니다.
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(GetByRef))
    FName MyNameProperty;
};

FMySparseClassData

원본 클래스의 경우 이 구조체를 사용하려면 약간의 수정이 필요합니다. 프로세스에는 다음과 같은 특수한 변경 사항 3가지가 필요합니다.

  • 클래스가 이 구조체를 희소 클래스 데이터 타입으로 사용하도록 합니다.

  • 새로운 구조체로 이동하는 프로퍼티 주변에 #if WITH_EDITORONLY_DATA 사전 컴파일러 지시어 블록을 추가하고, 원본 클래스에서 _DEPRECATED 접미사로 표시합니다. 추가적으로, 모든 UProperty 지정자를 제거하고 프로퍼티를 private 액세스로 세팅합니다. 코드의 다른 부분은 _DEPRECATED 이름을 사용하도록 변경하지 마세요. 이러한 줄은 희소 클래스 데이터 구조체에 대한 접근자 호출로 대체됩니다.

  • 에디터 빌드에서 (#if WITH_EDITOR )MoveDataToSparseClassDataStruct 함수를 오버라이드합니다. 이 함수는 원본 클래스에서 희소 클래스 데이터 구조체로 일회성 복사를 수행하여 기존 데이터 값을 보존합니다.

이렇게 변경한 후의 클래스는 다음과 같습니다.

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:
    // 이 프로퍼티는 에디터에서 변경할 수 있지만, 클래스별로만 변경됩니다.
    // 블루프린트 그래프에서는 액세스하거나 변경할 수 없습니다.
    // 희소 클래스 데이터의 잠재적 후보입니다.
    UPROPERTY()
    float MyFloatProperty_DEPRECATED;

    // 이 프로퍼티는 에디터에서 변경할 수 없습니다.
    // 블루프린트 그래프에서 액세스할 수 있지만, 변경할 수는 없습니다.
    // 희소 클래스 데이터의 잠재적 후보입니다.
    UPROPERTY()
    int32 MyIntProperty_DEPRECATED;

    // 이 프로퍼티는 에디터에서 클래스별로 편집할 수 있습니다.
    // 블루프린트 그래프에서 액세스할 수 있지만, 변경할 수는 없습니다.
    // 희소 클래스 데이터의 잠재적 후보입니다.
    UPROPERTY()
    FName MyNameProperty_DEPRECATED;
#endif // WITH_EDITORONLY_DATA

    //~ 나머지 프로퍼티는 인스턴스별로 변경할 수 있습니다.
    //~ 따라서 나머지 프로퍼티는 희소 클래스 데이터 구현에서 제외됩니다.
public:
    // 이 프로퍼티는 배치된 MyActor 인스턴스에서 편집할 수 있습니다.
    // 희소 클래스 데이터의 잠재적 후보가 아닙니다.
    UPROPERTY(EditAnywhere)
    FVector MyVectorProperty;

    // 이 프로퍼티는 블루프린트 그래프에서 변경할 수 있습니다.
    // 희소 클래스 데이터의 잠재적 후보가 아닙니다.
    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();

    // 이 줄을 수정하여 모든 희소 클래스 데이터 프로퍼티를 포함합니다.
    SparseClassData->MyFloatProperty = MyFloatProperty_DEPRECATED;
    SparseClassData->MyIntProperty = MyIntProperty_DEPRECATED;
    SparseClassData->MyNameProperty = MyNameProperty_DEPRECATED;
    #endif // WITH_EDITORONLY_DATA
}
#endif // WITH_EDITOR

Engine/BlueprintGeneratedClass.h

이렇게 하면 희소 클래스 데이터가 작동합니다. 영향을 받는 프로퍼티를 에디터에서 편집하거나 액세스하는 사용자는 비헤이비어의 차이점을 느끼지 못하며, 출시 빌드의 메모리 사용량은 줄어듭니다. 프로퍼티가 C++ 코드에서 참조될 경우, 게터 함수에 대한 호출로 변수에 액세스하려는 시도는 모두 대체해야 합니다. 예를 들어, 코드에서 MyFloatProperty 변수에 액세스하기 위해 사용되는 모든 부분은 GetMyFloatProperty 를 대신 호출해야 합니다. UHT는 사용자를 위해 이러한 함수를 자동 생성합니다. 사소하지 않은 게터 함수가 있고 그 비헤이비어를 유지해야 하는 경우, NoGetter UProperty 메타데이터 지정자로 UHT가 자체 게터 함수를 생성하지 않도록 해야 합니다. 값 대신 상수 참조로 액세스하고 싶은 변수의 경우 GetByRef UProperty 메타데이터 지정자를 사용하세요.

언리얼 엔진 문서의 미래를 함께 만들어주세요! 더 나은 서비스를 제공할 수 있도록 문서 사용에 대한 피드백을 주세요.
설문조사에 참여해 주세요
취소