Version: 2022.3
언어: 한국어
커스텀 에디터 창 생성
SerializedObject 데이터 바인딩

커스텀 인스펙터 생성

Unity는 MonoBehaviours 및 ScriptableObjects를 위한 기본 인스펙터를 생성하지만, 다음 이유로 커스텀 인스펙터를 작성하는 것이 좋습니다.

  • 스크립트 프로퍼티를 보다 사용자 친화적으로 나타낼 수 있습니다.
  • 프로퍼티를 정리하고 그룹화할 수 있습니다.
  • 사용자의 선택에 따라 UI 섹션을 표시하거나 숨길 수 있습니다.
  • 개별 설정 및 프로퍼티의 의미에 관한 추가 정보를 제공할 수 있습니다.

UI 툴킷을 사용하여 커스텀 인스펙터를 만드는 것은 즉시 모드 GUI(IMGUI)를 사용하는 것과 비슷합니다. 하지만 UI 툴킷은 자동 데이터 바인딩과 자동 실행 취소 지원과 같은 여러 이점이 있습니다. IMGUI는 전적으로 스크립트를 통해 인스펙터 UI를 만드는 반면, UI 툴킷을 사용하면 스크립트를 통해서뿐 아니라 UI 빌더에서 시각적으로 또는 두 가지 방법을 함께 사용하여 UI를 빌드할 수 있습니다.

이 가이드의 최종 소스 코드는 여기 이 페이지 하단에서 확인할 수 있습니다.

이 가이드에서는 MonoBehaviour 클래스를 위한 커스텀 인스펙터를 만들며, 이 과정에서 스크립트와 UXML(UI 빌더를 사용해)를 모두 사용하여 UI를 만듭니다. 또한 커스텀 인스펙터에 커스텀 프로퍼티 드로어도 포함합니다.

선행 조건

이 가이드는 Unity에 익숙하지만 UI 툴킷은 처음 접하는 개발자를 위해 만들어졌습니다. Unity와 C# 스크립팅에 관한 기본적인 이해가 있는 것이 좋습니다.

또한 이 가이드는 다음 컨셉을 참조합니다.

콘텐츠

다루는 주제

이 가이드에서는 다음을 수행해봅니다.

  • 새 MonoBehaviour 생성
  • 커스텀 인스펙터 스크립트 생성
  • 커스텀 인스펙터 내에서 UXML 사용
  • 실행 취소 및 데이터 바인딩
  • 기본 인스펙터 생성
  • 프로퍼티 필드
  • 커스텀 프로퍼티 드로어 생성

새 MonoBehaviour 생성

