본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 14. 10:26

Godot 4 저장 시스템: 실제 출시된 게임에서 가져온 5가지 패턴

요약

본 기사는 Godot 엔진에서 게임 저장 시스템을 구현할 때 흔히 간과되는 문제점들을 지적하며, 실제 상용 게임 사례를 바탕으로 다섯 가지 저장 패턴을 분석합니다. 각 패턴(ConfigFile, JSON, Binary Serialization, Custom Resources 등)의 장단점을 설명하고, 어떤 종류의 데이터에 어떤 방식을 적용하는 것이 가장 적절한지 가이드라인을 제시합니다.

핵심 포인트

  • 저장 시스템은 단순한 기능이 아니라 리팩토링, 모딩, 패치 대응까지 고려해야 하는 중요한 설계 결정입니다.
  • 설정(settings) 저장에는 사람이 읽기 쉬운 ConfigFile을 사용하고, 기본 자료형 위주의 데이터는 JSON을 사용하는 것이 적절합니다.
  • Godot의 네이티브 `store_var`/`get_var`를 이용한 이진 직렬화는 타입 안정성이 높고 빠르지만, 버전 관리 시스템(VCS)에서 diff가 불가능하다는 단점이 있습니다.
  • 가장 권장되는 패턴은 Custom Resources와 ResourceSaver를 사용하는 것입니다. 이는 완전한 타입 안정성을 제공하며 Godot의 공식적인 방법론입니다.
  • 저장 데이터의 성격(설정 vs. 게임 진행 상황)에 따라 적절한 직렬화 방식을 선택하는 것이 중요합니다.

모든 Godot 튜토리얼은 저장 시스템이 쉽다고 가정합니다. 하지만 그렇지 않습니다. 첫날 내리는 선택은 여러분의 저장 포맷이 리팩터링 (refactor) 과정에서 살아남을지, 모더 (modder)들이 설정을 편집할 수 있을지, 그리고 패치를 출시했을 때 플레이어들이 진행 상황을 잃게 될지를 조용히 결정합니다. 저는 두 개의 Godot 게임을 출시했으며, 다른 몇몇 게임의 문서와 소스 코드를 파헤쳐 보았습니다. 여기 실제 상용 Godot 게임에서 나타나는 다섯 가지 저장 패턴을, 어떤 상황에서 적절하고 어떤 상황에서 문제가 되는지에 따라 순위를 매겨 소개합니다.

  1. 상태(state)가 아닌 설정을 위한 ConfigFile
    ConfigFile은 INI 스타일의 파일, 즉 key = value 쌍이 포함된 [section] 블록을 작성합니다. 공식 문서에서는 이를 "간단한 설정 파일 생성"이라고 설명합니다. 이것은 한 가지, 즉 설정 (settings)에 매우 능숙합니다. 오디오 볼륨, 해상도, 키 바인딩 (keybinds), 접근성 토글 등이 이에 해당합니다. 이 파일은 사람이 읽을 수 있고, user://settings.cfg 로 배포하기 쉬우며, 기술적 지식이 있는 플레이어들이 아주 쉽게 편집할 수 있습니다.

var cfg = ConfigFile.new()
cfg.set_value("audio", "master", 0.8)
cfg.set_value("controls", "jump", "space")
cfg.save("user://settings.cfg")

문제가 되는 지점: 중첩된 데이터 (nested data)를 잘 처리하지 못합니다. 게임 진행 상황을 ConfigFile에 저장하면 딕셔너리 (dictionary)를 수동으로 평탄화 (flattening)해야 하는 상황에 직면하게 됩니다. GDQuest의 저장 치트시트 (save cheatsheet)에서는 ConfigFile을 오직 "설정과 같은 작은 데이터"에만 사용할 것을 명시적으로 권장합니다.

  1. 직접 편집하는 데이터를 위한 FileAccess를 이용한 JSON
    JSON은 모든 웹 개발자가 알고 있는 형식이며, Godot에서는 FileAccess와 함께 JSON.stringify / JSON.parse_string을 통해 작동합니다. Godot Learning의 2026년 1월 튜토리얼은 자동 저장 및 여러 슬롯을 포함한 전체 구현 과정을 다룹니다.

