Version: 2018.4
언어: 한국어
스크립팅 제약
빌트인 직렬화

스크립트 직렬화

직렬화는 데이터 구조나 오브젝트 상태를 Unity 에디터가 저장하고 나중에 재구성할 수 있는 포맷으로 자동으로 변환하는 프로세스를 말합니다. Unity 에디터에서는 저장 및 로딩, 인스펙터 창, 인스턴스화, 프리팹과 같은 일부 내장 기능에 직렬화가 사용됩니다. 직렬화가 사용되는 모든 기능에 대한 자세한 배경 정보는 내장 직렬화 사용 문서를 참조하십시오.

Unity 프로젝트에서 데이터를 정리하는 방법은 Unity 에디터에서 해당 데이터를 직렬화하는 방법에 영향을 미치고 프로젝트 성능에 큰 영향을 미칠 수 있습니다. 아래에서는 Unity 에디터의 직렬화에 대해 설명하고 직렬화를 위해 프로젝트를 최적화하는 방법을 안내합니다.

직렬화 오류, 커스텀 직렬화, 빌트인 직렬화 문서도 참조하십시오.

핫 리로드 이해

핫 리로드

핫 리로드는 에디터가 열려 있는 상태에서 스크립트를 생성하거나 편집한 후 스크립트 동작을 즉시 적용하는 프로세스를 말합니다. 변경 사항을 적용하기 위해 게임이나 에디터를 다시 시작할 필요가 없습니다.

스크립트를 변경해서 저장하면 Unity 에디터가 현재 로드되어 있는 스크립트 데이터를 모두 핫 리로드합니다. 우선 로드된 모든 스크립트에서 직렬화 가능한 변수를 저장하고 스크립트를 로드한 후 변수를 복원합니다. 핫 리로드 후에는 직렬화할 수 없는 모든 데이터를 잃게 됩니다.

저장 및 로드

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

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

인스펙터 창

인스펙터 창에서 게임 오브젝트의 컴포넌트 필드 값을 확인하거나 변경하면 Unity 에디터가 해당 데이터를 직렬화한 후 인스펙터 창에 표시합니다. 인스펙터 창은 필드 값을 표시할 때 Unity 스크립팅 API와 통신하지 않습니다.

스크립트에 프로퍼티가 사용되었을 때 인스펙터 창에서 값을 확인하거나 변경하면 Unity 에디터가 인스펙터 창 필드를 직접 직렬화하므로 프로퍼티 게터(getter)와 세터(setter)가 호출되지 않습니다. 다시 말해, 인스펙터 창에 보이는 필드 값은 스크립트 프로퍼티를 나타내지만 인스펙터 창에서 값을 변경하더라도 스크립트에서 프로퍼티 게터(getter)와 세터(setter)가 호출되지 않습니다.

직렬화 규칙

Unity 에디터의 시리얼라이저는 실시간 게임 환경에서 실행되므로 성능에 상당한 영향을 미칩니다. 따라서 Unity 에디터의 직렬화는 기타 프로그래밍 환경의 직렬화와 다르게 동작합니다. 아래에서 Unity 에디터에서 직렬화를 사용하는 방법에 대한 몇 가지 팁을 살펴보십시오.

스크립트의 필드가 직렬화되도록 하는 방법

필드가 다음과 같아야 합니다.

직렬화할 수 있는 단순 필드 타입

  • Serializable 속성이 있는 비추상, 비일반 커스텀 클래스
    (아래의 커스텀 클래스가 직렬화될 수 있도록 하는 방법 참조)

  • Serializable 속성이 있는 커스텀 구조체

  • UnityEngine.Object에서 파생된 오브젝트에 대한 레퍼런스

  • 기본 데이터 형식(int, float, double, bool, string 등)

  • 열거형 타입

  • 특정 Unity 내장 타입: Vector2, Vector3, Vector4, Rect, Quaternion, Matrix4x4, Color, Color32, LayerMask, AnimationCurve, Gradient, RectOffset, GUIStyle

직렬화할 수 있는 컨테이너 필드 타입

  • 직렬화할 수 있는 단순 필드 타입의 배열

  • 직렬화할 수 있는 단순 필드 타입의 List<T>

