개발 이야기 안하는 개발자

언리얼5_1_4 : 언리얼 프로젝트의 에셋 본문

Unreal/이득우의 언리얼 프로그래밍

언리얼5_1_4 : 언리얼 프로젝트의 에셋

07e 2024. 3. 5. 20:46
반응형

직렬화

오브젝트나 연결된 오브젝트의 묶음을 바이트 스트림으로 변환하는 과정 

복잡한 데이터를 일렬로 세우기 때문.

데이터 압축 , 암호화를 통해 데이터를 효율적이고 안전하게 보관할 수 있다.

 

파일경로 알아오기 (Saved 파일까지)

const FString SaveDir = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));

 

FArchive라고 하는 파일 경로 끝에 데이터를 담을 수 있다.

struct FStudentData
{
	FStudentData() {}
	FStudentData(int32 InOrder, const FString& InName) : Order(InOrder), Name(InName) {}

	friend FArchive& operator<<(FArchive& Ar, FStudentData& InStudentData)
	{
		Ar << InStudentData.Order;
		Ar << InStudentData.Name;
		return Ar;
	}

	int32 Order;
	FString Name;
};

...

const FString RawDataFileName(TEXT("RawData.bin"));
FString RawDataAbsolutePath = FPaths::Combine(*SaveDir, *RawDataFileName); //전체 파일 경로
FPaths::MakeStandardFilename(RawDataAbsolutePath); //변경할 파일 폴더

FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
if (nullptr != RawFileWriterAr)
{
    *RawFileWriterAr << RawDataSrc;	//직렬화 담기
    RawFileWriterAr->Close();
    delete RawFileWriterAr;
    RawFileWriterAr = nullptr;
}

FStudentData RawDataDest;
FArchive* RawFileReaderAR = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);
if (nullptr != RawFileReaderAR)
{
    *RawFileReaderAR << RawDataDest;
    RawFileReaderAR->Close();
    delete RawFileReaderAR;
    RawFileReaderAR = nullptr;
}

 

 

언리얼에서 오브젝트 직렬화

언리얼 오브젝트는 Serialize 가 이미 구현이 되어있기 때문에 오버라이드 해서 사용하면 된다.

virtual void Serialize(FArchive& Ar) override;

 

언리얼 스마트 포인터(TUniquePtr<"targetClass"> )를 사용해서 쓰고 읽기

//경로 설정
const FString ObjectDataFileName(TEXT("ObjectData.bin"));
FString ObjectDataAbsolutePath = FPaths::Combine(*SaveDir, *ObjectDataFileName);
FPaths::MakeStandardFilename(ObjectDataAbsolutePath);

//담을 데이터 직렬화
TArray<uint8> BufferArray;
FMemoryWriter MemoryWriterAr(BufferArray);
StudentSrc->Serialize(MemoryWriterAr);

//쓰기
if (TUniquePtr<FArchive> FileWriterAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*ObjectDataAbsolutePath)))
{
    *FileWriterAr << BufferArray;
    FileWriterAr->Close();
}

//읽기
TArray<uint8> BufferArrayFromFile;
if (TUniquePtr<FArchive> FileReaderAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath)))
{
    *FileReaderAr << BufferArrayFromFile;
    FileReaderAr->Close();
}

FMemoryReader MemoryReaderAr(BufferArrayFromFile);
UStudent* StudentDest = NewObject<UStudent>();
StudentDest->Serialize(MemoryReaderAr);

 

 

Json 직렬화

언리얼에서도 지원하는 Json 직렬화가 있다.

주의할 점은 프로젝트 Build.cs에서 플러그인을 추가해주어야 한다. Json , JsonUtilites

//쓸 파일의 경로
FString JsonDataFileName(TEXT("StudentJsonData.txt"));
FString JsonDataAbsolutePath = FPaths::Combine(*SaveDir, *JsonDataFileName);
FPaths::MakeStandardFilename(JsonDataAbsolutePath);

//Json을 사용하기 위한 객체 생성
TSharedRef<FJsonObject> JsonObjectSrc = MakeShared<FJsonObject>();
FJsonObjectConverter::UStructToJsonObject(StudentSrc->GetClass(), StudentSrc, JsonObjectSrc);

//직결화 진행 및 파일에 쓰기
FString JsonOutString;
TSharedRef<TJsonWriter<TCHAR>> JsonWritedAr = TJsonWriterFactory<TCHAR>::Create(&JsonOutString);
if (FJsonSerializer::Serialize(JsonObjectSrc, JsonWritedAr))
{
    FFileHelper::SaveStringToFile(JsonOutString, *JsonDataAbsolutePath);
}