var data = { "hp" : 80 , "pos" : [ 10 , 20 ], "inventory" : [ "sword" , "potion" ]}
var file = FileAccess.open("user://save_01.json", FileAccess.WRITE)
file.store_string(JSON.stringify(data))

문제가 되는 지점: JSON은 Godot 타입 (types)을 기본적으로 지원하지 않습니다. Vector2(10, 20)는 [10, 20]이 되며, 양방향 모두를 위해 변환 코드를 직접 작성해야 합니다.

한 가지 유형을 놓치면 로드 시 Vector2가 아닌 Array가 조용히 생성되어, 캐릭터가 null.x 위치에서 스폰됩니다. 다음과 같은 경우 JSON을 사용하세요:

  • 사람(모더, QA, 디자이너)이 저장 파일을 읽어야 할 때
  • 저장 데이터가 대부분 기본 자료형 (strings, numbers, arrays)일 때
  • 어차피 JSON을 사용하는 웹 백엔드와 동기화할 때
  1. store_var / get_var를 이용한 이진 직렬화 (Binary serialization)
    가장 간과되는 네이티브 옵션입니다. FileAccess.store_var()와 FileAccess.get_var()는 Variant 타입을 이진 파일에 직접 기록합니다. Vector2는 Vector2로 유지되고, Dictionary는 Dictionary로 유지됩니다. 변환 코드가 필요 없습니다.
var file = FileAccess.open("user://save_01.dat", FileAccess.WRITE)
file.store_var({"hp": 80, "pos": Vector2(10, 20)})

문서에 따르면 이 형식은 "객체(objects)의 저장 및 로드를 방지하여 Godot에서 코드 실행을 가능하게 하는 요소를 차단하므로 기본적으로 안전(secure by default)합니다." 이러한 보안은 객체의 역직렬화 (deserialize)를 거부하는 데서 오지만, 이는 곧 한계이기도 합니다. 즉, 씬 참조 (scene references)나 클래스 인스턴스 (class instances)를 직접 저장할 수 없습니다.

단점: 이진 파일은 버전 관리 시스템 (version control)에서 차이점 비교 (diff)를 할 수 없습니다. 만약 저장 데이터가 설정 파일(config)과 같고 PR 리뷰가 필요하다면, 이것은 잘못된 도구입니다. 속도가 중요하고 사람이 파일을 읽을 필요가 없는 핫 패스 (hot-path) 저장에 사용하세요.

  1. 커스텀 리소스 (Custom Resources) + ResourceSaver
    Godot이 사용자가 사용하기를 권장하는 패턴입니다. 저장할 모든 필드에 대해 @export 속성을 가진 Resource 서브클래스를 정의하고, 인스턴스를 채운 뒤 ResourceSaver.save()를 호출합니다. GDQuest의 리소스 저장 가이드에서는 이를 "완전한 타입 안정성 (type safety)을 갖춘 가장 간결한 방법"이라고 부릅니다.
class_name SaveData extends Resource

@export var hp : int = 100
@export var pos : Vector2 = Vector2.ZERO
@export var inventory : Array[String] = []

var save = SaveData.new()
save.hp = 80
save.pos = player.position
ResourceSaver.save(save, "user://save_01.tres")

var loaded : SaveData = load("user://save_01.tres")

정적 타이핑 (Static typing), 코드 완성 (Code completion), 자동 변환이 가능합니다.

가장 중요한 점은, 확장자를 변경함으로써 동일한 Resource 클래스를 .tres (텍스트, diff 가능) 또는 .res (이진, 더 작음) 중 하나로 저장할 수 있다는 것입니다. 여기서 발생하는 문제점은 스키마 마이그레이션 (schema migration)입니다. 필드를 추가하는 것은 괜찮지만, 필드의 이름을 바꾸거나 삭제하면 기존 저장 데이터가 깨집니다. 기존 필드를 영원히 유지하거나 마이그레이션 코드를 작성해야 합니다. Godot의 공식 저장 문서에서는 이를 타입 안정성 (type safety)을 위해 감수해야 하는 트레이드오프 (trade-off)로 지적합니다.

  1. 하이브리드 패턴: 상태를 위한 Resource, 설정을 위한 ConfigFile
    이것이 실제로 출시되는 게임들이 사용하는 방식입니다. 저는 위 네 가지 패턴 중 하나만 사용하는 복잡한 Godot 게임을 단 하나도 본 적이 없습니다.
    패턴:
  • user://settings.cfg (ConfigFile): 오디오, 컨트롤, 디스플레이 설정용
  • user://save_01.tres (커스텀 Resource): 게임 상태 (game state)용
  • 선택 사항: user://stats.json (JSON): 분석 (analytics) 또는 수동으로 확인하고 싶은 항목용