참고: Unity 에디터는 다단계 타입(다차원 배열, 가변 배열, 중첩 컨테이터 타입)의 직렬화를 지원하지 않습니다.
이런 타입을 직렬화하는 방법에는 두 가지 방법이 있습니다. 중첩 타입을 클래스 또는 구조체에 래핑하거나, ISerializationCallbackReceiver 직렬화 콜백을 사용하여 커스텀 직렬화를 수행해야 합니다. 자세한 내용은 커스텀 직렬화 문서를 참조하십시오.

커스텀 클래스가 직렬화될 수 있도록 하는 방법

필드가 다음과 같아야 합니다.

  • Serializable 속성이 있어야 함

  • 추상적이지 않아야 함

  • 정적이지 않아야 함

  • 일반적이지 않아야 함. 단, 일반 클래스에서 상속 받을 수는 있음

커스텀 클래스 또는 구조체의 필드가 직렬화되도록 하려면 위에 있는 스크립트의 필드가 직렬화되도록 하는 방법을 참조하십시오.

어떤 때에 시리얼라이저가 예상과 다르게 동작할 수 있습니까?

커스텀 클래스가 구조체처럼 동작할 때

UnityEngine.Object에서 파생되지 않은 커스텀 클래스는 Unity 에디터에서 구조체가 직렬화되는 방법과 비슷하게 값에 따라 인라인에서 직렬화됩니다. 커스텀 클래스의 인스턴스에 대한 레퍼런스를 여러 다른 필드에 저장하면 직렬화할 때 별도의 오브젝트가 됩니다. 그 후 Unity 에디터가 필드를 역직렬화하면 데이터가 똑같은 여러 개의 개별 오브젝트가 필드에 포함됩니다.

레퍼런스가 있는 복합 오브젝트 그래프를 직렬화할 때에는 Unity 에디터가 오브젝트를 자동으로 직렬화하게 하지 마십시오. 그 대신 ISerializationCallbackReceiver를 사용하여 Unity 에디터가 오브젝트 레퍼런스로부터 여러 오브젝트를 생성하지 못하게 하십시오. 자세한 내용은 ISerializationCallbackReceiver 문서를 참조하십시오.

앞의 내용은 커스텀 클래스에만 해당됩니다. Unity 에디터가 커스텀 클래스를 “인라인”에서 직렬화하는 이유는 클래스의 데이터가 해당 데이터를 사용하는 MonoBehaviour 또는 ScriptableObject의 전체 직렬화 데이터의 일부가 되기 때문입니다. 필드에서 UnityEngine.Object 파생 클래스인 public Camera myCamera 등을 레퍼런스하면 Unity 에디터는 UnityEngine.Object 카메라에 대한 실제 레퍼런스를 직렬화합니다. 스크립트의 인스턴스가 MonoBehaviour 또는 ScriptableObject(모두 UnityEngine.Object에서 파생됨)에서 파생된 경우에도 마찬가지입니다.

커스텀 클래스에서 null을 지원하지 않을 때

다음 스크립트를 사용하는 MonoBehaviour를 역직렬화할 때 할당이 몇 번 이루어지는지 생각해보십시오.

class Test : MonoBehaviour
{
    public Trouble t;
}
[Serializable]
class Trouble
{
   public Trouble t1;
   public Trouble t2;
   public Trouble t3;
}

Test 오브젝트가 한 번 할당될 것이라 예상해도 이상하지 않습니다. Test 오브젝트와 Trouble 오브젝트가 각각 한 번씩 총 두 번 할당될 것이라 예상할 수도 있습니다.

하지만 Unity 에디터에서는 할당이 수천 번 넘게 이루어집니다. 시리얼라이저는 null을 지원하지 않습니다. 오브젝트를 직렬화하는 경우 필드가 null이면 Unity 에디터는 해당 타입의 새 오브젝트를 인스턴스화하고 직렬화합니다. 당연히 이렇게 하면 무한한 주기가 발생할 수 있으므로, 뎁스가 7단계로 제한됩니다. 이 시점에 도달하면 Unity 에디터는 타입이 커스텀 클래스, 구조체, 목록, 배열인 필드의 직렬화를 중지합니다.

