직렬화는 데이터 구조 또는 게임 오브젝트 상태를 Unity가 보관하고 나중에 다시 복구할 수 있는 포맷으로 변환하는 자동 프로세스입니다.
Unity 프로젝트에서 데이터를 정리하는 방식은 Unity가 해당 데이터를 직렬화하는 방식에 영향을 미치므로 프로젝트 성능에 상당한 영향을 미칠 수 있습니다. 이 페이지에서는 Unity의 직렬화와 직렬화를 위해 프로젝트를 최적화하는 방법에 대한 개요를 살펴봅니다.
이 문서는 다음의 항목을 다룹니다.
Unity의 시리얼라이저는 런타임 시 효율적으로 작동하도록 특별히 고안되었습니다. 이로 인해 Unity의 직렬화는 다른 프로그래밍 환경에서의 직렬화와 다르게 동작합니다. Unity의 시리얼라이저는 해당 프로퍼티가 아닌 C# 클래스의 필드에서 직접 작동하므로 필드를 직렬화하려면 반드시 따라야하는 규칙이 있습니다. 다음 섹션에서는 Unity의 필드 직렬화를 사용하는 방법에 대한 개요를 서술합니다.
필드 직렬화를 사용하려면 필드에 대해 다음 사항을 준수해야 합니다.
List<T>
참고: Unity는 멀티 레벨 타입(다차원 배열, 가변 배열, 딕셔너리, 중첩된 컨테이너 타입)의 직렬화를 지원합니다. 이러한 타입을 직렬화하고자 하는 경우 다음의 두 가지 옵션을 선택할 수 있습니다.
Unity가 커스텀 클래스를 직렬화하려면 클래스가 다음 사항을 준수하는지 확인해야 합니다.
UnityEngine.Object
파생 클래스의 인스턴스를 필드에 할당하고 Unity가 해당 필드를 저장하면 Unity는 필드를 해당 인스턴스에 대한 레퍼런스로 직렬화합니다. Unity는 인스턴스를 개별적으로 직렬화하므로 여러 필드가 인스턴스에 할당될 때 중복되지 않습니다. 하지만 UnityEngine.Object
에서 파생되지 않은 커스텀 클래스의 경우 Unity는 커스텀 클래스를 참조하는 MonoBehaviour 또는 ScriptableObject의 직렬화된 데이터에 인스턴스 상태를 직접 포함합니다. 이 작업은 인라인과 [SerializeReference]
로 하는 두 가지 방식이 있습니다.
[SerializeReference]
를 지정하지 않으면 값을 사용하여 커스텀 클래스 인라인을 직렬화합니다. 즉 일부 서로 다른 필드에 커스텀 클래스 인스턴스에 대한 레퍼런스를 보관하는 경우 필드가 직렬화되면 별개의 오브젝트가 됩니다. 그러면 Unity가 필드를 역직렬화할 때 동일한 데이터를 가진 서로 다른 오브젝트가 포함됩니다.[SerializeReference]
직렬화: [SerializeReference]
를 지정하면 Unity는 관리되는 레퍼런스로 오브젝트를 구축합니다. 호스트 오브젝트는 직렬화된 해당 데이터에 직접 오브젝트를 보관하지만 전용 레지스트리 섹션에는 보관하지 않습니다.[SerializeReference]
는 일부 오버헤드를 추가하지만 다음 경우를 지원합니다.
[SerializeReference]
를 사용하지 않고 몇몇 다른 필드에 있는 커스텀 클래스 인스턴스에 대해 레퍼런스를 보관하면 직렬화 시 별개의 오브젝트가 됩니다.[SerializeReference]
없이 Unity는 부모 클래스에 속한 필드만 직렬화합니다. Unity가 클래스 인스턴스를 역직렬화하면 파생된 클래스가 아닌 부모 클래스를 인스턴스화합니다.참고: 인라인 직렬화는 더욱 효과적이며 [SerializeReference]
가 지원하는 기능 중 하나가 특별히 필요한 경우가 아니라면 인라인 직렬화를 사용해야 합니다. [SerializeReference]
를 사용하는 방법에 대한 자세한 내용은 SerializeReference 문서를 참조하십시오.
Unity는 다음의 경우 외에는 일반적으로 프로퍼티를 직렬화하지 않습니다.
public int MyInt
{
get => m_backing;
private set => m_backing = value;
}
[SerializeField] private int m_backing;
public int MyInt { get; set; }
간혹 Unity의 시리얼라이저가 지원하지 않는 항목(예: C# 딕셔너리)을 직렬화하려는 경우가 있습니다. 가장 좋은 방법은 클래스에서 ISerializationCallbackReceiver를 구현하는 것입니다. 이를 통해 직렬화와 역직렬화 시 키 포인트에서 호출되는 콜백을 다음과 같이 구현할 수 있습니다.
OnBeforeSerialize()
콜백을 호출합니다. 이 콜백에서 Unity가 이해하는 항목으로 데이터를 변환할 수 있습니다. 예를 들어 C# 딕셔너리를 직렬화하려면 딕셔너리에서 키 배열과 값 배열로 데이터를 복사합니다.OnBeforeSerialize()
콜백이 종료되면 Unity는 배열을 직렬화합니다.OnAfterDeserialize()
콜백을 호출합니다. 이 콜백 안에는 메모리의 오브젝트에 편리한 폼으로 데이터를 다시 변환합니다. 예를 들어 키 배열과 값 배열을 사용하여 C# 딕셔너리를 다시 채웁니다.Unity는 직렬화를 사용하여 씬, 에셋, 에셋 번들을 기기 메모리에 저장하거나 로드합니다. 여기에는 [MonoBehaviour](../ScriptReference/ MonoBehaviour.html) 컴포넌트와 ScriptableObjects와 같은 자체 스크립팅 API 오브젝트에 저장된 데이터가 포함됩니다.
Unity 에디터의 여러 기능은 코어 직렬화 시스템을 기반으로 제작됩니다. 직렬화와 관련해서는 인스펙터 창과 핫 리로드에 대해 알아두는 것이 중요합니다.
인스펙터 창은 검사한 오브젝트의 직렬화된 필드 값을 보여줍니다. 인스펙터에서 값을 변경하면 인스펙터는 직렬화된 데이터를 업데이트하고 검사한 오브젝트를 업데이트하는 역직렬화를 트리거합니다.
빌트인 Unity 오브젝트와 스크립팅 오브젝트(예: MonoBehaviour 파생 클래스) 모두 동일하게 적용됩니다.
Unity는 인스펙터 창에서 값을 보거나 변경할 때 C# 프로퍼티 게터와 세터를 호출하지 않습니다. 대신 Unity는 직렬화된 지원 필드를 직접 액세스합니다.
핫 리로드에서는 에디터를 열어 스크립트 동작을 바로 적용하는 동안 스크립트를 생성하거나 편집합니다. 변경 사항을 적용하기 위해 에디터를 다시 시작하지 않아도 됩니다.
스크립트를 변경해서 저장하면 Unity는 로드한 모든 스크립트 데이터를 핫 리로드합니다. Unity는 로드한 모든 스크립트에 직렬화 가능한 변수를 저장한 다음 해당 스크립트를 리로드하고 직렬화된 변수를 복원합니다. 핫 리로드는 직렬화할 수 없는 모든 데이터를 폐기하므로 나중에는 데이터에 액세스할 수 없습니다.
이는 모든 에디터 창과 프로젝트의 모든 MonoBehaviours에 영향을 끼칩니다. Unity의 다른 직렬화와는 달리 프라이빗 필드는 ‘SerializeField’ 속성이 없더라도 리로드할 때 기본적으로 직렬화됩니다.
Unity가 스크립트를 리로드하면 Unity는 다음을 수행합니다.
[SerializeField]
속성이 없는 경우에도 복원합니다. 하지만 예를 들어 스크립트에서 레퍼런스를 리로드한 후 레퍼런스가 null이 되기를 원하는 경우처럼 Unity가 private 변수를 복원하지 않도록 해야 하는 경우가 있습니다. 이런 경우에는 [field: NonSerialized]
속성을 사용합니다.프리팹은 하나 이상의 게임 오브젝트 및 컴포넌트가 직렬화된 데이터입니다. 프리팹 인스턴스에는 프리팹 소스와 수정 리스트에 대한 레퍼런스가 포함되어 있습니다. 수정은 프리팹 소스에서 특정 프리팹 인스턴스를 생성하기 위해 Unity가 수행하는 작업입니다.
프리팹 인스턴스는 Unity 에디터에서 프로젝트를 편집할 때만 존재합니다. Unity 에디터는 게임 오브젝트를 두 세트의 직렬화 데이터(프리팹 소스와 프리팹 인스턴스의 수정 사항)로부터 인스턴스화합니다.
프리팹이나 게임 오브젝트처럼 일반적으로 씬에 존재하는 항목에 Instantiate
를 호출하면 Unity는 다음을 수행합니다.
UnityEngine.Object
에서 파생한 모든 항목을 직렬화할 수 있습니다.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 지시문 내에 필드를 선언하는 경우).
스크립트 직렬화가 오류를 일으킬 수 있습니다. 아래에서 이러한 문제에 대한 해결 방법을 볼 수 있습니다.
MonoBehaviour 생성자 또는 필드 이니셜라이저에서 GameObject.Find
와 같은 스크립팅 API를 호출하면 이러한 오류가 발생합니다.
이러한 문제를 해결하려면 생성자 대신 MonoBehaviour.Start
에서 스크립팅 API를 호출하면 됩니다.
System.Serializable
표시된 클래스의 생성자에서 GameObject.Find 같은 스크립팅 API를 호출하면 이런 오류가 발생합니다.
직렬화된 오브젝트의 생성자에서 스크립팅 API를 호출하지 않도록 코드를 편집해 이 문제를 해결할 수 있습니다.
위의 제한은 대부분의 스크립팅 API에 영향을 줍니다. 다음과 같은 일부 Unity 스크립팅 API만 제외되며 어디서나 호출할 수 있습니다.
Debug.Log
Mathf
함수직렬화 중 오류 가능성을 낮추기 위해서는 대안책이 없는 경우 외에는 독립적인 구조이면서 Unity 자체에서 데이터를 가져오거나 설정할 필요가 없는 API 메서드만 호출합니다.
데이터를 정리하면 Unity의 직렬화를 최적으로 사용할 수 있습니다.
UnityEngine.Object
에서 파생된 클래스를 통해서만 다른 클래스를 레퍼런스할 수 있습니다. 이런 클래스는 별개로, 서로만 레퍼런스하고 콘텐츠를 포함하지 않습니다.Serialization