unity ToolKit 와 UI Builder 이용해서 커스텀에디터만들기-1

2022. 7. 20. 09:59유니티 unity

커스텀 에디터를 만들려고 여러 자료를 찾다가 Toolkit UIBuilder이라는 걸 찾았습니다.

 

원래는 스크립트상으로 UI를 그렸지만 UiBuilder 라는걸 이용하면 UGUI처럼 UI를 그릴 수 있습니다.

 

참고했던 유튜브는

https://www.youtube.com/watch?v=olcf8LWQDoI

 

UI ToolKit이란

기존 UI 시스템을 개선하고 편의성을 더한 새로운 UI시스템이고

현재 기준으로 프로젝트를 새로 만들면 포함되어있습니다.

UIElements에서 UI Toolkit으로 이름 변경했습니다.

 

런타임에서도 좋다는데 아직까지는 UGUI도 괜찮은 거 같아서

커스텀 에디터를 만드는 거는 UIToolKit를 이용하면 좋을 거 같습니다.

 

web 개발에 비슷하게 설계가 되어있고

UI ToolKit에 UXML는 XML

USS는 CSS라고 생각하면 될꺼같습니다.

 

키는 방법은

 

이런 식으로 나옵니다

 

커스텀 에디터를 만들수도 있고 런타임에서도 사용이 가능하고 인스펙터 창을 꾸밀 수 있습니다.

 

출처 :https://www.youtube.com/watch?v=2_UY_RBEVLQ

 

 

 

sample를 보시면

 

매우 많은 기능들이 있다는 걸 알 수 있습니다.

 

간단하게 생각해서 UI Builder로 UI를 그린 다음에 그에 상호작용을 넣으면 엄청 쉽게 맵 에디터를 만들 수 있다고 판단했습니다

 

 

UIBuilder관련 기능들에 대해서 알아봅시다

 

왼쪽에 Editor Extension Authoring 체크하면 커스텀 에디터에서만 사용 가능한 기능들이 추가로 나옵니다.

 

저것들을 이용해서 자신이 원하는 커스텀 에디터를 만들 수 있습니다.

 

위치 조절이 맨 처음 UGUI랑 달라서 헷갈릴 수 있는데

 

저 같은 경우는 VisualElement로 구역을 나눠서 사용했습니다

 

A로 사이즈를 넣고

자식으로 B와 C 를 넣었습니다.

 

 

A의 Flex

오른쪽으로 설정한 뒤

B C 크기를 조절해서 맞추고

B C 의 Border(테두리) 값을 조절했습니다

 

 

 

이런 식으로 UI를 꾸밀 수 있고

 

uxml를 저장해서 프리팹처럼 사용이 가능합니다

 

a로 저장한 뒤 다른 곳에서 사용해보겠습니다.

 

 

 

UGUI처럼 UI프리팹을 불러와서 사용한 것처럼 사용이 가능합니다.

이제 저것들을 스크립트상에서 사용이 가능하도록 해야 하는데

UGUI UI들을 동적으로 사용할 때랑 매우 비슷합니다.

 

 스크립트 하나를 만들어봅니다

using UnityEditor;
using UnityEngine;

public class EditorTest : EditorWindow
{
    [MenuItem("Test/EditorTest")] 
    public static void Show()
    {
        EditorTest wnd = GetWindow<EditorTest>();
        //창을 띄웁니다.
        wnd.titleContent = new GUIContent("EditorTest");
        //창 이름을 EditorTest로 합니다.
        /*
       
        Vector2 size = new Vector2(800, 400);
        wnd.minSize = size;
        wnd.maxSize = size;
        //사이즈를 조절가능
        */
    }
}

MenuItem를 이용해서

Test창에서 EditorTest를 클릭 시 함수가 실행되도록 합니다

. 이런 식으로 창이 뜨는데 이제 우리가 만든 uxml를 넣어주면 됩니다.

 

EditorWindow 함수

https://docs.unity3d.com/ScriptReference/EditorWindow.html

 

 

uxml를 넣는 방법은 맨 처음 창이 열리면 실행되는 함수 CreateGUI에서 uxml를 불러옵니다

 

using UnityEngine.UIElements;

[SerializeField]
private VisualTreeAsset visualTree1;
    
