[Unity] 직렬화 시스템 — [SerializeField], .asset, .meta가 하는 일
[Unity] 직렬화 시스템 — [SerializeField], .asset, .meta가 하는 일
들어가며
Unity에서 ScriptableObject를 만들면 .asset 파일이 생긴다. 이 파일을 텍스트 에디터로 열어보면 이런 내용이 보인다:
--- !u!114 &11400000
MonoBehaviour:
m_Script: {fileID: 11500000, guid: d51f196eb3cf5bb4...}
m_Name: CATEGORY_ATTACK
id: 1
codeName: attack
displayName: Attack
처음 보면 궁금한 게 많다.
- 왜
MonoBehaviour라고 적혀있나? ScriptableObject인데? guid는 뭔가?[SerializeField]를 붙이면 정확히 어떤 일이 일어나는가?.meta파일은 왜 생기는가?
이 글에서는 Unity 직렬화 시스템이 무엇을 저장하고, 어디에 저장하고, 어떻게 복원하는지 단계적으로 정리한다.
1. 직렬화가 뭔가
메모리에 있는 객체를 파일로 저장하고, 나중에 다시 복원하는 것이다.
// 메모리에 있는 객체
SkillData fireball = new SkillData();
fireball.damage = 100;
fireball.cooldown = 3.0f;
// 직렬화 = 이 객체의 필드 값을 파일에 저장
// 역직렬화 = 파일에서 값을 읽어서 객체를 복원
Unity는 이 과정을 자동으로 해준다. 규칙에 맞는 필드를 선언만 하면 된다.
2. 뭐가 저장되고 뭐가 안 되나
규칙은 간단하다
public class SkillDataSO : ScriptableObject
{
// ── 저장됨 ──
public int damage = 100; // public 기본형 → 자동
public string skillName = "Fireball"; // public string → 자동
public Sprite icon; // Unity Object 참조 → 자동
public List<string> tags; // List<직렬화가능> → 자동
[SerializeField] private float _cooldown; // private + 어트리뷰트 → 저장됨
// ── 저장 안 됨 ──
private int _cachedDps; // private → 무시
public static int TotalCount; // static → 무시
public Dictionary<string, int> dict; // Dictionary → 미지원
}
| 조건 | 저장 여부 |
|---|---|
public 필드 |
O |
[SerializeField] private |
O |
private (어트리뷰트 없음) |
X |
static |
X |
Dictionary |
X |
delegate / Action |
X |
List<T> (T가 저장 가능) |
O |
[Serializable] 클래스 |
O |
왜 이 규칙인가
Unity 직렬화 시스템은 필드(field) 중심이다. 메서드나 property는 저장 대상이 아니다.
[SerializeField] private int id = 1; // 필드 → 저장됨. .asset에 들어감.
public int ID => id; // 프로퍼티 → 저장 안 됨. getter일 뿐.
property는 코드(getter/setter 메서드)이지 데이터가 아니다. 저장할 수 없다.
3. [SerializeField] vs [Serializable] — 붙이는 대상이 다르다
이 둘이 자주 헷갈린다.
[SerializeField] — 필드에 붙인다
[SerializeField] private int id; // 이 필드를 저장 대상으로 만든다
[SerializeField] private string codeName;
"private인데 Unity가 저장하게 해줘"라는 의미.
[Serializable] — 클래스/구조체에 붙인다
[System.Serializable]
public class DamageInfo // 이 타입의 내부 필드까지 저장 가능하게 한다
{
public int amount;
public float critRate;
}
public class SkillDataSO : ScriptableObject
{
[SerializeField] private DamageInfo damageInfo;
// → DamageInfo 안의 amount, critRate까지 .asset에 저장됨
}
정리:
필드 하나를 저장 대상으로 → [SerializeField]
커스텀 타입 내부까지 저장 → [System.Serializable]4. .asset 파일 안에 뭐가 들어있나
ScriptableObject를 만들면 .asset 파일이 생긴다. 실제로 열어보면:
%YAML 1.1
--- !u!114 &11400000
MonoBehaviour:
m_Script: {fileID: 11500000, guid: d51f196eb3cf5bb4..., type: 3}
m_Name: CATEGORY_ATTACK
categories: []
icon: {fileID: 21300000, guid: f6e5d4c3b2a10987...}
id: 1
codeName: attack
displayName: Attack
description: Physical attack skills
한 줄씩 보면:
| 줄 | 의미 |
|---|---|
!u!114 |
Unity 내부 타입 번호 114 = "사용자 C# 스크립트" |
MonoBehaviour: |
YAML 태그 이름. MB든 SO든 114번은 이 이름을 씀 (역사적 이유) |
m_Script: {guid: ...} |
이 .asset의 실제 C# 타입을 가리키는 GUID |
m_Name |
에셋 이름 |
id: 1 |
[SerializeField] private int id 의 저장된 값 |
icon: {guid: ...} |
다른 에셋(Sprite)을 GUID로 참조 |
MonoBehaviour라고 적혀있는데 SO 아닌가?
맞다. 실제 타입은 m_Script의 GUID가 결정한다. MonoBehaviour:는 Unity YAML 포맷이 사용자 스크립트를 통칭하는 태그 이름일 뿐이다.
@startuml
skinparam defaultFontName Malgun Gothic
skinparam backgroundColor #1E1E1E
skinparam defaultFontColor #D4D4D4
skinparam objectBorderColor #569CD6
skinparam objectBackgroundColor #2D2D2D
skinparam packageBorderColor #569CD6
skinparam packageBackgroundColor #252526
skinparam arrowColor #D4D4D4
skinparam noteBorderColor #6A9955
skinparam noteBackgroundColor #2D2D2D
skinparam titleFontColor white
skinparam titleFontSize 20
title .asset 파일 구조
object "CATEGORY_ATTACK.asset" as asset {
MonoBehaviour: (YAML 태그)
m_Script: guid → IdentifiedObject.cs
id: 1
codeName: "attack"
icon: guid → fire_icon.png
categories: []
}
object "IdentifiedObject.cs" as script {
[SerializeField] int id
[SerializeField] string codeName
[SerializeField] Sprite icon
}
object "fire_icon.png" as icon {
Sprite 이미지 데이터
}
asset -down-> script : m_Script (GUID)
asset -down-> icon : icon 참조 (GUID)
note bottom of asset
실제 타입은 m_Script GUID가 결정
MonoBehaviour 태그는 이름만 그렇다
end note
@enduml
5. .meta 파일과 GUID
.meta는 언제 생기나
Unity 프로젝트에 파일이 추가될 때마다 자동 생성된다. 예외 없음.
Category_Attack.asset ← 에셋 파일
Category_Attack.asset.meta ← 자동 생성된 메타 파일
PlayerController.cs ← 스크립트 파일
PlayerController.cs.meta ← 자동 생성
Icons/ ← 폴더
Icons.meta ← 폴더에도 생김.meta 안에 뭐가 있나
fileFormatVersion: 2
guid: dfa9966517debc542a9ce23e6e10a99b
NativeFormatImporter:
mainObjectFileID: 11400000
핵심은 guid 한 줄이다. 이 에셋의 영구 신분증.
GUID가 왜 중요한가
Unity는 에셋 참조를 파일명이 아니라 GUID로 연결한다.
SkillData.asset 안에:
icon: {guid: dfa9966517debc54...} ← 파일명이 아니라 GUID
fire_icon.png.meta 안에:
guid: dfa9966517debc54... ← 같은 GUID그래서:
파일명 변경: fire_icon.png → attack_icon.png
→ .meta의 GUID는 안 바뀜
→ 참조 유지됨
폴더 이동: Assets/Icons/ → Assets/Art/Icons/
→ .meta가 같이 이동
→ GUID 그대로 → 참조 유지됨
.meta 삭제:
→ Unity가 새 .meta 생성 → 새 GUID 발급
→ 이 파일을 참조하던 모든 곳에서 연결 깨짐!
.meta 파일은 절대 삭제하면 안 되고, 반드시 버전 관리(SVN/Git)에 포함해야 한다.
6. .asset vs .prefab vs .scene
Unity에서 데이터를 저장하는 파일 3종류:
| 파일 | 뭘 저장하나 | 예시 |
|---|---|---|
.asset |
ScriptableObject 인스턴스 | Fireball.asset, Category_Attack.asset |
.prefab |
GameObject 구조 (컴포넌트+자식) | Player.prefab, Bullet.prefab |
.unity |
씬 전체 (모든 오브젝트+설정) | MainMenu.unity, Level1.unity |
[SerializeField] 필드는 그 오브젝트가 어디에 있느냐에 따라 다른 파일에 저장된다:
// MonoBehaviour의 필드
public class PlayerController : MonoBehaviour
{
[SerializeField] private int maxHP = 100;
}
// → 씬에 있으면 .unity에 저장
// → Prefab이면 .prefab에 저장
// ScriptableObject의 필드
public class SkillDataSO : ScriptableObject
{
[SerializeField] private int damage = 100;
}
// → 항상 .asset에 저장
7. Instantiate — 직렬화 기반 복사
Instantiate()는 Unity의 복사 함수인데, 내부적으로 직렬화 → 역직렬화로 동작한다.
var clone = Instantiate(original);
// 내부 동작:
// 1. original의 직렬화 데이터를 읽는다
// 2. 새 인스턴스를 만든다
// 3. 읽은 데이터를 새 인스턴스에 쓴다
그래서 직렬화 대상이 아닌 필드는 복사되지 않는다:
public class SkillDataSO : ScriptableObject
{
[SerializeField] private int damage = 100; // ✅ 복사됨
private int _cachedDps = 999; // ❌ 복사 안 됨 (기본값 0)
}
복사 규칙 — 타입에 따라 다르다
public class ParentSO : ScriptableObject
{
[SerializeField] private int level = 10; // 값 타입
[SerializeField] private DamageInfo info; // [Serializable] class
[SerializeField] private SkillDataSO mainSkill; // SO 참조
[SerializeField] private Sprite icon; // Unity Object 참조
}
| 필드 | 타입 | 복사 결과 |
|---|---|---|
level |
int (값 타입) | 독립 복사. 바꿔도 원본 안 바뀜. |
info |
[Serializable] class | 새 인스턴스 생성. 독립적. |
mainSkill |
ScriptableObject | 참조만 복사. 같은 .asset을 가리킴. |
icon |
Sprite | 참조만 복사. 같은 이미지를 가리킴. |
핵심 규칙:
UnityEngine.Object를 상속한 타입 → 참조만 복사 (같은 걸 가리킴)
나머지 전부 → 새로 만들어서 복사 (독립적)
mainSkill을 통해 값을 바꾸면 원본도 영향받는다. 이걸 방지하려면mainSkill도 별도로Instantiate하거나Clone()을 재정의해야 한다.
8. 코드 → 컴파일 → .dll vs 데이터 → 직렬화 → .asset
코드 파이프라인:
SkillDataSO.cs → [Roslyn 컴파일] → IL → Assembly-CSharp.dll
"클래스 정의(틀)가 dll에 들어감"
필드를 추가하려면 → 코드 수정 → 재컴파일
데이터 파이프라인:
[CreateAssetMenu] → Inspector에서 생성 → Fireball.asset
"인스턴스의 필드 값이 YAML로 디스크에 저장됨"
값을 바꾸려면 → Inspector에서 수정 → 재컴파일 불필요.cs 파일을 수정하면 재컴파일이 필요하지만, .asset 파일의 값을 Inspector에서 바꾸면 재컴파일 없이 즉시 반영된다. 이게 ScriptableObject의 핵심 가치라고 생각한다.
정리
Unity 직렬화 시스템:
저장 대상 = [SerializeField] 필드 또는 public 필드
저장 위치 = .asset (SO) / .prefab (Prefab) / .unity (씬)
참조 방식 = GUID 기반 (.meta 파일에 저장)
[SerializeField] → 필드를 저장 대상으로 만든다
[Serializable] → 커스텀 타입 내부까지 저장 가능하게 한다
.meta + GUID → 파일명/경로가 바뀌어도 참조가 유지된다
Instantiate 복사:
UnityEngine.Object → 참조만 복사
나머지 → 새로 만들어서 복사
.cs 변경 = 재컴파일 필요
.asset 변경 = 재컴파일 불필요 (데이터만 바뀜)참고 자료
'Unity' 카테고리의 다른 글
| [Unity] 코루틴은 프레임, yield 타이밍, 그리고 DLL 분석하기 (0) | 2026.07.13 |
|---|---|
| [Unity] 코루틴과 IEnumerator, Heap (0) | 2026.07.03 |
| [Unity] UnityEngine.Object의 이중 구조와 == null의 진짜 의미 (1) | 2026.06.11 |
| [Unity] C# 컴파일 과정과 IL, Mono, IL2CPP, asmdef 이해하기 (0) | 2026.06.10 |
| [Unity] Render서버를 통한 Addressable 로드 (0) | 2025.12.03 |