futurelabunseen / B-JeonganLee

UNSEEN 2nd Term Learning and Project Repo.
5 stars 0 forks source link

13강: 언리얼 오브젝트 관리 I - 직렬화 #55

Closed fkdl0048 closed 3 months ago

fkdl0048 commented 3 months ago

13강: 언리얼 오브젝트 관리 I - 직렬화

언리얼 엔진의 직렬화

직렬화(Serialization)란?

직렬화란, 오브젝트나 연결된 오브젝트의 묶음(오브젝트 그래프)을 바이트 스트림으로 변환하는 과정이다. (복잡한 데이터를 일렬로 세우기 때문에 직렬화)

거꾸로 복구하는 과정도 포함해서 이를 역직렬화(Deserialization)라고 한다.

이런 직렬화는 다음과 같은 장점을 지닌다.

이런 직렬화는 실제로 유니티에서 리플렉션과 함께 많이 사용되는 기술이다.

직렬화 구현시 고려할 점

이러한 직렬화를 직접 구현할 경우 다양한 상황을 고려해야 함

이러한 상황을 모두 감안해 직렬화 모델을 만드는 것은 매우 어렵다.

언리얼 엔진의 직렬화 시스템

언리얼 엔진은 이러한 상황을 모두 고려한 직렬화 시스템을 자체적으로 제공하고 있다.

Json 직렬화

Json은 JavaScript Object Notation의 약자로, 데이터를 표현하기 위한 경량의 데이터 교환 형식이다. (보통 웹 환경에서 서버와 클라이언트 사이에 사용)

언리얼은 Json, JosnUtilities 라이브러리 활용

Json 데이터 예씨

Json은 {}로 오브젝트 내 데이터를 키와 밸류 조합으로 표현한다. 배열은 []로 표현하고 이외에는 문자열, 숫자, 불리언, 널을 표현한다.

{
  "Name": "이정안",
  "Age": 42,
}

언리얼 스마트 포인터 라이브러리 개요

일반 C++오브젝트의 포인터 문제를 해결해주는 언리얼 엔진의 라이브러리이다.

실습

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"

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 = -1;
 FString Name = TEXT("홍길동");
};

/**
 * 
 */
UCLASS()
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
 GENERATED_BODY()

public:
 UMyGameInstance();

 virtual void Init() override;

private:

 TObjectPtr<class UStudent> StudentSrc;
};
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyGameInstance.h"

#include "JsonObjectConverter.h"
#include "Student.h"

void PrintStudentInfo(const UStudent* InStudent, const FString& InTag)
{
 UE_LOG(LogTemp, Log, TEXT("[%s] 이름 %s 순번 %d"), *InTag, *InStudent->GetName(), InStudent->GetOrder());
}

UMyGameInstance::UMyGameInstance()
{
}

void UMyGameInstance::Init()
{
 Super::Init();

 FStudentData RawDataSrc(16, TEXT("이정안"));

 const FString SaveDir = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));
 UE_LOG(LogTemp, Log, TEXT("저장할 파일 폴더: %s"), *SaveDir);

 {
  const FString RawDataFileName("RawData.bin");
  FString RawDataAbsolutePath = FPaths::Combine(*SaveDir, *RawDataFileName);
  UE_LOG(LogTemp, Log, TEXT("저장할 파일 전체 경로: %s"), *RawDataAbsolutePath);
  FPaths::MakeStandardFilename(RawDataAbsolutePath);
  UE_LOG(LogTemp, Log, TEXT("변경할 파일 전체 경로: %s"), *RawDataAbsolutePath);

  FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
  if (RawFileWriterAr)
  {
   *RawFileWriterAr << RawDataSrc;
   RawFileWriterAr->Close();
   delete RawFileWriterAr;
   RawFileWriterAr = nullptr;
  }

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

   UE_LOG(LogTemp, Log, TEXT("[RawData]: 이름 %s 순번 %d"), *RawDataDest.Name, RawDataDest.Order);
  }
 }

 // Unreal Obj Serilization

 StudentSrc = NewObject<UStudent>();
 StudentSrc->SetOrder(42);
 StudentSrc->SetName("이정안");

 {
  const FString ObjectDataFileName(TEXT("ObjectData.bin"));
  FString ObjectDataAbsolutePath = FPaths::Combine(*SaveDir, *ObjectDataFileName);
  FPaths::MakeStandardFilename(ObjectDataAbsolutePath);

  // 직렬화를 위한 버퍼 (언리얼 형식)
  TArray<uint8> BufferArray;
  FMemoryWriter MemoryWriterAr(BufferArray);
  // 간단하게 Serialize 함수를 호출하여 직렬화를 수행한다.
  StudentSrc->Serialize(MemoryWriterAr);

  // 스마트 포인터 라이브러리를 사용하여 위에서 한 nullptr이나 delete를 대체한다.
  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);
  PrintStudentInfo(StudentDest, TEXT("ObjectData"));
 }

 // Json
 {
  const FString JsonDataFileName(TEXT("StudentJsonData.txt"));
  FString JsonDataAbsolutePath = FPaths::Combine(*SaveDir, *JsonDataFileName);
  FPaths::MakeStandardFilename(JsonDataAbsolutePath);

  TSharedRef<FJsonObject> JsonObjectSrc = MakeShared<FJsonObject>();
  FJsonObjectConverter::UStructToJsonObject(StudentSrc->GetClass(), StudentSrc, JsonObjectSrc);

  FString JsonOutString;
  // null이 아님을 보장 (만들기 때문에, 있다면 접근)
  TSharedRef<TJsonWriter<TCHAR>> JsonWriterAr = TJsonWriterFactory<TCHAR>::Create(&JsonOutString);
  if (FJsonSerializer::Serialize(JsonObjectSrc, JsonWriterAr))
  {
   FFileHelper::SaveStringToFile(JsonOutString, *JsonDataAbsolutePath);
  }

  FString JsonInString;
  FFileHelper::LoadFileToString(JsonInString, *JsonDataAbsolutePath);

  TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonInString);

  TSharedPtr<FJsonObject> JsonObjectDest;
  // but 읽어들어야 하기에 null이 될 수 있음 따라서 TSharedPtr로 받아야 함
  if (FJsonSerializer::Deserialize(JsonReaderAr, JsonObjectDest))
  {
   UStudent* JsonStudentDest = NewObject<UStudent>();
   if (FJsonObjectConverter::JsonObjectToUStruct(JsonObjectDest.ToSharedRef(), JsonStudentDest->GetClass(), JsonStudentDest))
   {
    PrintStudentInfo(JsonStudentDest, TEXT("JsonData"));
   }
  }
 }
}
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Student.generated.h"

/**
 * 
 */
UCLASS()
class UNREALSERIALIZATION_API UStudent : public UObject
{
 GENERATED_BODY()

public:
 UStudent();

 int32 GetOrder() const {return Order;}
 void SetOrder(int32 InOrder) {Order = InOrder;}

 const FString& GetName() const {return Name;}
 void SetName(const FString& InName) {Name = InName;}

 // 이미 구현이 되어 있는 Serialize 함수를 재정의한다.
 virtual void Serialize(FArchive& Ar) override;

private:

 UPROPERTY()
 int32 Order;

 UPROPERTY()
 FString Name;
};
// Fill out your copyright notice in the Description page of Project Settings.

#include "Student.h"

UStudent::UStudent()
{
 Order = -1;
 Name = TEXT("홍길동");
}

void UStudent::Serialize(FArchive& Ar)
{
 Super::Serialize(Ar);

 Ar << Order;
 Ar << Name;
}

정리