Version: 2023.1
언어: 한국어
실행 시 에디터 스크립트 코드 실행
스크립트 컴파일

스크립트 직렬화

직렬화는 데이터 구조 또는 게임 오브젝트 상태를 Unity가 보관하고 나중에 다시 복구할 수 있는 포맷으로 변환하는 자동 프로세스입니다.

Unity 프로젝트에서 데이터를 정리하는 방식은 Unity가 해당 데이터를 직렬화하는 방식에 영향을 미치므로 프로젝트 성능에 상당한 영향을 미칠 수 있습니다. 이 페이지에서는 Unity의 직렬화와 직렬화를 위해 프로젝트를 최적화하는 방법에 대한 개요를 살펴봅니다.

이 문서는 다음의 항목을 다룹니다.

직렬화 규칙

Unity의 시리얼라이저는 런타임 시 효율적으로 작동하도록 특별히 고안되었습니다. 이로 인해 Unity의 직렬화는 다른 프로그래밍 환경에서의 직렬화와 다르게 동작합니다. Unity의 시리얼라이저는 해당 프로퍼티가 아닌 C# 클래스의 필드에서 직접 작동하므로 필드를 직렬화하려면 반드시 따라야하는 규칙이 있습니다. 다음 섹션에서는 Unity의 필드 직렬화를 사용하는 방법에 대한 개요를 서술합니다.

필드 직렬화를 사용하려면 필드에 대해 다음 사항을 준수해야 합니다.

  • 공용이거나 SerializeField 속성이 있어야 함
  • 정적이 아님
  • 상수가 아님
  • 읽기 전용이 아님
  • 다음과 같이 직렬화 가능한 필드 타입이 있어야 함
    • 기본 데이터 형식(int, float, double, bool, string 등)
    • 열거형 타입(32 바이트 이하)
    • 고정 크기 버퍼
    • Unity 빌트인 타입(예: Vector2, Vector3, Rect, Matrix4x4, Color, AnimationCurve)
    • Serializable 속성이 있는 커스텀 구조체
    • UnityEngine.Object에서 파생된 오브젝트에 대한 레퍼런스
    • Serializable 속성이 있는 커스텀 클래스(커스텀 클래스 직렬화 참조)
    • 위에서 언급한 필드 타입의 배열
    • 위에서 언급한 필드 타입의 List<T>

참고: Unity는 멀티 레벨 타입(다차원 배열, 가변 배열, 딕셔너리, 중첩된 컨테이너 타입)의 직렬화를 지원합니다. 이러한 타입을 직렬화하고자 하는 경우 다음의 두 가지 옵션을 선택할 수 있습니다.

커스텀 클래스 직렬화

Unity가 커스텀 클래스를 직렬화하려면 클래스가 다음 사항을 준수하는지 확인해야 합니다.

UnityEngine.Object 파생 클래스의 인스턴스를 필드에 할당하고 Unity가 해당 필드를 저장하면 Unity는 필드를 해당 인스턴스에 대한 레퍼런스로 직렬화합니다. Unity는 인스턴스를 개별적으로 직렬화하므로 여러 필드가 인스턴스에 할당될 때 중복되지 않습니다. 하지만 UnityEngine.Object에서 파생되지 않은 커스텀 클래스의 경우 Unity는 커스텀 클래스를 참조하는 MonoBehaviour 또는 ScriptableObject의 직렬화된 데이터에 인스턴스 상태를 직접 포함합니다. 이 작업은 인라인[SerializeReference]로 하는 두 가지 방식이 있습니다.

  • 인라인 직렬화: 기본적으로 Unity는 클래스를 참조하는 필드에 [SerializeReference]를 지정하지 않으면 값을 사용하여 커스텀 클래스 인라인을 직렬화합니다. 즉 일부 서로 다른 필드에 커스텀 클래스 인스턴스에 대한 레퍼런스를 보관하는 경우 필드가 직렬화되면 별개의 오브젝트가 됩니다. 그러면 Unity가 필드를 역직렬화할 때 동일한 데이터를 가진 서로 다른 오브젝트가 포함됩니다.
  • [SerializeReference] 직렬화: [SerializeReference]를 지정하면 Unity는 관리되는 레퍼런스로 오브젝트를 구축합니다. 호스트 오브젝트는 직렬화된 해당 데이터에 직접 오브젝트를 보관하지만 전용 레지스트리 섹션에는 보관하지 않습니다.

