간단한 데이터 직렬화와 역직렬화 기법
지난 절에서 Save Game Object를 통해 데이터를 저장하고 불러오는 방법을 익혔다면, 이번에는 그 내부 원리를 짚어볼 차례입니다.
핵심은 직렬화(Serialization)와 역직렬화(Deserialization)입니다. 이 개념을 이해하면 저장 시스템에서 어떤 데이터가 안전하게 보존되는지, 어떤 데이터는 별도 처리가 필요한지 훨씬 명확해집니다.
직렬화(Serialization)란 무엇인가?
직렬화는 메모리(RAM) 상에 존재하는 복잡한 객체(Object)의 데이터를 디스크에 저장하거나 네트워크를 통해 전송할 수 있는 형태로 변환하는 과정입니다. 메모리 상의 객체는 여러 변수가 서로 다른 메모리 주소에 흩어져 있거나, 복잡한 포인터 관계로 얽혀 있을 수 있습니다. 이러한 구조를 그대로 디스크에 저장하거나 다른 컴퓨터로 보내는 것은 불가능합니다.
직렬화는 이러한 복잡한 메모리 구조를 연속적인 바이트 스트림(Byte Stream), 즉 하나의 긴 일렬 데이터로 변환합니다. 이렇게 변환된 데이터는 파일로 저장되거나 네트워크를 통해 전송될 수 있습니다.
비유: 마치 살아있는 복잡한 생물(메모리 객체)을 박제하거나, 설계도를 표준화된 문서(바이트 스트림)로 만드는 것과 같습니다. 박제된 생물이나 표준화된 설계도는 보관하거나 이동하기 쉽습니다.
언리얼 엔진의 저장 게임 오브젝트는 바로 이 직렬화 과정을 거쳐 디스크에 파일 형태로 저장됩니다. 블루프린트 개발자는 Save Game to Slot 노드를 호출함으로써 이 복잡한 직렬화 과정을 언리얼 엔진에게 맡기는 것입니다.
역직렬화(Deserialization)란 무엇인가?
역직렬화는 직렬화된 바이트 스트림 데이터를 다시 메모리 상의 원래 객체 형태로 복원하는 과정입니다. 디스크에 저장된 파일이나 네트워크를 통해 수신된 바이트 스트림을 해석하여, 게임 내에서 다시 사용할 수 있는 객체로 재구성하는 것이죠.
비유: 박제된 생물을 다시 살아있는 생물로 복원하거나, 표준화된 설계도를 보고 실제 건물을 다시 짓는 것과 같습니다.
언리얼 엔진의 Load Game From Slot 노드를 호출하면, 디스크의 저장 파일이 역직렬화 과정을 거쳐 SaveGame Object 형태로 메모리에 복원됩니다.
블루프린트와 직렬화/역직렬화
블루프린트를 사용할 때 대부분의 직렬화/역직렬화 과정은 언리얼 엔진 내부적으로 자동으로 처리됩니다. 개발자가 직접 바이트 스트림을 다룰 필요는 거의 없습니다. 하지만 어떤 데이터가 직렬화될 수 있고 없는지 이해하는 것은 중요합니다.
- 저장 게임 오브젝트의 변수:
BP_MySaveGame같은 저장 게임 오브젝트 블루프린트에 선언된 변수(Integer, Float, Vector, Text, Array, Map, Structure, Object Reference 등)는 기본적으로 직렬화 대상입니다.Save Game to Slot을 호출하면 이 변수 값들이 자동으로 저장 파일에 기록됩니다. - 복잡한 오브젝트 참조: 저장 게임 오브젝트에 특정 액터(
Actor Reference)를 직접 넣을 때는 주의해야 합니다. 일반적인Actor Reference는 레벨 로드 시 메모리 주소가 바뀔 수 있으므로, 액터 자체보다 고유 ID(Get Actor Unique ID)나 이름(Get Name)을 저장하고 로드 시 다시 조회해 연결하는 방식이 더 안정적입니다.- 예외: 언리얼 엔진 5.0부터 도입된
Soft Object Reference나Soft Class Reference는 직렬화가 가능하며, 필요한 시점에 로드하여 사용할 수 있도록 설계된 참조 타입입니다. 이는 에셋(Static Mesh, Material 등)에 대한 참조를 저장할 때 유용합니다.
- 예외: 언리얼 엔진 5.0부터 도입된
- 컴포넌트의 변수: 액터 블루프린트 내 컴포넌트의 변수는 기본적으로 해당 액터가 저장될 때 함께 직렬화되지 않습니다. 컴포넌트의 특정 상태를 저장하고 싶다면, 그 상태를 액터 블루프린트의 변수에 복사하여 저장 게임 오브젝트에 담거나, 저장/로드 로직을 직접 구현해야 합니다.
직렬화와 역직렬화의 활용 (고급 개념)
언리얼 엔진의 내장 저장 시스템 외에도, 직렬화/역직렬화는 다음과 같은 상황에서 간접적으로 활용되거나 이해가 필요합니다.
- 네트워크 통신: 멀티플레이어 게임에서 클라이언트와 서버 간에 데이터를 주고받을 때, 메모리상의 게임 상태는 네트워크를 통해 전송 가능한 바이트 스트림으로 직렬화되어야 합니다.
- 에셋 관리: 언리얼 엔진의 모든 에셋(Static Mesh, Texture, Material, Blueprint 등)은 디스크에 파일 형태로 저장될 때 직렬화됩니다. 엔진이 이 파일을 로드할 때 역직렬화하여 메모리 상의 에셋 객체로 만듭니다.
- 커스텀 저장 형식:
SaveGame시스템 외에 완전히 커스텀된 저장 형식을 만들고자 할 때, 파일을 직접 읽고 쓰는 로직을 구현해야 하며, 이때 어떤 데이터를 어떤 순서와 형태로 바이트로 변환하고 다시 복원할지 직렬화/역직렬화의 개념이 필요합니다.
블루프린트 개발에서는 대부분 SaveGame Object를 통해 직렬화/역직렬화의 복잡성을 추상화하여 사용하지만, 이 개념을 이해하고 있으면 왜 특정 타입의 데이터가 저장될 수 있고 없는지, 그리고 고급 저장 기능을 구현할 때 어떤 문제가 발생할 수 있는지에 대한 통찰력을 얻을 수 있습니다. 이는 데이터를 다루는 게임 개발의 근간이 되는 중요한 원리입니다.
저장 안정성 검증 루틴
직렬화/역직렬화 로직을 적용한 뒤에는 다음 순서로 검증하면 안정성이 높아집니다.
- 저장 직후 즉시 로드하여 동일 값 재현 여부 확인
- 게임 재시작 후 로드 테스트로 슬롯 유효성 확인
- 필드 추가/삭제 시 기본값 처리와 버전 호환 정책 점검
- 참조형 데이터는 ID 기반 복원으로 null 참조 여부 확인
이 루틴을 배포 전 체크 항목으로 고정하면 저장 데이터 회귀를 줄일 수 있습니다.
이번 절에서는 데이터 직렬화와 역직렬화의 기본 개념, 그리고 언리얼 엔진 블루프린트에서 이들이 어떻게 작동하는지에 대해 알아보았습니다.
블루프린트 저장 흐름 점검 메모
저장 기능 실습은 "저장 성공" 여부만 확인하면 놓치는 부분이 많습니다. 아래 기준을 추가로 확인하면 복구 실패를 줄일 수 있습니다.
- 저장 직후와 에디터 재시작 후의 로드 결과가 동일한지 비교합니다.
- 버전 변경 시 기본값 보정 로직이 있는지 점검합니다.
- 참조형 데이터는 ID 기준으로 직렬화/역직렬화하는지 확인합니다.