Unity Save System — JSON vs Binary
게임 세이브 시스템은 “데이터를 어떤 형식으로, 어디에 저장하느냐”로 결정됩니다. Unity에서 가장 많이 쓰는 두 방식은 JSON(텍스트, 디버그 용이)과 Binary(바이너리, 크기·속도 우위)입니다. 두 방식의 특성을 정확히 이해하고 프로젝트 요구사항에 맞게 선택해야 합니다.
1. 저장 경로 선택
섹션 제목: “1. 저장 경로 선택”// Application.persistentDataPath: 플랫폼별 쓰기 가능 경로// PC: C:/Users/<user>/AppData/LocalLow/<company>/<product>/// Android: /data/data/<package>/files/// iOS: /var/mobile/Applications/<UUID>/Documents/
string SavePath => Path.Combine(Application.persistentDataPath, "save.dat");PlayerPrefs는 소량의 설정값(볼륨, 언어)에만 씁니다. 실제 게임 세이브는 파일 시스템에 직접 저장하는 것이 안전합니다.
2. JSON 세이브 시스템
섹션 제목: “2. JSON 세이브 시스템”2.1 데이터 클래스 정의
섹션 제목: “2.1 데이터 클래스 정의”[System.Serializable]public class SaveData{ public string playerName; public int level; public float health; public Vector3Serializable position; // Vector3은 직렬화 직접 지원 안 됨 public List<string> unlockedItems = new(); public long savedAt; // UTC ticks}
[System.Serializable]public struct Vector3Serializable{ public float x, y, z;
public static implicit operator Vector3(Vector3Serializable v) => new(v.x, v.y, v.z); public static implicit operator Vector3Serializable(Vector3 v) => new() { x = v.x, y = v.y, z = v.z };}2.2 저장 / 로드 구현
섹션 제목: “2.2 저장 / 로드 구현”using System.IO;using UnityEngine;
public static class JsonSaveSystem{ private static readonly string Path = System.IO.Path.Combine(Application.persistentDataPath, "save.json");
public static void Save(SaveData data) { data.savedAt = System.DateTime.UtcNow.Ticks; string json = JsonUtility.ToJson(data, prettyPrint: false); File.WriteAllText(Path, json); }
public static SaveData Load() { if (!File.Exists(Path)) return new SaveData();
string json = File.ReadAllText(Path); return JsonUtility.FromJson<SaveData>(json); }
public static void Delete() => File.Delete(Path);}JsonUtility는 Unity 내장 직렬화기로 가볍고 빠릅니다. 단, Dictionary, private 필드, 다형성 타입은 지원하지 않습니다. 이 경우 Newtonsoft.Json (Json.NET) 패키지를 사용합니다.
3. Binary 세이브 시스템
섹션 제목: “3. Binary 세이브 시스템”3.1 BinaryFormatter (사용 주의)
섹션 제목: “3.1 BinaryFormatter (사용 주의)”BinaryFormatter는 .NET의 내장 바이너리 직렬화기지만 보안 취약점 때문에 Unity 2022+에서 obsolete 처리되었습니다. 대신 BinaryWriter/Reader를 직접 사용합니다.
3.2 BinaryWriter/Reader 구현
섹션 제목: “3.2 BinaryWriter/Reader 구현”using System.IO;using UnityEngine;
public static class BinarySaveSystem{ private static readonly string Path = System.IO.Path.Combine(Application.persistentDataPath, "save.dat");
public static void Save(SaveData data) { using var fs = new FileStream(Path, FileMode.Create); using var writer = new BinaryWriter(fs);
writer.Write(data.playerName ?? string.Empty); writer.Write(data.level); writer.Write(data.health); writer.Write(data.position.x); writer.Write(data.position.y); writer.Write(data.position.z); writer.Write(data.unlockedItems.Count); foreach (var item in data.unlockedItems) writer.Write(item); writer.Write(data.savedAt); }
public static SaveData Load() { if (!File.Exists(Path)) return new SaveData();
using var fs = new FileStream(Path, FileMode.Open); using var reader = new BinaryReader(fs);
var data = new SaveData { playerName = reader.ReadString(), level = reader.ReadInt32(), health = reader.ReadSingle(), position = new Vector3Serializable { x = reader.ReadSingle(), y = reader.ReadSingle(), z = reader.ReadSingle() } };
int count = reader.ReadInt32(); for (int i = 0; i < count; i++) data.unlockedItems.Add(reader.ReadString());
data.savedAt = reader.ReadInt64(); return data; }}4. 암호화 적용
섹션 제목: “4. 암호화 적용”모바일 게임에서 세이브 파일 위변조를 막으려면 AES 암호화를 적용합니다.
using System.Security.Cryptography;using System.Text;
public static class SaveEncryption{ // 실제 프로덕션에서는 키를 소스코드에 하드코딩하지 말고 // 서버 검증 + 난독화를 함께 사용할 것 private static readonly byte[] Key = Encoding.UTF8.GetBytes("MyGame16ByteKey!"); // 16 bytes = AES-128 private static readonly byte[] IV = Encoding.UTF8.GetBytes("InitVector1234!!");
public static byte[] Encrypt(string plainText) { using var aes = Aes.Create(); aes.Key = Key; aes.IV = IV;
using var encryptor = aes.CreateEncryptor(); byte[] inputBytes = Encoding.UTF8.GetBytes(plainText); return encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length); }
public static string Decrypt(byte[] cipherBytes) { using var aes = Aes.Create(); aes.Key = Key; aes.IV = IV;
using var decryptor = aes.CreateDecryptor(); byte[] outputBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); return Encoding.UTF8.GetString(outputBytes); }}5. 방식 비교
섹션 제목: “5. 방식 비교”| 항목 | JSON | Binary |
|---|---|---|
| 가독성 | 텍스트, 수동 편집 가능 | 불가능 |
| 파일 크기 | 크다 (키 이름 포함) | 작다 |
| 직렬화 속도 | 보통 | 빠름 |
| 스키마 변경 대응 | 쉬움 (필드 추가 무시) | 어려움 (순서 의존) |
| 위변조 난이도 | 쉬움 | 상대적으로 어려움 |
| 권장 용도 | 설정, 개발 단계 | 프로덕션 세이브 |
- 개발 초기에는 JSON으로 데이터 구조를 빠르게 검증하고, 출시 전에 Binary + 암호화로 전환하는 것이 일반적인 패턴이다.
BinaryFormatter는 보안 이슈로 사용하지 말고BinaryWriter/Reader를 직접 구현한다.Vector3,Quaternion등 Unity 타입은 직렬화 래퍼 구조체를 만들어 처리한다.- 세이브 파일 경로는
Application.persistentDataPath를 사용하고 플랫폼별 차이를 확인한다.