[SerializeReference]는 일부 오버헤드를 추가하지만 다음 경우를 지원합니다.

  • 필드는 null일 수 있습니다. 인라인 직렬화는 null을 나타낼 수 없지만 null을 할당되지 않은 필드가 있는 인라인 오브젝트로 교체합니다.
  • 동일한 오브젝트에 대한 여러 레퍼런스입니다. [SerializeReference]를 사용하지 않고 몇몇 다른 필드에 있는 커스텀 클래스 인스턴스에 대해 레퍼런스를 보관하면 직렬화 시 별개의 오브젝트가 됩니다.
  • 그래프와 순환 데이터(예: 자신에 대한 레퍼런스가 있는 오브젝트)입니다. 인라인 클래스 직렬화는 null 또는 공유된 레퍼런스를 지원하지 않으므로 데이터의 주기가 인스펙터의 이상한 동작이나 콘솔 오류 또는 무한 루프와 같은 예기치 못한 결과를 초래할 수 있습니다.
  • 다형성입니다. 부모 클래스에서 파생된 클래스를 생성하여 부모 클래스를 타입으로 사용하는 필드에 할당하면 [SerializeReference] 없이 Unity는 부모 클래스에 속한 필드만 직렬화합니다. Unity가 클래스 인스턴스를 역직렬화하면 파생된 클래스가 아닌 부모 클래스를 인스턴스화합니다.
  • 오브젝트의 배열 포지션을 하드 코딩하거나 전체 배열을 검색하지 않고 데이터 구조에 특정 오브젝트를 가리키는 안정적인 식별자가 필요한 경우입니다. SerializationUtility.SetManagedReferenceIdForObject를 참조하십시오.

참고: 인라인 직렬화는 더욱 효과적이며 [SerializeReference]가 지원하는 기능 중 하나가 특별히 필요한 경우가 아니라면 인라인 직렬화를 사용해야 합니다. [SerializeReference]를 사용하는 방법에 대한 자세한 내용은 SerializeReference 문서를 참조하십시오.

프로퍼티 직렬화

Unity는 다음의 경우 외에는 일반적으로 프로퍼티를 직렬화하지 않습니다.

  • 프로퍼티에 명시적 지원 필드가 있는 경우 Unity는 일반 직렬화 규칙에 따라 이를 직렬화합니다. 예는 다음과 같습니다.
public int MyInt
{
get => m_backing;
private set => m_backing = value;
}
[SerializeField] private int m_backing;
  • Unity는 핫 리로드 동안에만 자동으로 생성된 필드를 사용하여 프로퍼티를 직렬화합니다.

    public int MyInt { get; set; }

    Unity가 자동 생성된 필드로 프로퍼티를 직렬화하지 않길 원하는 경우 [field: NonSerialized] 속성을 사용합니다.

커스텀 직렬화