//파일에서 데이터 읽어오기
FString JsonInString;
FFileHelper::LoadFileToString(JsonInString, *JsonDataAbsolutePath);

//Json을 사용하기 위한 객체 생성
TSharedPtr<FJsonObject> JsonObjectDest;
TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonInString);

//역직렬화 진행 및 데이터 가져오기
if (FJsonSerializer::Deserialize(JsonReaderAr, JsonObjectDest))
{
    UStudent* JsonStudentDest = NewObject<UStudent>();
    if(FJsonObjectConverter::JsonObjectToUStruct(JsonObjectDest.ToSharedRef(), JsonStudentDest->GetClass(), JsonStudentDest))
    {
    	...
    }
}

 

 

 

 

패키지

언리얼 오브젝트를 감싼 포장 오브젝트.

DLC와 같은 확장 컨텐츠에 사용되는 별도의 데이터 묶음. 

 

언리얼 오브젝트 패키지는 보통 하나의 에셋을 갖는다. (다중이여도 상관 없음)

이 하나의 에셋에는 다양한 서브오브젝트들을 갖는다.

//PackageName이라고 하는 패키지 생성
StudentPackage = CreatePackage(*PackageName);
EObjectFlags ObjectFlag = RF_Public | RF_Standalone;

//에셋을 제작할 거고, 이 에셋은 패키지하위에 들어감)
UStudent* TopStudent = NewObject<UStudent>(StudentPackage, UStudent::StaticClass(), *AssetName, ObjectFlag);
TopStudent->SetName(TEXT("MyPackage"));
TopStudent->SetOrder(36);

//총 10개의 다른 서브오브젝트를 제작, 각각 에셋하위에 들어감
const int32 NumofSubs = 10;
for (int32 ix = 1; ix <= NumofSubs; ix++)
{
    FString SubObjectName = FString::Printf(TEXT("Student%d"), ix);
    UStudent* SubStudent = NewObject<UStudent>(TopStudent, UStudent::StaticClass(), *SubObjectName, ObjectFlag);
    SubStudent->SetName(FString::Printf(TEXT("Student%d"), ix));
    SubStudent->SetOrder(ix);
}

//패키지를 경로에 저장
const FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
FSavePackageArgs SaveArgs;
SaveArgs.TopLevelFlags = ObjectFlag;

if (UPackage::SavePackage(StudentPackage, nullptr, *PackageFileName, SaveArgs))
{
	...
}

 

 

적혀있는 Student가 Package의 이름.

TopStudent가 에셋이다.

 

 

패키지 로드

//패키지 찾기
UPackage* StudentPackage = ::LoadPackage(nullptr, *PackageName, LOAD_None);
if (nullptr == StudentPackage)
{
	//못찾은 경우
    return;
}

//찾은 패키지 로드
StudentPackage->FullyLoad();

//패키지내에 있는 에셋 가져오기
UStudent* TopStudent = FindObject<UStudent>(StudentPackage, *AssetName);

 

 

 

에셋 로드

게임 제작 단계에선 에셋 간의 연결 작업을 위해 직접 패키지를 불러 할당하는 작업은 부하가 크다.

그렇기 때문에 꼭 필요한 에셋인 경우에 생성자 코드에서 미리 로딩하는 것이 좋다.

단, 런타임에서 로딩해야 하는 경우라면 관리자를 사용해서 비동기로 로딩하는것이 좋다.

//생성자
const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
static ConstructorHelpers::FObjectFinder<UStudent> UASSET_TopStudent(*TopSoftObjectPath);
if (UASSET_TopStudent.Succeeded())
{
	//성공 로드. UASSET_TopStudent.Object로 접근 가능
}


...



//보통 메소드
const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
UStudent* TopStudent = LoadObject<UStudent>(nullptr, *TopSoftObjectPath);

 

 

관리자를 활용한 비동기 로드

FStreamableManager를 활용한다.

FStreamableManager StreamableManager;
TSharedPtr<FStreamableHandle> Handle;

...

const FString TopSoftObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
Handle = StreamableManager.RequestAsyncLoad(TopSoftObjectPath,
    [&]()
    {
        if (Handle.IsValid() && Handle->HasLoadCompleted())
        {
            UStudent* TopStudent = Cast<UStudent>(Handle->GetLoadedAsset());
            if (TopStudent)
            {
            	//성공적 로드
                Handle->ReleaseHandle();
                Handle = nullptr;
            }
        }
    }
);

 

 

 

 

 

 

 

 

 

 

 

반응형