본문 바로가기

📌 Unity 직렬화와 [field: SerializeField] 정리

@코야딩구2025. 8. 25. 19:18

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 직렬화 시스템의 동작 때문

목차