시작하려면 커스텀 인스펙터를 만들 수 있는 커스텀 클래스, 즉 MonoBehaviour 또는 ScriptableObject를 생성해야 합니다. 이 가이드에서는 모델과 컬러와 같은 프로퍼티가 있는 단순한 자동차를 나타내는 MonoBehaviour` 스크립트를 사용합니다.

Assets/Scripts 안에 새 스크립트 파일 Car.cs를 만들고 다음 코드를 복사하여 넣으십시오.

using UnityEngine;

public class Car : MonoBehaviour
{
  public string m_Make = "Toyota";
  public int m_YearBuilt = 1980;
  public Color m_Color = Color.black;
}

씬에 새 게임 오브젝트를 만들고 Car 스크립트 컴포넌트를 부착하십시오.

Car 오브젝트의 기본 인스펙터
Car 오브젝트의 기본 인스펙터

커스텀 인스펙터 스크립트 생성

직렬화된 오브젝트를 위한 커스텀 인스펙터를 만들려면 에디터 기본 클래스에서 파생된 클래스를 만들고 CustomEditor 속성을 추가해야 합니다. 이 속성은 이 커스텀 인스펙터가 어떤 클래스를 나타내는지 Unity에 알립니다. UI 툴킷에서 이의 워크플로는 즉시 모드 GUI(IMGUI)의 워크플로와 동일합니다.

Assets/Scripts/Editor 안에 파일 Car_Inspector.cs를 만들고 다음 코드를 복사하여 넣으십시오.

using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

[CustomEditor(typeof(Car))]
public class Car_Inspector : Editor
{
}
참고
커스텀 인스펙터 파일은 Editor 폴더 안 또는 에디터 전용 어셈블리 정의 안에 있어야 합니다. UnityEditor 네임스페이스를 사용할 수 없으므로, 스탠드얼론 빌드를 만들려는 시도는 실패합니다.

이 시점에서 Car 컴포넌트가 있는 게임 오브젝트를 선택하면 Unity가 아직 기본 인스펙터를 표시합니다. 기본 인스펙터를 대체하려면 Car_Inspector 클래스 내 CreateInspectorGUI()를 오버라이드해야 합니다.

CreateInspectorGUI() 함수는 인스펙터를 위한 시각적 트리를 빌드합니다. 함수는 UI를 포함하는 VisualElement를 반환해야 합니다. 아래의 CreateInspectorGUI() 구현은 새로운 빈 VisualElement를 만들고 레이블을 추가합니다.

Car_Inspector 스크립트 안의 CreateInspectorGUI() 함수를 오버라이드하고 아래 코드를 복사하십시오.

public override VisualElement CreateInspectorGUI()
{
  // Create a new VisualElement to be the root of our inspector UI
  VisualElement myInspector = new VisualElement();

  // Add a simple label
  myInspector.Add(new Label("This is a custom inspector"));

  // Return the finished inspector UI
  return myInspector;
}
레이블이 있는 커스텀 인스펙터
레이블이 있는 커스텀 인스펙터

커스텀 인스펙터 내에서 UXML 사용

UI 툴킷에서는 다음 두 가지 방법으로 UI 컨트롤을 추가할 수 있습니다.

  • 스크립트 구현
  • 미리 만들어진 UI 트리를 포함하는 UXML 파일 로드

이 섹션에서는 UI 빌더를 사용하여 UI를 포함하는 UXML 파일을 만들고, 코드를 사용하여 UXML 파일의 UI를 로드 및 인스턴스화합니다.

Window > UI Toolkit > UI Builder 메뉴로 이동하여 UI 빌더를 열고, UI 빌더에서 File > New 메뉴로 이동하여 새 시각적 트리 에셋을 만드십시오.

레이블이 있는 커스텀 인스펙터
레이블이 있는 커스텀 인스펙터

UI 툴킷을 사용하여 에디터 창과 커스텀 인스펙터를 만드는 경우, 더 많은 컨트롤 타입을 이용할 수 있습니다. 기본적으로 이러한 에디터 전용 컨트롤은 UI 빌더에 표시되지 않습니다. 이러한 컨트롤을 사용 가능하게 만들려면 Editor Extension Authoring 체크박스를 활성화해야 합니다.

UI 빌더의 계층 구조 뷰에서 <unsaved file>*.uxml을 선택하고 Editor Extension Authoring 체크박스를 활성화하십시오.

레이블이 있는 커스텀 인스펙터
레이블이 있는 커스텀 인스펙터
참고
UI 툴킷을 사용하여 에디터 창과 커스텀 인스펙터를 만드는 경우, Project Settings > UI Builder에서 이 설정을 기본으로 활성화할 수 있습니다.

UI에 컨트롤을 추가하려면 Library에서 선택한 다음 위의 계층 구조로 드래그하십시오. 자동 레이아웃을 수정하려는 경우가 아니면 새 컨트롤의 포지션이나 크기를 변경할 필요는 없습니다. 기본적으로 레이블은 가용 패널의 너비 전체를 사용하며, 높이는 선택한 폰트 크기에 맞춰 조정됩니다.

Library에서 계층 구조로 레이블 컨트롤을 드래그하여 시각적 트리에 추가하십시오.

레이블이 있는 커스텀 인스펙터
레이블이 있는 커스텀 인스펙터

레이블 안의 텍스트를 변경하려면 해당 텍스트를 선택하고 UI 빌더 에디터 오른쪽에 있는 요소의 인스펙터에서 텍스트를 바꾸십시오.

레이블이 있는 커스텀 인스펙터
레이블이 있는 커스텀 인스펙터

UI 빌더는 시각적 트리를 저장할 때 UXML 형식의 시각적 트리 에셋으로 저장합니다. 자세한 내용은 UXML 문서 페이지를 참조하십시오.

아래 UXML은 이전 단계에서 UI 빌더가 생성한 코드를 나타냅니다.

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
    <ui:Label text="Label created in UI Builder" />
</ui:UXML>

만든 시각적 트리는 UI 빌더의 File 메뉴를 사용하여 Asset > Script > EditorCar_Inspector_UXML.uxml로 저장하십시오.

커스텀 인스펙터 내에서 만든 UXML 파일을 사용하려면 CreateInspectorGUI() 함수 안에서 로드하고 클로닝한 다음 시각적 트리에 추가해야 합니다. 이를 수행하려면 CloneTree 메서드를 사용하십시오. 모든 VisualElement는 만든 요소의 부모로 기능하는 파라미터로 전달할 수 있습니다.

CreateInspectorGUI() 함수를 수정하여 시각적 트리를 UXML 파일 안에서 클로닝하고 커스텀 인스펙터에서 사용하십시오.

public override VisualElement CreateInspectorGUI()
{
  // Create a new VisualElement to be the root of our inspector UI
  VisualElement myInspector = new VisualElement();

  // Add a simple label
  myInspector.Add(new Label("This is a custom inspector"));

  // Load and clone a visual tree from UXML
  VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Scripts/Editor/Car_Inspector_UXML.uxml");
  visualTree.CloneTree(myInspector);

  // Return the finished inspector UI
  return myInspector;
}

그러면 car 컴포넌트를 위한 인스펙터가 만들어진 레이블 두 개를 표시합니다. 하나는 스크립트를 통해, 다른 하나는 UI 빌더/UXML을 통해 표시됩니다.

두 개 레이블이 있는 커스텀 인스펙터
두 개 레이블이 있는 커스텀 인스펙터

코드는 시각적 트리를 클로닝하기 위해 시각적 트리 에셋(UXML) 파일을 로드해야 하며, 하드 코딩된 경로와 파일 이름을 사용합니다. 단, 하드 코딩된 파일은 파일 경로나 이름 등의 파일 프로퍼티가 변경되면 코드가 무효화될 수 있으므로 권장되지 않습니다.

시각적 트리 에셋에 액세스하는 더 좋은 방법은 에셋 파일에 대한 레퍼런스를 사용하는 것입니다. 메타 파일 내 GUID는 파일 레퍼런스를 저장합니다. 파일의 이름을 변경하거나 파일을 이동하더라도 GUID는 변경되지 않으며, Unity는 계속해서 GUID를 찾아 파일을 새 위치에서 로드할 수 있습니다.

프리팹과 ScriptableObjects의 경우, 에디터의 다른 파일에 대한 레퍼런스를 할당할 수 있습니다. 스크립트 파일의 경우 Unity는 Default Reference 설정을 허용합니다. 창 클래스에서 VisualTreeAsset 타입의 공용 필드를 선언하면 인스펙터가 상응하는 오브젝트 필드로 레퍼런스를 드래그하는 기능을 제공합니다. 즉, Car_Inspector 클래스의 새로운 인스턴스는 모두 상응하는 VisualTreeAsset 오브젝트에 대해 설정된 레퍼런스로 채워집니다. 이는 커스텀 인스펙터 및 에디터 창 스크립트에 UXML 파일을 할당하는 방법으로 권장되는 방법입니다.

스크립트에서 VisualTreeAsset을 위한 public 변수를 만들고, 에디터에서 Car_Inspector_UXML.uxml 파일을 기본 레퍼런스로 할당하십시오.

public VisualTreeAsset m_InspectorXML;
두 개 레이블이 있는 커스텀 인스펙터
두 개 레이블이 있는 커스텀 인스펙터
참고
기본 레퍼런스는 에디터에서만 작동하며, AddComponent() 메서드를 사용하는 스탠드얼론 빌드의 런타임 컴포넌트와는 함께 작동하지 않습니다.

기본 레퍼런스를 설정한 후에는 더 이상 LoadAssetAtPath 함수를 사용하여 VisualTreeAsset을 로드할 필요가 없습니다. 대신 UXML 파일에 대한 레퍼런스에서 바로 CloneTree를 사용할 수 있습니다.

이를 통해 CreateInspectorGUI() 메서드 내 코드를 3줄로 줄일 수 있습니다.

public VisualTreeAsset m_InspectorXML;

public override VisualElement CreateInspectorGUI()
{
  // Create a new VisualElement to be the root of our inspector UI
  VisualElement myInspector = new VisualElement();

  // Load from default reference
  m_InspectorXML.CloneTree(myInspector);

  // Return the finished inspector UI
  return myInspector;
}

실행 취소 및 데이터 바인딩

이 커스텀 인스펙터의 목적은 Car 클래스의 모든 프로퍼티를 표시하는 것입니다. 사용자가 UI 컨트롤을 수정하면 Car 클래스의 인스턴스 내 값 또한 변경되어야 합니다. 이를 위해서는 시각적 트리에 UI 컨트롤을 추가하고 클래스의 개별 프로퍼티와 연결해야 합니다.

UI 툴킷은 SerializedObject 데이터 바인딩을 통한 UI 컨트롤과 직렬화된 프로퍼티의 연결을 지원합니다. 직렬화된 프로퍼티에 바인딩된 컨트롤은 프로퍼티의 현재 값을 표시하고 사용자가 UI에서 프로퍼티를 변경하면 프로퍼티 값을 업데이트합니다.컨트롤의 값을 검색하여 프로퍼티에 다시 쓰는 코드를 작성할 필요가 없습니다.

UI 빌더를 사용하여 인스펙터에 자동차의 m_Make 프로퍼티를 위한 TextField 컨트롤을 추가하십시오.

UI에 텍스트 필드 추가
UI에 텍스트 필드 추가

직렬화된 프로퍼티에 컨트롤을 바인드하려면 컨트롤의 binding-path 필드에 프로퍼티를 할당하십시오. 코드, UXML 또는 UI 빌더에서 이 작업을 수행할 수 있습니다. 프로퍼티는 이름으로 매칭되므로 철자가 올바른지 확인하십시오.

UI 빌더의 m_Make 프로퍼티에 새 TextField를 바인드하십시오.

UI 빌더의 컨트롤에 프로퍼티 바인딩
UI 빌더의 컨트롤에 프로퍼티 바인딩

아래는 인스펙터 UI를 위한 UXML 코드(데이터 바인딩 속성 포함)입니다.

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
    <ui:TextField label="Make of the car" text="&lt;not set&gt;" binding-path="m_Make" />
</ui:UXML>

컨트롤의 바인딩 경로를 설정함으로써 컨트롤이 연결되어야 할 직렬화된 프로퍼티의 이름을 컨트롤에 알려주게 됩니다. 하지만 컨트롤은 여전히 프로퍼티가 속한 직렬화된 오브젝트의 인스턴스를 수신해야 합니다. VisualElement.Bind 메서드를 사용하여 MonoBehaviour와 같은 직렬화된 오브젝트를 시각적 트리 전체에 바인드할 수 있습니다. 그러면 개별 컨트롤이 해당 오브젝트의 적절한 프로퍼티에 바인드됩니다.

커스텀 인스펙터를 작성할 때는 바인딩이 자동으로 이루어집니다.시각적 트리의 반환이 완료되면 CreateInspectorGUI()가 암시적 바인드를 수행합니다.자세히 알아보려면 SerializedObject 데이터 바인딩을 참조하십시오.

텍스트 필드가 표시된 커스텀 인스펙터
텍스트 필드가 표시된 커스텀 인스펙터

UI 툴킷은 직렬화된 프로퍼티와 함께 작동하므로, 추가 코드를 사용하지 않아도 실행 취소/다시 실행 기능이 자동으로 지원됩니다.

프로퍼티 필드

Car 클래스의 프로퍼티를 표시하려면 각 필드를 위한 컨트롤을 추가해야 합니다. 컨트롤은 바인딩이 가능하도록 프로퍼티 타입과 일치해야 합니다. 예를 들어, int는 정수 필드나 정수 슬라이더에 바인드되어야 합니다.

프로퍼티 타입에 기반하여 특정한 컨트롤을 추가하는 대신 일반 PropertyField 컨트롤을 사용할 수도 있습니다. 이 컨트롤은 대다수의 직렬화된 프로퍼티 타입에 작동하며, 이 프로퍼티 타입을 위한 기본 인스펙터 UI를 생성합니다.

Car 클래스의 m_YearBuiltm_Color 프로퍼티를 위한 PropertyField 컨트롤을 추가하십시오. 각 프로퍼티를 위한 바인딩 경로를 할당하고 Label 텍스트를 입력하십시오.

UI 빌더에 프로퍼티 필드 추가
UI 빌더에 프로퍼티 필드 추가

PropertyField의 이점은 스크립트 내 변수 타입을 변경하면 인스펙터 UI도 자동으로 변경된다는 것입니다. 단, 시각적 트리가 직렬화된 오브젝트에 바인드되기까지는 필요한 컨트롤 타입을 알 수 없으며 UI 툴킷이 프로퍼티 타입을 결정할 수 있으므로 UI 빌더 내 컨트롤의 미리보기는 불가능합니다.

프로퍼티 필드가 있는 커스텀 인스펙터
프로퍼티 필드가 있는 커스텀 인스펙터

커스텀 프로퍼티 드로어 생성

커스텀 프로퍼티 드로어는 직렬화 가능한 커스텀 클래스를 위한 커스텀 인스펙터 UI입니다. 해당 직렬화 가능한 클래스가 다른 직렬화된 오브젝트의 일부인 경우, 커스텀 UI는 해당 프로퍼티를 인스펙터에 표시합니다. UI 툴킷에서 PropertyField 컨트롤은 필드의 커스텀 프로퍼티 드로어를 표시합니다(있는 경우).

Assets/Scripts에 새 스크립트 Tire.cs를 만들고 다음 코드를 복사하여 파일에 넣으십시오.

[System.Serializable]
public class Tire
{
  public float m_AirPressure = 21.5f;
  public int m_ProfileDepth = 4;
}

아래 코드에 나타난 것처럼 Car 클래스에 Tire의 리스트를 추가하십시오.

public class Car : MonoBehaviour
{
  public string m_Make = "Toyota";
  public int m_YearBuilt = 1980;
  public Color m_Color = Color.black;

  // This car has four tires
  public Tire[] m_Tires = new Tire[4];
}

PropertyField 컨트롤은 모든 표준 프로퍼티 타입과 함께 작동하며, 직렬화 가능한 커스텀 클래스 및 배열도 지원합니다. 자동차 타이어의 프로퍼티를 표시하려면 UI 빌더에 PropertyField를 하나 더 추가하고 m_Tires에 바인드하십시오.

m_Tires 프로퍼티를 위한 PropertyField 컨트롤을 추가하십시오.

PropertyField 컨트롤을 사용하여 배열 표시
PropertyField 컨트롤을 사용하여 배열 표시

아래는 현재 인스펙터 UI를 위해 생성된 Car_Inspector_UXML.uxml의 UXML 코드입니다.

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
    <ui:TextField label="Make of the car" text="&lt;not set&gt;" binding-path="m_Make" />
    <uie:PropertyField label="Year Built" binding-path="m_YearBuilt" />
    <uie:PropertyField binding-path="m_Color" label="Paint Color" />
    <uie:PropertyField binding-path="m_Tires" label="Tires" />
</ui:UXML>

커스텀 프로퍼티 드로어를 사용하면 리스트에 있는 개별 Tire 요소의 모습을 커스터마이즈할 수 있습니다. 커스텀 프로퍼티 드로어는 Editor 기본 클래스가 아닌 PropertyDrawer 클래스에서 파생됩니다. 커스텀 프로퍼티를 위한 UI를 만들려면 CreatePropertyGUI 메서드를 오버라이드해야 합니다.

Assets/Scripts/Editor 폴더 안에 새 스크립트 Tire_PropertyDrawer.cs를 만들고 아래의 코드를 복사하여 넣으십시오.

using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

[CustomPropertyDrawer(typeof(Tire))]
public class Tire_PropertyDrawer : PropertyDrawer
{
  public override VisualElement CreatePropertyGUI(SerializedProperty property)
  {
    // Create a new VisualElement to be the root the property UI
    var container = new VisualElement();

    // Create drawer UI using C#
    // ...

    // Return the finished UI
    return container;
  }
}

커스터마이즈된 인스펙터에서처럼 코드와 UXML을 사용하여 프로퍼티를 위한 UI를 만들 수 있습니다. 이 예제에서는 코드를 사용하여 커스텀 UI를 만들었습니다.

아래에 표시된 대로 CreatePropertyGUI 메서드를 확장하여 Tire 클래스 프로퍼티 드로어를 위한 커스텀 UI를 만드십시오.

public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
  // Create a new VisualElement to be the root the property UI
  var container = new VisualElement();

  // Create drawer UI using C#
  var popup = new UnityEngine.UIElements.PopupWindow();
  popup.text = "Tire Details";
  popup.Add(new PropertyField(property.FindPropertyRelative("m_AirPressure"), "Air Pressure (psi)"));
  popup.Add(new PropertyField(property.FindPropertyRelative("m_ProfileDepth"), "Profile Depth (mm)"));
  container.Add(popup);

  // Return the finished UI
  return container;
}
커스텀 프로퍼티 드로어를 사용하는 인스펙터
커스텀 프로퍼티 드로어를 사용하는 인스펙터

프로퍼티 드로어에 대한 자세한 내용은 PropertyDrawer의 문서를 참조하십시오.

참고:Unity는 기본 인스펙터를 IMGUI로 만들기 때문에 기본 인스펙터 내에서의 커스텀 프로퍼티 드로어 사용을 지원하지 않습니다.커스텀 프로퍼티 드로어를 생성하려면, 이 가이드에서 TireCar에 대해 생성하는 것처럼 해당 프로퍼티를 사용하는 클래스의 커스텀 인스펙터도 생성해야 합니다.

기본 인스펙터 생성

커스텀 인스펙터 개발 중 기본 인스펙터에 대한 액세스를 유지하는 것이 좋습니다. UI 툴킷에서는 간편하게 커스텀 UI에 기본 인스펙터 UI를 추가할 수 있습니다.

UI 빌더에서 UI에 Foldout 컨트롤을 추가하고 이름을 Default_Inspector로 지정한 다음 레이블 텍스트를 할당하십시오.

기본 인스펙터를 위한 폴드아웃
기본 인스펙터를 위한 폴드아웃

UI 빌더를 사용하여 폴드아웃을 만들지만, 인스펙터는 만들지 않습니다. 인스펙터 스크립트 안에서 기본 인스펙터의 콘텐츠가 생성되어 코드를 통해 폴드아웃 컨트롤에 부착됩니다.

UI 빌더에서 만든 폴드아웃에 기본 인스펙터 UI를 부착하려면 폴드아웃에 대한 레퍼런스를 획득해야 합니다. 인스펙터의 시각적 트리에서 폴드아웃의 시각적 요소를 검색해 가져올 수 있습니다. 이 작업은 UQuery API 패밀리를 사용하여 수행합니다. 이름, USS 클래스, 타입 또는 이러한 속성의 조합으로 UI 내 개별 요소를 검색하여 가져올 수 있습니다.

UI 빌더에서 설정한 이름을 사용하여 CreateInspectorGUI 메서드 내 Foldout 컨트롤에 대한 레퍼런스를 획득하십시오.

// Get a reference to the default inspector foldout control
VisualElement inspectorFoldout = myInspector.Q("Default_Inspector");

InspectorElementFillDefaultInspector 메서드는 주어진 직렬화된 오브젝트를 위한 기본 인스펙터가 있는 시각적 트리를 생성하고 파라미터로 메서드에 전달된 부모 시각적 요소에 연결합니다.

아래 코드를 사용하여 기본 인스펙터를 만들어 폴드아웃에 부착하십시오.

// Attach a default inspector to the foldout
InspectorElement.FillDefaultInspector(inspectorFoldout, serializedObject, this);
폴드아웃과 기본 인스펙터
폴드아웃과 기본 인스펙터

Final scripts

아래는 이 가이드에서 만든 모든 파일의 소스 코드 전문입니다.

Car.cs

using UnityEngine;

public class Car : MonoBehaviour
{
  public string m_Make = "Toyota";
  public int m_YearBuilt = 1980;
  public Color m_Color = Color.black;

  // This car has four tires
  public Tire[] m_Tires = new Tire[4];
}

Car_Inspector.cs

using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

[CustomEditor(typeof(Car))]
public class Car_Inspector : Editor
{
  public VisualTreeAsset m_InspectorXML;

  public override VisualElement CreateInspectorGUI()
  {
    // Create a new VisualElement to be the root of our inspector UI
    VisualElement myInspector = new VisualElement();

    // Load from default reference
    m_InspectorXML.CloneTree(myInspector);

    // Get a reference to the default inspector foldout control
    VisualElement inspectorFoldout = myInspector.Q("Default_Inspector");

    // Attach a default inspector to the foldout
    InspectorElement.FillDefaultInspector(inspectorFoldout, serializedObject, this);

    // Return the finished inspector UI
    return myInspector;
  }
}

Car_Inspector_UXML.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
    <ui:TextField label="Make of the car" text="&lt;not set&gt;" binding-path="m_Make" />
    <uie:PropertyField label="Year Built" binding-path="m_YearBuilt" />
    <uie:PropertyField binding-path="m_Color" label="Paint Color" />
    <uie:PropertyField binding-path="m_Tires" label="Tires" />
    <ui:Foldout text="Default Inspector" name="Default_Inspector" />
</ui:UXML>

Tire.cs

[System.Serializable]
public class Tire
{
  public float m_AirPressure = 21.5f;
  public int m_ProfileDepth = 4;
}

Tire_Property.cs

using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

[CustomPropertyDrawer(typeof(Tire))]
public class Tire_PropertyDrawer : PropertyDrawer
{
  public override VisualElement CreatePropertyGUI(SerializedProperty property)
  {
    // Create a new VisualElement to be the root the property UI
    var container = new VisualElement();

    // Create drawer UI using C#
    var popup = new UnityEngine.UIElements.PopupWindow();
    popup.text = "Tire Details";
    popup.Add(new PropertyField(property.FindPropertyRelative("m_AirPressure"), "Air Pressure (psi)"));
    popup.Add(new PropertyField(property.FindPropertyRelative("m_ProfileDepth"), "Profile Depth (mm)"));
    container.Add(popup);

    // Return the finished UI
    return container;
  }
}
커스텀 에디터 창 생성
SerializedObject 데이터 바인딩