일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- topdownmove
- Module
- 메테리얼
- Toon Shading
- build.cs
- Console
- 네트워크 기초
- 언리얼엔진5 #언리얼 클라이언트 프로그래밍
- 폭설 #미친 날씨
- 브론즈
- 툰쉐이딩
- 원카페#무인카페#카페추천#카페맛집
- STL
- UE_5
- Unreal
- CS
- CS50
- 헤더 경로
- 백준
- 언리얼
- Harvard
- 오늘밤 세계에서 이 사랑이 사라진다 해도 #독후감 #오열
- C++
- c++ 베이직
- leetcode
- Gas
- 언리얼 엔진5 #언리얼 클라이언트 프로그래밍
- A* Algorithm
- 코드리뷰
- 순환 리스트
- Today
- Total
WN_인생기록
[UE_5] 직렬화 본문
직렬화 : 오브젝트나 연결된 오브젝트의 묶음을 바이트 스트림으로 변환하는 과정
- 복잡한 데이터를 일렬로 세우기 때문에 직렬화 라고 함
시리얼라이제이션(직렬화) : 오브젝트 그래프에서 바이트 스트림으로
디시리얼라이제이션 : 바이트 스트림에서 오브젝트 그래프로 복구
왜 직렬화가 중요한가
프로그램의 상태를 저장하고 필요할 때 복원할 수 있다 (세이브)
객체 정보를 클립보드에 복사해서 다른 프로그램에 전송가능
네트워크를 통해 다른 컴퓨터에 복원 ( 멀티플레이)
데이터 압축, 암호화를 통해서 데이터를 효율적으로 보관 가능
구현할때 고려할 점
1. 데이터 레이아웃 : 오브젝트가 소유한 데이터를 변환할 것인가? (어떻게 직렬화 될 것인가?)
2. 이식성 : 다른 시스템에 전송해도 그대로 사용 가능한가?
3. 버전관리 : 새로운 기능이 추가될때 어떻게 확장할 것인가?
4. 성능 : 비용을 줄이기 위해 어떤 데이터 형식을 사용할 것인가?
5. 보안 : 데이터를 어떻게 안전하게 보호할 것인가?
6. 에러 처리 : 전송 과정에서 문제가 발생할 경우 어떻게 인식하고 처리할 것인가?
언리얼에서는 자체적으로 시스템 제공
클래스는 FArchive
메모리 아카이브 (FMemoryReader,FMemoryWriter)가 있음
먼저, 프로젝트의 디렉토리 내의 폴더 경로를 알아야 한다.
const FString SaveDir = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));
const FString RawDataFileName(TEXT("RowData.bin"));
FString RawDataAbsolutePath = FPaths::Combine(*SaveDir, *RawDataFileName);
FPaths::MakeStandardFilename(RawDataAbsolutePath);
FPaths를 통해 Saved에 대한 경로를 얻을 수 있고,
이를 파일 이름과, 경로를 합쳐서 최종 경로를 만들어내야 한다.
마지막에는 MakeStandardFilename() 기능을 통해서 표준경로로 바꿔줘야 한다. 이렇게 하면 프로젝트 내의 폴더 경로를 코드로 저장할 수 있다.
// 파일 쓰기를 위한 파일 스트림 생성
FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
if (RawFileWriterAr)
{
// 데이터 객체를 파일에 직렬화
*RawFileWriterAr << RawDataSrc;
// 파일 스트림 닫기
RawFileWriterAr->Close();
// 메모리 해제
delete RawFileWriterAr;
RawFileWriterAr = nullptr;
}
// 파일 읽기를 위한 파일 스트림 생성
FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);
if (RawFileReaderAr)
{
// 파일로부터 객체 데이터 역직렬화
*RawFileReaderAr << RawDataDest;
// 파일 스트림 닫기
RawFileReaderAr->Close();
// 메모리 해제
delete RawFileReaderAr;
RawFileReaderAr = nullptr;
}
IFileManager의 ::Get().CreateFileWriter((경로))를 통해서 파일 스트림을 생성하고,
해당 파일에 데이터를 직렬화 시킨다. << 라는 기능을 통해서 직렬화 시킬 수 있다.
스트림을 닫고, 메모리를 해제하고, 확실하게 nullptr 까지 만들어줘야 메모리 누수가 없다.
IFileManager의 ::Get().CreateFileReader((경로))를 통해서 파일 스트림을 생성하고,
해당 파일에 데이터를 역직렬화 시킨다.
마찬가지로 스트림을 닫고, 메모리를 해제하고, 확실하게 nullptr 까지 만들어줘야 메모리 누수가 없다.
이렇게 하는것이 가장 기본적이나, 언리얼에서는 객체를 직렬화 할때, 좀 더 준비된 시스템이 있다.
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);
TArray<uint8> 을 통해서, 데이터를 직렬화 시키는데, 이를 FMemoryWriter 이라는 클래스에서 위의 과정을 줄여준다.
StudentSrc 라는 클래스에서 직렬화에 대한 오버라이딩으로 변수의 순번만 정해주면 된다.
virtual void Serialize(FArchive& Ar) override
{
Super::Serialize(Ar);
Ar << (변수1);
Ar << (변수2);
}
FArchive 데이터형에 FMemoryWriter 를 지정해주면 자동으로 객체에 대한 정보를 저장한다.
이후에 TUniquePtr을 통해 delete와 nullptr을 선언하는 로직을 줄여줄 수 있다.
이후에 FMemoryReader를 통해서 역직렬화를 할 수 있다.
마지막으로, Json이라는 파일명으로도 직렬화가 가능한데 이를 위해서는 Module에
"Json","JsonUtilities"
을 추가해줘야 하며,
#include "JsonObjectConverter.h"
를 cpp에 추가해줘야 한다.
// JSON 파일 이름 설정 및 경로 조합
const FString JsonDataFileName(TEXT("StudentJsonData.txt"));
FString JsonDataAbsolutePath = FPaths::Combine(*SaveDir, *JsonDataFileName);
// 표준 경로 형식으로 변환
FPaths::MakeStandardFilename(JsonDataAbsolutePath);
// JSON 객체 생성 및 구조체 데이터를 JSON으로 변환
TSharedRef<FJsonObject> JsonObjectSrc = MakeShared<FJsonObject>();
FJsonObjectConverter::UStructToJsonObject(StudentSrc->GetClass(), StudentSrc, JsonObjectSrc);
// JSON 문자열 생성 및 파일에 저장
FString JsonOutString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriterAr = TJsonWriterFactory<TCHAR>::Create(&JsonOutString);
if (FJsonSerializer::Serialize(JsonObjectSrc, JsonWriterAr))
{
FFileHelper::SaveStringToFile(JsonOutString, *JsonDataAbsolutePath);
}
// JSON 파일 로드
FString JsonInString;
FFileHelper::LoadFileToString(JsonInString, *JsonDataAbsolutePath);
// JSON 문자열을 읽어들이고 객체로 역직렬화
TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonInString);
TSharedPtr<FJsonObject> JsonObjectDest;
if (FJsonSerializer::Deserialize(JsonReaderAr, JsonObjectDest))
{
UStudent* JsonStudentDest = NewObject<UStudent>();
if (FJsonObjectConverter::JsonObjectToUStruct(JsonObjectDest.ToSharedRef(), JsonStudentDest->GetClass(), JsonStudentDest))
{
// 역직렬화된 JSON 객체 정보 로깅
UE_LOG(LogTemp, Log, TEXT("이름 %s, 순번 %d"), *JsonStudentDest->GetName(), JsonStudentDest->GetOrder());
}
}
FJsonObjectConverter 를 통해서 UStructToJsonObject 으로 변환 시킬 수 있다.
이후에 데이터를 직렬화 하고, 역직렬화한 데이터를 JsonObjectToUStruct으로 언리얼에 적용 시킬 수 있다.
'언리얼 개발 > 탐구(이론)' 카테고리의 다른 글
[UE_5] 헤더 경로 찾을때 문제생겼을 때 (0) | 2024.06.14 |
---|---|
[UE_5] 언리얼 메모리 관리 시스템 (1) | 2024.06.05 |
[UE_5] TMap (0) | 2024.06.05 |
[UE_5] GAS_ GameplayAbility - Instancing Policy, NetExecutionPolicy (0) | 2024.05.21 |
[UE_5] Online Subsystem (0) | 2024.05.16 |