다수의 Unity 서브시스템이 직렬화 시스템을 기반으로 제작되므로, Test MonoBehaviour에서 예기치 못한 큰 직렬화 흐름이 발생하면 모든 서브시스템이 필요 이상으로 느리게 작동합니다.

다형성이 지원되지 않을 때

public Animal[] animals가 있는데 여기에 Dog, Cat, Giraffe 인스턴스를 추가하면 직렬화 후에 세 개의 Animal 인스턴스가 생깁니다.

이 제한에 대처하는 방법 중 하나는 이러한 현상이 인라인에서 직렬화되는 커스텀 클래스에만 해당된다는 점을 깨닫는 것입니다. 다른 UnityEngine.Objects에 대한 레퍼런스는 실제 레퍼런스로 직렬화되므로 이러한 경우에는 다형성을 실제로 활용할 수 있습니다. ScriptableObject 파생 클래스나 다른 MonoBehaviour 파생 클래스를 생성한 후 해당 클래스를 레퍼런스하면 됩니다. 하지만 MonoBehaviour 또는 스크립트 가능한 오브젝트를 다른 곳에 저장해야 하고 효율적으로 인라인에서 직렬화하기 어렵다는 단점이 있습니다.

이런 제한이 있는 이유는 오브젝트 데이터스트림의 레이아웃을 미리 알 수 있다는 것이 직렬화 시스템의 핵심적인 기초 중의 하나이기 때문입니다. 레이아웃은 필드 안에 무엇이 저장되느냐에 따라 결정되지 않고 클래스의 필드 타입에 따라 결정됩니다.

직렬화 최적 사용

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

  • Unity 에디터가 최대한 작은 데이터 세트를 직렬화하도록 데이터를 정리하십시오. 이렇게 하는 일차적인 목적은 컴퓨터 하드 드라이브 공간을 절약하기 위해서가 아니라 이전 프로젝트 버전과 역호환성을 유지하기 위해서입니다. 큰 직렬화된 데이터 세트를 작업에 사용할 때에는 개발이 많이 진행될수록 역호환성을 유지하기가 점점 어려워질 수 있습니다.

  • Unity 에디터가 중복 데이터나 캐시된 데이터를 직렬화하지 않도록 데이터를 정리하십시오. 이런 데이터가 직렬화되면 역호환성에 중대한 문제가 발생합니다. 즉, 데이터가 동기화되지 않을 가능성이 커서 오류 발생 위험이 높아집니다.

  • 다른 클래스를 레퍼런스하는 중첩된 재귀적 구조는 사용하지 마십시오. 직렬화된 구조의 레이아웃은 데이터에 관계없이 항상 같아야 하고, 스크립트에서 무엇이 노출되느냐에 따라서만 달라야 합니다. UnityEngine.Object에서 파생된 클래스를 통해서만 다른 오브젝트를 레퍼런스할 수 있습니다. 이런 클래스는 완전히 별개로, 서로만 레퍼런스하고 콘텐츠를 포함하지 않습니다.

에디터 코드를 핫 리로드 가능하게 만들기

스크립트를 다시 로드할 때 Unity 에디터는 모든 로드된 스크립트의 변수를 모두 직렬화하고 저장합니다. Unity 에디터는 스크립트를 다시 로드한 후 스크립트를 직렬화하기 전의 원래 값으로 복원합니다.

스크립트를 다시 로드할 때 Unity는 직렬화 요구 사항을 충족하는 private 변수를 비롯한 모든 변수를 복원하고, 변수에 SerializeField 속성이 없는 경우에도 복원합니다. 하지만 private 변수가 복원되지 않도록 해야 하는 경우가 있습니다. 예를 들어 스크립트에서 레퍼런스를 다시 로드한 후 레퍼런스가 null이 되기를 원할 때에는 NonSerialized 속성을 사용해야 합니다.

Unity 에디터는 정적 변수를 복원하지 않으므로, 스크립트를 다시 로드한 후에도 유지해야 하는 상태에는 정적 변수를 사용하지 마십시오.


• 2017–05–15 편집 리뷰를 거쳐 페이지 수정됨

스크립팅 제약
빌트인 직렬화