Slay the Spire 2는 실행 상태 (run state)와 영구적인 해금 요소 (persistent unlocks)를 별도의 파일로 나누어 표준 Godot user 폴더에 저장하며, 이를 통해 실행 중 충돌이 발생하더라도 메타 진행 상황 (meta-progress)이 파괴되지 않도록 합니다. 핵심 교훈은 파일 형식의 선택이 아니라, 생명주기 (lifecycle, 실행별, 프로필별, 설치별)에 따라 상태를 분리하는 것입니다.

AI 어시스턴트들이 여기서 실수하는 부분
이것은 2026년의 문제입니다. ChatGPT나 Claude에게 Godot 4 저장 시스템을 물어보면 거의 항상 다음 세 가지 패턴 중 하나를 받게 됩니다:

  1. func _save(): with var file = File.new()
    이것은 Godot 3 문법입니다. File은 4.0에서 제거되었습니다. 대체 방식은 정적 호출인 FileAccess.open()입니다.

  2. 수동 Vector 직렬화 (serialization)를 포함한 JSON이지만, @export 어노테이션 (annotation) 제안이 없음.
    범용 모델들은 Resource 서브클래스에 @export를 사용하는 것이 현대적인 방식이라는 것을 알지 못합니다.

  3. 저장 데이터용 store_line()
    이는 일반 텍스트에는 작동하지만, 모든 Godot 타입을 잃게 됩니다. 또한 '수동 변환을 동반한 JSON'이라는 안티 패턴 (antipattern)을 조장합니다. 이는 제가 이전 포스트인 'AI 어시스턴트들이 여전히 틀리는 Godot 4 API 호출'에서 다루었던 것과 동일한 경향입니다.

해결책은 동일합니다. 프로젝트의 project.godot를 읽고 사용자가 실제로 어떤 Godot 버전을 사용 중인지 아는, Godot를 인식하는 도구를 사용하십시오.

Ziva와 같은 도구는 추론(inference) 시점에 현재의 Godot 문서를 모델에 주입하며, 이를 통해 4.7 프로젝트에서 File.new()의 늪에 빠지는 것을 방지합니다. 빠른 결정을 위한 테이블입니다.

패턴용도피해야 할 상황
ConfigFile설정(Settings), 키 바인딩(keybinds)게임 상태(Game state)
JSON모더(Modder)가 편집 가능한 저장 파일, 웹 동기화(web sync)타입(Type) 의존성이 높은 상태
Binary빠른 저장, 거대한 상태(big state)차이점(diff)을 확인해야 하는 파일
Custom Resource타입 안정성(Type-safe)이 보장된 게임 상태스키마(Schema) 변동이 심한 데이터
Hybrid실제 출시된 게임아주 작은 프로토타입

만약 오늘 새로운 Godot 4 프로젝트를 시작한다면, 상태(state)를 위해서는 Custom Resource를, 설정(settings)을 위해서는 ConfigFile을 기본값으로 선택하십시오. 분석(analytics)이나 디버그(debug) 관련 사항은 실제로 직접 읽어야 할 필요가 있는 경우에만 JSON으로 마이그레이션하십시오. 이를 잘못 결정했을 때의 대가는 단순히 "저장 파일이 보기 흉하다"는 것이 아닙니다. 그것은 "출시 6개월 차에 패치를 배포했는데, 저장 데이터가 사라져서 플레이어의 5%가 Steam에 별점 1점 리뷰를 남기는 것"입니다. 6개월 차의 재작업(rewrite)을 견뎌낼 수 있는 패턴을 선택하십시오.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
1

댓글

0