희소 클래스 데이터(Sparse Class Data) 시스템은 자주 사용되는 액터 타입의 중복 데이터를 제거하여 메모리를 절약합니다. 게임을 개발할 때
인게임에서 동시에 여러 개의 인스턴스를 가지는 액터 클래스의 멤버인 프로퍼티. 이는 상당량의 메모리가 중복된 사본에 사용되고 있음을 의미합니다.
배치된 액터 인스턴스를 변경하지 않는 프로퍼티. 즉 액터의 인스턴스가 베이스 값을 오버라이드하거나 변경하지 않으므로 프로퍼티에
EditInstanceOnly
또는EditAnywhere
UProperty 지정자가 필요하지 않은 경우입니다.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
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 메타데이터 지정자를 사용하세요.