버전: 2022.3+
이 예시는 C# 스크립트로 커스텀 에디터 창을 생성하여 사용자 입력에 반응하고__ UI__(사용자 인터페이스) 사용자가 애플리케이션과 상호 작용하도록 해 줍니다. Unity는 현재 3개의 UI 시스템을 지원합니다. 자세한 정보
See in Glossary 크기를 조절하고 핫 리로드를 처리하는 방법을 보여 줍니다.
커스텀 에디터 창은 EditorWindow 클래스에서 파생되는 클래스입니다. UI 툴킷은 CreateGUI 메서드를 사용하여 에디터 UI에 컨트롤을 추가하며, 창을 표시해야 할 때 Unity는 CreateGUI 메서드를 자동으로 호출합니다. 이 메서드는 Awake 또는 Update 같은 메서드와 동일한 방식으로 작동합니다.
커스텀 에디터 창을 생성할 때 다음 가이드라인을 따르십시오.
CreateGUI 메서드에 UXML/USS 로드에 의존하는 코드를 넣어 필요한 모든 에셋을 사용할 수 있도록 합니다.CreateGUI 내부 또는 CreateGUI가 호출된 후에 이벤트 등록 코드를 저장합니다.다음 다이어그램은 에디터 창 실행 순서를 보여 줍니다.
자세한 내용은 EditorWindow 클래스 기술 자료를 참고하십시오.
이 예시에서는 프로젝트 내 모든 스프라이트를 찾아 표시하고 리스트에 표시하는 스프라이트 브라우저를 생성합니다. 리스트에서 스프라이트를 선택하면 해당 스프라이트 이미지가 창의 오른쪽에 표시됩니다.
이 예시에서 생성한 완성된 파일은 이 GitHub 저장소에서 찾을 수 있습니다.
이 가이드는 Unity 에디터, UI 툴킷, C# 스크립팅에 익숙한 개발자를 위한 가이드입니다. 시작하기 전에 먼저 다음을 숙지하십시오.
UI 컨트롤을 UI에 추가하려면 시각적 요소를 비주얼 트리에 추가합니다. UI 툴킷은 VisualElement.Add() 메서드를 사용하여 기존 시각적 요소에 자식을 추가하고, rootvisualElement 프로퍼티를 통해 에디터 창의 비주얼 트리에 액세스합니다.
MyCustomEditor를 입력합니다.예시에서는 스프라이트 리스트를 표시하기 위해 AssetDatabase를 사용하여 프로젝트의 모든 스프라이트를 찾습니다. 스프라이트 브라우저의 경우 사용 가능한 창 공간을 2개로 나누는 TwoPaneSplitView를 추가합니다. 하나는 고정 크기이고 다른 하나는 가변 크기입니다. 창의 크기를 조절하면 가변 창의 크기는 조절되고 고정 크기 창의 크기는 동일하게 유지됩니다.
리스트에 필요한 다음 지시문을 파일 상단에 추가합니다.
using System.Collections.Generic;
CreateGUI() 안의 코드를 다음 코드로 대체합니다. 그러면 프로젝트 내 모든 스프라이트를 열거합니다.
public void CreateGUI()
{
// Get a list of all sprites in the project
var allObjectGuids = AssetDatabase.FindAssets("t:Sprite");
var allObjects = new List<Sprite>();
foreach (var guid in allObjectGuids)
{
allObjects.Add(AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GUIDToAssetPath(guid)));
}
}
CreateGUI() 안에 다음 코드를 추가합니다 그러면 TwoPaneSplitview가 생성되고 2개의 자식 요소가 서로 다른 컨트롤의 플레이스홀더로 추가됩니다.
// Create a two-pane view with the left pane being fixed.
var splitView = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
// Add the view to the visual tree by adding it as a child to the root element.
rootVisualElement.Add(splitView);
// A TwoPaneSplitView needs exactly two child elements.
var leftPane = new VisualElement();
splitView.Add(leftPane);
var rightPane = new VisualElement();
splitView.Add(rightPane);
메뉴에서 Window > UI Toolkit > MyCustomEditor를 선택하여 창을 엽니다. 창에 2개의 빈 패널이 있는 분할된 뷰가 표시됩니다. 디바이더 바를 이동하여 작동하는지 확인합니다.
스프라이트 브라우저의 경우 왼쪽 창은 프로젝트의 모든 스프라이트 이름이 포함된 리스트입니다. ListView 컨트롤은 VisualElement에서 파생되므로 코드를 수정하여 VisualElement 대신 ListView를 사용하기 쉽습니다.
ListView 컨트롤은 선택 가능한 항목 리스트를 표시합니다. 가시 영역을 덮을 만큼 많은 요소를 생성하고, 리스트를 스크롤할 때 시각적 요소를 풀링 및 재활용하도록 최적화되어 있습니다. 이렇게 하면 항목이 많은 리스트에서도 성능을 최적화하고 메모리 사용량을 줄일 수 있습니다.
이 컨트롤을 활용하려면 다음과 같이 ListView를 초기화합니다.
리스트의 각 요소에 대해 복잡한 UI 구조를 생성할 수 있습니다. 이 예시에서는 데모 목적으로 간단한 텍스트 레이블을 사용하여 스프라이트 이름을 표시합니다.
CreateGUI() 내에서 왼쪽 창을 VisualElement 대신 ListView로 변경합니다.
public void CreateGUI()
{
...
var leftPane = new ListView();
splitView.Add(leftPane);
...
}
CreateGUI() 하단에 다음 코드를 추가하려 ListView를 초기화합니다.
public void CreateGUI()
{
...
// Initialize the list view with all sprites' names
leftPane.makeItem = () => new Label();
leftPane.bindItem = (item, index) => { (item as Label).text = allObjects[index].name; };
leftPane.itemsSource = allObjects;
}
메뉴에서 Window > UI Toolkit > MyCustomEditor를 선택하여 커스텀 에디터 창을 엽니다. 이 창에는 아래 이미지와 유사하게 스크롤 가능한 리스트 뷰와 선택 가능한 항목이 표시됩니다.
리스트에서 스프라이트 이미지를 선택할 때 오른쪽 패널에 스프라이트 이미지를 표시하려면 왼쪽 창의 selectionChanged 프로퍼티를 사용하고 콜백 함수를 추가합니다.
이미지를 표시하려면 선택한 스프라이트에 대한 새 이미지 컨트롤을 생성하고, 컨트롤을 추가하기 전에 VisualElement.Clear()를 사용하여 이전 내용을 모두 제거합니다.
팁: 창이 유실되고 메뉴가 다시 열리지 않으면 Window > Panels > Close all floating panels 메뉴에서 모든 플로팅 패널을 닫거나 창 레이아웃을 재설정하십시오.
왼쪽 창에 있는 리스트에서 선택한 항목을 변경할 때 콜백 함수를 추가하십시오.
public void CreateGUI()
{
...
// React to the user's selection
leftPane.selectionChanged += OnSpriteSelectionChange;
}
private void OnSpriteSelectionChange(IEnumerable<object> selectedItems)
{
}
콜백 함수는 TwoPaneSplitview의 오른쪽 창에 액세스해야 합니다. 이렇게 하려면 CreateGUI() 내에서 만든 오른쪽 창을 멤버 변수로 변경합니다.
private VisualElement m_RightPane;
public void CreateGUI()
{
...
m_RightPane = new VisualElement();
splitView.Add(m_RightPane);
...
}
OnSpriteSelectionChange 함수에 다음 코드를 추가합니다. 그러면 이전 내용이 창에서 모두 지워지고, 선택한 스프라이트를 가져오며, 스프라이트를 표시하는 새 이미지 컨트롤을 추가합니다.
private void OnSpriteSelectionChange(IEnumerable<object> selectedItems)
{
// Clear all previous content from the pane.
m_RightPane.Clear();
// Get the selected sprite and display it.
var enumerator = selectedItems.GetEnumerator();
if (enumerator.MoveNext())
{
var selectedSprite = enumerator.Current as Sprite;
if (selectedSprite != null)
{
// Add a new Image control and display the sprite.
var spriteImage = new Image();
spriteImage.scaleMode = ScaleMode.ScaleToFit;
spriteImage.sprite = selectedSprite;
// Add the Image control to the right-hand pane.
m_RightPane.Add(spriteImage);
}
}
}
메뉴에서 Window > UI Toolkit > MyCustomEditor를 선택하여 커스텀 에디터 창을 엽니다. 왼쪽 리스트에서 스프라이트를 선택하면 아래 이미지와 유사하게 해당 스프라이트 이미지가 창의 오른쪽에 표시됩니다.
에디터 창의 크기는 허용되는 최소 크기와 최대 크기 내에서 조절할 수 있습니다. 이러한 크기를 설정하려면 EditorWindow.minSize와 EditorWindow.maxSize 프로퍼티를 작성하십시오. 창의 크기가 변경되지 않도록 하려면 두 프로퍼티에 동일한 크기를 할당합니다.
창 크기가 너무 작아서 UI 전체가 표시되지 않는 경우 창을 스크롤할 수 있도록 ScrollView 요소를 사용합니다. 왼쪽 창의 ListView는 내부적으로 ScrollView를 사용하지만, 오른쪽 창은 일반 VisualElement입니다. 오른쪽 창의 크기를 변경하려면 양방향으로 스크롤할 수 있는 ScrollView로 변경합니다.
ShowMyEditor() 함수 하단에 다음 코드를 추가하여 창의 크기를 제한합니다.
public static void ShowMyEditor()
{
...
// Limit size of the window.
wnd.minSize = new Vector2(450, 200);
wnd.maxSize = new Vector2(1920, 720);
}
CreateGUI() 내에서 오른쪽 창의 VisualElement를 양방향으로 스크롤할 수 있는 ScrollView로 변경합니다.
public void CreateGUI()
{
...
m_RightPane = new ScrollView(ScrollViewMode.VerticalAndHorizontal);
splitView.Add(m_RightPane);
...
}
메뉴에서 Window > UI Toolkit > MyCustomEditor를 선택하여 커스텀 에디터 창을 엽니다. 이제 스프라이트 브라우저 창에 스크롤바가 표시됩니다. 창의 크기를 조절하여 스크롤바가 작동하는지 확인합니다.
C# 도메인 리로드는 스크립트가 다시 컴파일되거나 에디터가 플레이 모드로 전환될 때 발생합니다. 방금 만든 에디터 창에서 스프라이트 브라우저를 열고 스프라이트를 선택한 다음 플레이 모드로 진입합니다. 창이 초기화되고 선택 항목이 사라집니다.
올바른 에디터 창은 핫 리로드 워크플로와 함께 작동해야 합니다. VisualElement 오브젝트는 직렬화할 수 없으므로 리로드될 때마다 UI를 다시 생성해야 합니다. 즉 리로드가 완료된 후에 CreateGUI() 메서드가 호출됩니다. 이렇게 하면 필요한 데이터를 EditorWindow 클래스에 저장하여 리로드 전에 UI 상태를 복원할 수 있습니다.
스프라이트 리스트에서 선택한 인덱스를 저장하려면 MyCustomEditor 클래스에 멤버 변수를 추가합니다. 선택할 때 이 멤버 변수는 ListView의 새로운 선택 인덱스를 저장합니다.
public class MyCustomEditor : EditorWindow
{
[SerializeField] private int m_SelectedIndex = -1;
....
}
선택한 리스트 인덱스를 저장하고 복원하려면 CreateGUI() 하단에 다음 코드를 추가합니다.
public void CreateGUI()
{
...
// Restore the selection index from before the hot reload.
leftPane.selectedIndex = m_SelectedIndex;
// Store the selection index when the selection changes.
leftPane.selectionChanged += (items) => { m_SelectedIndex = leftPane.selectedIndex; };
}
메뉴에서 Window > UI Toolkit > MyCustomEditor를 선택하여 커스텀 에디터 창을 엽니다. 리스트에서 스프라이트를 선택하고 플레이 모드에 진입하여 핫 리로드를 테스트하십시오.
참고로 완성된 스크립트는 다음과 같습니다.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class MyCustomEditor : EditorWindow
{
[SerializeField] private int m_SelectedIndex = -1;
private VisualElement m_RightPane;
[MenuItem("Window/UI Toolkit/MyCustomEditor")]
public static void ShowMyEditor()
{
// This method is called when the user selects the menu item in the Editor.
EditorWindow wnd = GetWindow<MyCustomEditor>();
wnd.titleContent = new GUIContent("My Custom Editor");
// Limit size of the window.
wnd.minSize = new Vector2(450, 200);
wnd.maxSize = new Vector2(1920, 720);
}
public void CreateGUI()
{
// Get a list of all sprites in the project.
var allObjectGuids = AssetDatabase.FindAssets("t:Sprite");
var allObjects = new List<Sprite>();
foreach (var guid in allObjectGuids)
{
allObjects.Add(AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GUIDToAssetPath(guid)));
}
// Create a two-pane view with the left pane being fixed.
var splitView = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
// Add the panel to the visual tree by adding it as a child to the root element.
rootVisualElement.Add(splitView);
// A TwoPaneSplitView always needs two child elements.
var leftPane = new ListView();
splitView.Add(leftPane);
m_RightPane = new ScrollView(ScrollViewMode.VerticalAndHorizontal);
splitView.Add(m_RightPane);
// Initialize the list view with all sprites' names.
leftPane.makeItem = () => new Label();
leftPane.bindItem = (item, index) => { (item as Label).text = allObjects[index].name; };
leftPane.itemsSource = allObjects;
// React to the user's selection.
leftPane.selectionChanged += OnSpriteSelectionChange;
// Restore the selection index from before the hot reload.
leftPane.selectedIndex = m_SelectedIndex;
// Store the selection index when the selection changes.
leftPane.selectionChanged += (items) => { m_SelectedIndex = leftPane.selectedIndex; };
}
private void OnSpriteSelectionChange(IEnumerable<object> selectedItems)
{
// Clear all previous content from the pane.
m_RightPane.Clear();
var enumerator = selectedItems.GetEnumerator();
if (enumerator.MoveNext())
{
var selectedSprite = enumerator.Current as Sprite;
if (selectedSprite != null)
{
// Add a new Image control and display the sprite.
var spriteImage = new Image();
spriteImage.scaleMode = ScaleMode.ScaleToFit;
spriteImage.sprite = selectedSprite;
// Add the Image control to the right-hand pane.
m_RightPane.Add(spriteImage);
}
}
}
}