간혹 Unity의 시리얼라이저가 지원하지 않는 항목(예: C# 딕셔너리)을 직렬화하려는 경우가 있습니다. 가장 좋은 방법은 클래스에서 ISerializationCallbackReceiver를 구현하는 것입니다. 이를 통해 직렬화와 역직렬화 시 키 포인트에서 호출되는 콜백을 다음과 같이 구현할 수 있습니다.

  1. 오브젝트가 직렬화될 때 Unity는 OnBeforeSerialize() 콜백을 호출합니다. 이 콜백에서 Unity가 이해하는 항목으로 데이터를 변환할 수 있습니다. 예를 들어 C# 딕셔너리를 직렬화하려면 딕셔너리에서 키 배열과 값 배열로 데이터를 복사합니다.
  2. OnBeforeSerialize() 콜백이 종료되면 Unity는 배열을 직렬화합니다.
  3. 나중에 오브젝트가 역직렬화될 때 Unity는 OnAfterDeserialize() 콜백을 호출합니다. 이 콜백 안에는 메모리의 오브젝트에 편리한 폼으로 데이터를 다시 변환합니다. 예를 들어 키 배열과 값 배열을 사용하여 C# 딕셔너리를 다시 채웁니다.

Unity가 직렬화 사용하는 방법

저장 및 로드

Unity는 직렬화를 사용하여 , 에셋, 에셋 번들을 기기 메모리에 저장하거나 로드합니다. 여기에는 [MonoBehaviour](../ScriptReference/ MonoBehaviour.html) 컴포넌트와 ScriptableObjects와 같은 자체 스크립팅 API 오브젝트에 저장된 데이터가 포함됩니다.

Unity 에디터의 여러 기능은 코어 직렬화 시스템을 기반으로 제작됩니다. 직렬화와 관련해서는 인스펙터 창과 핫 리로드에 대해 알아두는 것이 중요합니다.

인스펙터 창

인스펙터 창은 검사한 오브젝트의 직렬화된 필드 값을 보여줍니다. 인스펙터에서 값을 변경하면 인스펙터는 직렬화된 데이터를 업데이트하고 검사한 오브젝트를 업데이트하는 역직렬화를 트리거합니다.

빌트인 Unity 오브젝트와 스크립팅 오브젝트(예: MonoBehaviour 파생 클래스) 모두 동일하게 적용됩니다.

Unity는 인스펙터 창에서 값을 보거나 변경할 때 C# 프로퍼티 게터와 세터를 호출하지 않습니다. 대신 Unity는 직렬화된 지원 필드를 직접 액세스합니다.

핫 리로드

핫 리로드에서는 에디터를 열어 스크립트 동작을 바로 적용하는 동안 스크립트를 생성하거나 편집합니다. 변경 사항을 적용하기 위해 에디터를 다시 시작하지 않아도 됩니다.

스크립트를 변경해서 저장하면 Unity는 로드한 모든 스크립트 데이터를 핫 리로드합니다. Unity는 로드한 모든 스크립트에 직렬화 가능한 변수를 저장한 다음 해당 스크립트를 리로드하고 직렬화된 변수를 복원합니다. 핫 리로드는 직렬화할 수 없는 모든 데이터를 폐기하므로 나중에는 데이터에 액세스할 수 없습니다.

이는 모든 에디터 창과 프로젝트의 모든 MonoBehaviours에 영향을 끼칩니다. Unity의 다른 직렬화와는 달리 프라이빗 필드는 ‘SerializeField’ 속성이 없더라도 리로드할 때 기본적으로 직렬화됩니다.

Unity가 스크립트를 리로드하면 Unity는 다음을 수행합니다.

  1. Unity는 로드된 모든 스크립트의 모든 변수를 직렬화하고 저장합니다.
  2. Unity는 원래의 사전 직렬화 값에 다음과 같이 변수를 복원합니다.
    • Unity는 직렬화 요구 사항을 충족하는 private 변수를 비롯한 모든 변수를 복원하고, 변수에 [SerializeField] 속성이 없는 경우에도 복원합니다. 하지만 예를 들어 스크립트에서 레퍼런스를 리로드한 후 레퍼런스가 null이 되기를 원하는 경우처럼 Unity가 private 변수를 복원하지 않도록 해야 하는 경우가 있습니다. 이런 경우에는 [field: NonSerialized] 속성을 사용합니다.
    • Unity는 정적 변수를 복원하지 않으므로 스크립트를 리로드한 후에도 유지해야 하는 상태에는 정적 변수를 사용하지 마십시오. 리로드 프로세스가 이를 폐기합니다.

프리팹

프리팹은 하나 이상의 게임 오브젝트컴포넌트가 직렬화된 데이터입니다. 프리팹 인스턴스에는 프리팹 소스와 수정 리스트에 대한 레퍼런스가 포함되어 있습니다. 수정은 프리팹 소스에서 특정 프리팹 인스턴스를 생성하기 위해 Unity가 수행하는 작업입니다.

프리팹 인스턴스는 Unity 에디터에서 프로젝트를 편집할 때만 존재합니다. Unity 에디터는 게임 오브젝트를 두 세트의 직렬화 데이터(프리팹 소스와 프리팹 인스턴스의 수정 사항)로부터 인스턴스화합니다.

인스턴스화

프리팹이나 게임 오브젝트처럼 일반적으로 씬에 존재하는 항목에 Instantiate를 호출하면 Unity는 다음을 수행합니다.

  1. Unity가 이를 직렬화합니다. 이는 런타임 시 에디터에서 발생합니다. Unity는 UnityEngine.Object에서 파생한 모든 항목을 직렬화할 수 있습니다.
  2. Unity는 새로운 게임 오브젝트를 생성하고 새로운 게임 오브젝트에 데이터를 역직렬화합니다.
  3. Unity는 다른 배리언트에서 동일한 직렬화 코드를 실행해 어떤 UnityEngine.Objects가 레퍼런스된 것인지 보고합니다. 모든 레퍼런스된 UnityEngine.Objects를 체크하여 Unity가 인스턴스화하는 데이터의 일부인지 확인합니다. 레퍼런스가 텍스처 같은 외부 요소를 가리키면 Unity는 해당 레퍼런스를 그대로 유지합니다. 레퍼런스가 자식 게임 오브젝트 같은 내부 요소를 가리키면 Unity는 해당 복사본 레퍼런스로 패치합니다.

사용되지 않는 에셋 언로드

EditorUtility.UnloadUnusedAssetsImmediate는 네이티브 Unity 가비지 컬렉터로 일반적인 C#의 가비지 컬렉터와는 목적이 다릅니다. 씬을 로드한 후 텍스처처럼 더 이상 참조하지 않는 오브젝트를 확인하고 안전하게 언로드합니다. 네이티브 Unity 가비지 컬렉터는 어떤 오브젝트가 모든 레퍼런스를 외부 UnityEngine.Objects로 보고하는지 변형에서 시리얼라이저를 실행합니다. 이렇게 해서 한 씬에서는 텍스처를 사용하고 다음 씬에서 가비지 컬렉터가 이를 언로드합니다.

에디터와 런타임 직렬화의 차이

대부분의 직렬화는 에디터에서 일어나는 반면 역직렬화는 런타임에 중점을 둡니다. Unity는 에디터에서만 일부 기능을 직렬화하고 에디터에서 런타임 시 다른 기능을 직렬화할 수 있습니다.

기능 Editor Runtime
Assets in Binary Format 읽기/쓰기 지원됨 읽기 지원됨
Assets in YAML format 읽기/쓰기 지원됨 미지원
Saving scenes, prefabs and other assets 플레이 모드가 아닌 경우 지원됨 미지원
Serialization of individual objects with JsonUtility JsonUtility로 읽기/쓰기 지원

EditorJsonUtility로 오브젝트 추가 타입 지원
JsonUtility로 읽기/쓰기 지원
SerializeReference 지원됨 지원됨
ISerializationCallbackReceiver 지원됨 지원됨
FormerlySerializedAs 지원됨 미지원

오브젝트에는 다음과 같이 UNITY_EDITOR 스크립팅 심볼 안에 필드를 선언하는 경우처럼 에디터만 직렬화할 수 있는 추가 필드가 있을 수 있습니다.

public class SerializeRules : MonoBehaviour
{
# if UNITY_EDITOR
public int m_intEditorOnly;
# endif
}

위의 예시에서 m_intEditorOnly 필드는 에디터에서만 직렬화되며 빌드에 포함되지 않습니다. 이렇게 하면 빌드에서 에디터에만 필요한 데이터를 생략하여 메모리를 절약할 수 있습니다. 해당 필드를 사용하는 코드는 또한 클래스가 빌드 시 컴파일될 수 있도록 #if UNITY_EDITOR 블록처럼 조건부로 컴파일되어야 할 수 있습니다.

에디터는 Unity가 런타임 시에만 직렬화하는 필드가 있는 오브젝트를 지원하지 않습니다(예: UNITY_STANDALONE 지시문 내에 필드를 선언하는 경우).

스크립트 직렬화 오류

스크립트 직렬화가 오류를 일으킬 수 있습니다. 아래에서 이러한 문제에 대한 해결 방법을 볼 수 있습니다.

“Find isn’t allowed to be called from a MonoBehaviour constructor (or instance field initializer), call in Awake or Start instead.”

MonoBehaviour 생성자 또는 필드 이니셜라이저에서 GameObject.Find와 같은 스크립팅 API를 호출하면 이러한 오류가 발생합니다.

이러한 문제를 해결하려면 생성자 대신 MonoBehaviour.Start에서 스크립팅 API를 호출하면 됩니다.

“Find isn’t allowed to be called during serialization, call it from Awake or Start instead.”

System.Serializable 표시된 클래스의 생성자에서 GameObject.Find 같은 스크립팅 API를 호출하면 이런 오류가 발생합니다.

직렬화된 오브젝트의 생성자에서 스크립팅 API를 호출하지 않도록 코드를 편집해 이 문제를 해결할 수 있습니다.

스레드 세이프 Unity 스크립팅 API

위의 제한은 대부분의 스크립팅 API에 영향을 줍니다. 다음과 같은 일부 Unity 스크립팅 API만 제외되며 어디서나 호출할 수 있습니다.

직렬화 중 오류 가능성을 낮추기 위해서는 대안책이 없는 경우 외에는 독립적인 구조이면서 Unity 자체에서 데이터를 가져오거나 설정할 필요가 없는 API 메서드만 호출합니다.

직렬화 베스트 프랙티스

데이터를 정리하면 Unity의 직렬화를 최적으로 사용할 수 있습니다.

  • Unity가 최대한 작은 데이터 세트를 직렬화하도록 하는 것을 목표로 합니다. 이렇게 하는 목적은 컴퓨터 하드 드라이브 공간을 절약하기 위해서가 아니라 이전 프로젝트 버전과 역호환성을 유지하기 위해서입니다. 큰 직렬화된 데이터 세트를 작업에 사용할 때에는 개발이 많이 진행될수록 역호환성을 유지하기가 점점 어려워질 수 있습니다.
  • Unity가 중복 데이터나 캐시된 데이터를 직렬화하지 않도록 하십시오. 이런 데이터가 직렬화되면 역호환성에 중대한 문제가 발생합니다. 즉 데이터가 동기화되지 않을 가능성이 커서 오류 발생 위험이 높아집니다.
  • 다른 클래스를 레퍼런스하는 중첩된 재귀적 구조는 사용하지 마십시오. 직렬화된 구조의 레이아웃은 데이터에 관계없이 항상 같아야 하고, 스크립트에서 무엇이 노출되느냐에 따라서만 달라야 합니다. UnityEngine.Object에서 파생된 클래스를 통해서만 다른 클래스를 레퍼런스할 수 있습니다. 이런 클래스는 별개로, 서로만 레퍼런스하고 콘텐츠를 포함하지 않습니다.

직렬화

실행 시 에디터 스크립트 코드 실행
스크립트 컴파일