public void CreateGUI()
{
    VisualElement root = rootVisualElement;
    //rootVisualElement 이란 VisualElement에서 가장 최상의 VisualElement 입니다.맨 위
    //불러오는 방법은 여러방법이 있습니다.
    //변수로 담아서 불러오거나
    //불러와서 넣거나


    1 root.Add(visualTree1.Instantiate());
    //변수에담긴 uxml를 바로 생성시킵니다
    
    ----------
    
    2 VisualTreeAsset visualTree2 = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/a.uxml")
    3 VisualTreeAsset visualTree3 = (VisualTreeAsset)EditorGUIUtility.Load("a.uxml");
    4 VisualTreeAsset visualTree4 = Resources.Load<VisualTreeAsset>("경로");
    
    
    VisualElement labelFromUXML= 변수 2~4번 .Instantiate();
    //uxml를 불러오고 생성시킵니다.
    
    root.Add(labelFromUXML);
	//적용
}

 

rootVisualElement 이란 VisualElement에서 가장 최상의 VisualElement 입니다.맨 위

사용자가 만든 VisualElement가 아닙니다.

 

첫 번째는 방법은 스크립트 클릭해서 찾아 넣어주면 됩니다.

두 번째 방법은 직접 경로를 적어줘야 하고

세 번째 방법은 Editor Default Resources라는 폴더 안에 있어야 합니다.

네 번째 방법은 Resources라는 폴더 안에 있어야 합니다.

 

저는 두 번째 방법을 쓰겠습니다.

public void CreateGUI()
{
    VisualElement root = rootVisualElement;
    VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/a.uxml");
    root.Add(visualTree.Instantiate());
}

 

잘 나옵니다. 버튼을 눌러봐도 아무런 반응이 없습니다.

스크립트로 따로 상호작용을 넣어줘야 합니다.

 

넣기 전에 uss에 대해서 알아봅시다

 

styleSheets는 USS로 웹으로 치면 CSS 랑 같습니다.

이것을 이용해서 버튼 위에 마우스가 있을 때 색상을 바꾸거나

버튼이 활성화될 때 색상을 바꿀 수 있습니다.

css랑 같습니다.

new 버튼을 눌러서 a.uss를 만들어봅시다.

 

btn이라는 타입을 만듭니다.

 

 

btn를 해당 객체에 넣으면 이것을 가지고 있는 객체는 다 활성화가 됩니다.

동시에 꾸밀 수 있고 스크립트상에서 동적으로 넣을 수도 있습니다.

 

css에서 hover는 마우스가 위에 있다면 실행됩니다

 

https://developer.mozilla.org/ko/docs/Web/CSS/:hover

 

active는 누른순간부터 뗀시점까지 실행됩니다.

 

 

 

추가하는 방법은 UIBuilder에서 추가하는 방법과

uss를 열어서 스크립트로 추가하는 방법이 있습니다.

 

첫 번째는

 

이름 옆에 :'원하는기능'을 넣습니다.

 

클릭한 뒤 Background를 변경합니다

 

active도 똑같이 만들어봅시다 저는 background와 border에 노란색 넣었습니다.

 

드래그해서 버튼에다가 넣거나 해당 요소를 클릭한 뒤 Style Class List안에 넣습니다

적용 전 후

 

스크립트로 넣는 방법은 해당 uss를 켜고

 

a.uss

.btn {
}

.btn:active {
    border-color: rgb(253, 208, 82);
    background-color: rgba(248, 255, 8, 255);
}

.btn:hover {
    background-color: rgba(6, 0, 255, 255);
}

넣어주면 똑같습니다.

 

 

동적으로 넣는 방법은 스크립트에서 적용할 uss와 요소를 가져와서 적용하면 됩니다.

 

우선 버튼들의 uss를 제거합니다.

 

요소들의 이름 또는 Style Class를 추가해줍니다

 

 

 

다 똑같이 적용을 할 거라서 이름을 하나로 Btn로 통일했습니다.

 

요소를 가져오는 방법은 Query를 사용해서 배열로 가져오거나

Q를 사용해서 하나만 가져옵니다

배열로 가져와서 적용할 거라서 Query를 사용하겠습니다.

이름을 가져와서 하는 방법과 Class를 가져와서 하는 방법이 있습니다.

 

public void CreateGUI()
{
    //...



    root.Query<Button>("Btn").ForEach((btn) =>
    {
        btn.AddToClassList("btn");
        //btn 클래스 추가합니다.
    });
    //이름 Btn

    root.Query<Button>(null,"BtnClass").ForEach((btn) =>
    {
        btn.AddToClassList("btn");
        //btn 클래스 추가합니다.
    });
    //클래스 BtnClass

}

 

이런 식으로 a.uss가 있어서 저런 class를 추가가 가능합니다 반대로 uss 가 없는 상태에서 넣고 싶다면

