1. Unity의 직렬화(Serialization)란?
- 직렬화는 객체의 상태(필드 값)를 저장하고 복원할 수 있게 변환하는 과정이다.
- Unity는 Inspector에 값을 표시하고, 씬/프리팹/에셋에 데이터를 저장하기 위해 자체 직렬화 시스템을 사용한다.
👉 따라서 [SerializeField]가 붙은 필드나 [System.Serializable] 클래스는 Inspector에 노출되고, Unity가 값을 저장/복원해준다.
2. Unity 직렬화 규칙
- Unity에서 직렬화할 수 있는 것은 필드(Field)뿐이다.
- public 필드 → 자동 직렬화
- private 필드 + [SerializeField] → 직렬화
- [Serializable] 클래스 안의 필드 → 직렬화
- ❌ **프로퍼티(Property)**는 기본적으로 직렬화 불가
(프로퍼티는 사실상 get/set 메서드일 뿐이므로 Unity가 저장할 수 있는 메모리가 없음)
3. [field: SerializeField]의 역할
- C# 프로퍼티는 내부적으로 백킹 필드(backing field)라는 숨겨진 필드를 가진다.
public float BaseSpeed { get; private set; } = 5f;
- 컴파일러는 내부적으로 다음과 같은 코드를 만든다:
private float <BaseSpeed>k__BackingField = 5f;
public float BaseSpeed { get { return <BaseSpeed>k__BackingField; } private set { <BaseSpeed>k__BackingField = value; } }
➡️ 여기서 [field: SerializeField]를 붙이면
👉 이 자동 생성된 백킹 필드에 직렬화 속성을 적용 → Unity가 Inspector에 표시 가능해진다.
4. 사용 방식 비교
🔹 필드 + 프로퍼티 방식
[SerializeField] private float baseSpeed = 5f;
public float BaseSpeed { get { return baseSpeed; } private set { baseSpeed = value; } }
- 직렬화 대상: baseSpeed (명시적 필드)
- 프로퍼티는 단순히 baseSpeed를 감싸는 Wrapper
- Inspector에는 baseSpeed 값이 표시된다.
✅ 장점
- Unity에서 전통적으로 가장 많이 쓰이는 방식 → 친숙하고 직관적
- Unity 공식 문서/예제에서도 흔히 사용됨
❌ 단점
- 코드가 2줄 필요 (필드 + 프로퍼티)
- 관리해야 할 멤버가 늘어난다.
🔹 프로퍼티 + [field: SerializeField] 방식
[field: SerializeField] public float BaseSpeed { get; private set; } = 5f;
- 직렬화 대상: 자동 생성된 백킹 필드 (<>k__BackingField)
- Inspector에는 BaseSpeed 프로퍼티와 연결된 값이 바로 표시된다.
✅ 장점
- 코드가 1줄로 깔끔 (필드 따로 필요 없음)
- C# 프로퍼티 문법 그대로 유지 가능
- 관리할 멤버 수가 줄어듦
❌ 단점
- [field: SerializeField] 문법을 알아야 이해할 수 있음
- Unity 튜토리얼/팀 코드 컨벤션에서는 잘 안 보일 수 있음
5. Unity에서 클래스 변수가 “자동 초기화된 것처럼 보이는 이유”
- 순수 C# 환경이라면 new를 하지 않은 클래스 필드는 null 상태가 맞다.
- 하지만 Unity는 [Serializable] 클래스나 [SerializeField] 참조형 필드를 발견하면,
👉 Inspector에 표시할 수 있도록 직렬화된 “빈 인스턴스”를 자동으로 생성한다.
- 예시:
[Serializable]
public class PlayerAnimationData
{
[SerializeField] private string groundParameterName = "@Ground";
public int GroundParameterHash { get; private set; }
// Player의 Awake에서 호출 예정
public void Initialize()
{
GroundParameterHash = Animator.StringToHash(groundParameterName);
}
}
public class Player : MonoBehaviour
{
[field: Header("Animations")]
[field: SerializeField] public PlayerAnimationData AnimationData { get; private set; }
private void Awake()
{
AnimationData.Initialize(); // new로 할당 안했는데, 어떻게...?
}
}
- AnimationData는 코드 상에서 new를 하지 않았는데도 Inspector에서 값이 들어있음
- 이유: Unity 직렬화 시스템이 백킹 필드에 대한 인스턴스를 자동 생성했기 때문
👉 그래서 Awake()에서 곧바로 AnimationData.Initialize()를 호출해도 NullReference가 발생하지 않는다.
- 단, Inspector에 아예 값이 비워져 있으면 null이 될 수도 있으므로,
안전하게 하려면 new PlayerAnimationData(); 같이 직접 초기화를 해주거나 null 체크를 추가하는 게 더 확실하다.
6. 최종 정리
- Unity는 필드만 직렬화 가능
- 프로퍼티를 Inspector에 보이려면 [field: SerializeField]로 백킹 필드 직렬화를 지정해야 한다.
- “자동 초기화된 것처럼 보이는 현상”은 Unity 직렬화 시스템이 Inspector에 표시하기 위해 내부적으로 인스턴스를 생성해주기 때문
- 따라서 동작 자체는 필드 방식과 [field: SerializeField] 방식이 동일하며, 차이는 코드 스타일과 직렬화 대상(명시적 필드 vs 자동 백킹 필드)에 있다.
👉 요약:
- 결과는 똑같이 Inspector에 값이 보이고 저장/복원됨
- 필드 방식: 전통적이고 직관적
- [field: SerializeField] 방식: 깔끔하고 C#스러움
- Unity가 자동 초기화해주는 건 C# 규칙이 아니라 Unity 직렬화 시스템의 동작 때문
'Programming > Unity 정보, 기능' 카테고리의 다른 글
| Unity 간단 기능 정리 모음 #7 (3) | 2025.08.14 |
|---|---|
| Unity 기능 - Rigidbody.AddForce() 정리, ForceMode 별 차이와 활용법 (2) | 2025.07.28 |
| Unity 간단 기능 정리 모음 #6 (2) | 2025.07.28 |
| Unity 기능 - FindObjectOfType 호출 시점 (0) | 2025.07.23 |
| Unity 기능 - transform.Find() 사용 이유와 구조 이해 (3) | 2025.07.23 |