uss를 넣어주면 됩니다. 

우선 제거를 한 뒤 동적으로 넣어보겠습니다.

public void CreateGUI()
{

	//...
    
    
    StyleSheet style = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/a.uss");
    //불러온다
    root.styleSheets.Add(style);
    //넣는다
}

 

이번에는

 

 

특정 요소 하나만 가져와서 수정해보겠습니다. 다시 에디터에서 a.uss 넣고

 

찾을 때 root라는 변수 rootVisualElement 최상의 VisualElement에서 찾았지만 이번에는

B라는 rootVisualElement에서 찾아보겠습니다.

 

 

public void CreateGUI()
{
	//....
    
    
    VisualElement B = root.Q<VisualElement>("B");
    Button btn6 = B.Q<Button>("Btn6");
    btn6.AddToClassList("btn");
}

 

요소를 가져왔으니 이것을 활용해서 버튼 클릭 시 이벤트를 넣어봅시다.

 

 

public void CreateGUI()
{
	//...
    
    VisualElement B = root.Q<VisualElement>("B");
    Button btn6 = B.Q<Button>("Btn6");
    btn6.AddToClassList("btn");
    
    
    btn6.clicked += () =>
    {
        Debug.Log("안녕하세요");
        //람다식
    };
    
    btn6.clicked += Hello;
    //클릭이벤트
    
    btn6.clickable.clickedWithEventInfo += info;
    //정보도 같이 확인가능


}

private void Hello()
{
    Debug.Log("Hello");
}

private void info(EventBase obj)
{
    Button button = obj.target as Button;
    string name = button.name;
    Debug.Log(name);
}

 

요소의 정보를 알아 올 수도 있고 클릭 이벤트를 콜백으로 받을 수 있습니다.

https://docs.unity3d.com/ScriptReference/UIElements.Button.html

 

Unity - Scripting API: Button

A Button consists of a text label element that can respond to pointer and mouse events. You can replace or add to the content of the button by adding elements to its hierarchy. For example, to use a separate image as an icon for the button, you can make an

docs.unity3d.com

 

이번엔 정수 필드를 넣어서 스크립트를 넣어봅시다

 

 

public void CreateGUI()
{
    BaseField<int> intField = B.Q<BaseField<int>>("intField");
    intField.RegisterValueChangedCallback((evt) =>
    {
        Debug.Log($"새로운값 {evt.newValue},,,,이전값 {evt.previousValue}");
    });
}

RegisterValueChangedCallback는 값이 변경되면 해당 함수를 실행합니다.

 

 

https://docs.unity3d.com/ScriptReference/UIElements.BaseField_1.html

 

Unity - Scripting API: BaseField<T0>

Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable. Close

docs.unity3d.com

 

 

스크립트 보기

더보기
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public class EditorTest : EditorWindow
{
    [MenuItem("Test/EditorTest")] 
    public static void Show()
    {
        EditorTest wnd = GetWindow<EditorTest>();
        wnd.titleContent = new GUIContent("EditorTest");
        /*
        Vector2 size = new Vector2(800, 400);
        wnd.minSize = size;
        wnd.maxSize = size;
        */
    }
    

    public void CreateGUI()
    {
        VisualElement root = rootVisualElement;
        VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/a.uxml");
        root.Add(visualTree.Instantiate());
        VisualElement labelFromUXML = visualTree.Instantiate();
        StyleSheet style = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/a.uss");
        root.styleSheets.Add(style);

        
        // root.Query<Button>("Btn").ForEach((btn) =>
        // {
        //     btn.AddToClassList("btn");
        // });
        
        root.Query<Button>(null,"BtnClass").ForEach((btn) =>
        {
            btn.AddToClassList("btn");
        });

        VisualElement B = root.Q<VisualElement>("B");
        Button btn6 = B.Q<Button>("Btn6");
        //btn6.AddToClassList("btn");

        btn6.clicked += () =>
        {
            Debug.Log("안녕하세요");
        };
        btn6.clicked += Hello;
        btn6.clickable.clickedWithEventInfo += info;

        BaseField<int> intField = B.Q<BaseField<int>>("intField");
        intField.RegisterValueChangedCallback((evt) =>
        {
            Debug.Log($"새로운값 {evt.newValue},,,,이전값 {evt.previousValue}");
        });


    }

    private void Hello()
    {
        Debug.Log("Hello");
    }
    private void info(EventBase obj)
    {
        Button button = obj.target as Button;
        string name = button.name;
        Debug.Log(name);
    }

    
}

 

 

a.uss
0.00MB
a.uxml
0.00MB