화면 하나가 파일 두 개와 오브젝트 하나가 된다
Esc

    화면 하나가 파일 두 개와 오브젝트 하나가 된다

    –UnityUIUI Toolkit

    UI Toolkit 은 화면에 오브젝트를 놓지 않는다에서 두 체계가 다르다는 것까지 봤습니다. 이제 실제로 만들어 봅니다.

    시작은 무엇이 어디에 생기는지입니다. uGUI 에 익숙하면 이 부분이 제일 낯섭니다. 버튼을 만들었는데 하이어라키에 아무것도 안 생기거든요.

    화면 하나가 파일 두 개와 오브젝트 하나가 됩니다

    화면 하나를 띄우는 데 필요한 것 프로젝트 창 (파일) Popup.uxml 무엇이 있는지 Popup.uss 어떻게 보일지 PanelSettings 해상도와 순서 씬 (오브젝트 하나) UI Document Source Asset ← uxml Panel Settings ← 에셋 화면 확인 하이어라키에 남는 것은 UI Document 를 붙인 오브젝트 하나뿐입니다. 버튼도 글자도 씬에 없습니다. uxml 안에 있습니다. Panel Settings 는 여러 화면이 같이 씁니다. 해상도 규칙을 한곳에서 정하는 자리입니다.
    파일 셋과 오브젝트 하나. 이게 전부입니다.
    • .uxml: 무엇이 어떤 순서로 있는지
    • .uss: 그것들이 어떻게 보일지
    • Panel Settings: 해상도를 어떻게 맞출지, 여러 화면 중 무엇이 위인지
    • UI Document: 씬 오브젝트에 붙는 컴포넌트. 위의 것들을 물고 화면에 띄웁니다

    앞의 셋은 프로젝트 창의 에셋이고 마지막 하나만 씬에 있습니다. 그래서 하이어라키를 열면 오브젝트 하나뿐입니다.

    Panel Settings 가 왜 따로 떨어져 있는지는 이유가 있습니다. 여러 화면이 같은 것을 씁니다. 해상도 규칙을 화면마다 다시 정하지 않으려고 한곳에 모아 둔 것입니다. 그 안에 무엇이 있는지는 따로 한 편에서 봅니다.

    UXML 은 무엇이 어떤 순서로 있는지 적습니다

    XML 입니다. 확장자만 다릅니다.

    <?xml version="1.0" encoding="utf-8"?>
    <engine:UXML
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:engine="UnityEngine.UIElements"
        xmlns:editor="UnityEditor.UIElements"
    >
        <engine:VisualElement class="popup">
            <engine:Label text="보상 획득" />
            <engine:VisualElement class="row">
                <engine:Button name="ok" text="확인" />
                <engine:Button name="cancel" text="취소" />
            </engine:VisualElement>
        </engine:VisualElement>
    </engine:UXML>

    읽을 때 필요한 것은 셋입니다.

    • 루트는 <engine:UXML>. 문서가 “includes attributes for the namespace prefix definitions and the location of schema definition files” 라고 적어 뒀습니다. 맨 위의 xmlns: 들이 그 접두사 정의입니다. xmlns:engine="UnityEngine.UIElements" 가 engine 이라는 접두사를 그 네임스페이스로 묶습니다
    • 태그 이름이 곧 클래스 이름입니다. Button, Label, Toggle, VisualElement 는 실제 C# 타입 이름입니다. 없는 태그를 적으면 그런 타입이 없다고 합니다
    • name 과 class 는 쓰임이 다릅니다. name 은 나중에 코드에서 그 하나를 찾을 때, class 는 스타일을 걸 때 씁니다
    uxml 에 적은 순서가 그대로 쌓입니다 Popup.uxml VisualElement class="popup" Label text="보상 획득" VisualElement class="row" Button name="ok" Button name="cancel" 화면 보상 획득 ok cancel 들여쓴 만큼 자식이 되고, 위에 적은 것이 위에 놓입니다. 기본 방향이 세로라서 그렇습니다.
    구조를 글로 적으면 그 순서대로 쌓입니다.

    들여쓴 만큼 자식이 되고, 위에 적은 것이 위에 놓입니다. uGUI 의 하이어라키에서 순서가 그리는 순서였던 것과 비슷하지만, 여기서는 쌓이는 방향이 세로라서 그렇습니다. 그 얘기는 레이아웃 편에서 합니다.

    USS 는 어떻게 보일지 적습니다

    CSS 처럼 생겼습니다. 앞 UXML 에 붙일 스타일이라면 이렇습니다.

    .popup {
        background-color: rgb(28, 28, 34);
        padding: 16px;
    }
    
    .row {
        flex-direction: row;
        justify-content: flex-end;
    }
    
    #ok {
        background-color: rgb(80, 120, 240);
    }
    • .popup 처럼 점으로 시작하면 클래스 선택자입니다. UXML 의 class="popup" 에 걸립니다
    • #ok 처럼 샵으로 시작하면 이름 선택자입니다. name="ok" 에 걸립니다
    • 점도 샵도 없이 Button 이라고만 적으면 타입 선택자입니다. 그 타입 전부에 걸립니다

    생김새가 CSS 라서 그대로 될 것 같지만 아닙니다. 문서가 못을 박아 뒀습니다.

    USS supports a set of simple selectors that are analogous, but not identical, to simple selectors in CSS.

    무엇이 되고 무엇이 안 되는지는 선택자 편에서 따로 봅니다. 여기서는 파일이 나뉘어 있다는 것만 알면 됩니다. 구조를 고치는 것과 모양을 고치는 것이 서로 다른 파일을 건드리는 일이 됩니다.

    VisualElement 는 uGUI 의 오브젝트 자리입니다

    uxml 에 적은 태그 하나가 실행 중에는 VisualElement 하나가 됩니다. uGUI 로 치면 오브젝트 하나 자리입니다.

    다만 오브젝트가 아닙니다. 게임오브젝트도 아니고 컴포넌트도 아니고, 그냥 C# 객체입니다. 그래서 하이어라키에 안 보이고, GetComponent 로 못 찾고, 프리팹으로 못 만듭니다.

    찾는 방법이 따로 있습니다.

    var root = GetComponent<UIDocument>().rootVisualElement;
    var ok = root.Q<Button>("ok");
    ok.clicked += () => Debug.Log("눌렀다");
    • rootVisualElement 가 그 문서의 뿌리입니다
    • Q<T>(name) 으로 이름을 걸어 하나를 찾습니다. uGUI 의 transform.Find 와 GetComponent 를 합친 자리입니다
    • 여러 개를 찾을 때는 Query<T>() 를 씁니다

    이 트리를 문서에서는 비주얼 트리라고 부릅니다. 하이어라키가 아니라 이쪽이 실제 부모 자식 관계입니다.

    UI Document 가 화면에 올려 줍니다

    씬에 오브젝트를 하나 만들고 UI Document 컴포넌트를 붙입니다. 그리고 두 칸을 채웁니다.

    • Source Asset: 띄울 .uxml
    • Panel Settings: 해상도 규칙이 들어 있는 에셋

    띄우는 시점은 정해져 있습니다. 문서 표현으로 UI Document 는 “loads a UI Document component’s source UXML when OnEnable is called” 입니다. 오브젝트가 켜질 때 읽습니다.

    그래서 uGUI 에서 하던 것 중 몇 가지가 여기서는 다르게 됩니다.

    • 화면을 끄고 켜는 것은 그 오브젝트를 끄고 켜는 것입니다. 안쪽 요소를 하나씩 끄는 것이 아닙니다
    • 화면 여러 개를 동시에 띄우려면 UI Document 를 여러 개 둡니다. 겹치는 순서는 Panel Settings 의 Sort Order 로 정합니다
    • uxml 을 바꿔 끼우면 화면이 통째로 바뀝니다. 팝업을 여는 방법 중 하나가 이것입니다

    UI Builder 는 그 파일들을 저장하는 창입니다

    여기까지 읽으면 전부 손으로 적어야 할 것 같지만 아닙니다. UI Builder 라는 창이 있습니다. 문서 표현으로 “a visual authoring tool to create and edit UI Toolkit assets such as UXML and USS files” 입니다.

    끌어다 놓고 값을 고치면 그게 .uxml 과 .uss 로 저장됩니다. 만드는 방법이 두 가지가 아니라, 같은 파일을 두 가지 방법으로 고치는 것입니다. 창에서 고치든 텍스트로 고치든 결과는 같은 파일입니다.

    이 구조에서 따라오는 것이 하나 있습니다. UI 변경이 텍스트 차이로 남습니다.

    uGUI 에서 화면을 고치면 프리팹이나 씬 파일이 바뀝니다. 그 파일들은 사람이 읽기 어렵고, 두 사람이 같은 화면을 고치면 합치기가 까다롭습니다. uxml 과 uss 는 그냥 텍스트라서 무엇이 바뀌었는지 줄 단위로 보입니다. 화면을 여럿이 만지는 프로젝트에서는 이게 꽤 큽니다.

    정리

    • 화면 하나는 .uxml(구조) + .uss(스타일) + Panel Settings(해상도 규칙) 에셋 셋과, 씬의 UI Document 오브젝트 하나로 만들어집니다
    • 하이어라키에 남는 것은 그 오브젝트 하나뿐입니다. 버튼도 글자도 파일 안에 있습니다
    • uxml 의 루트는 <engine:UXML> 이고 맨 위 xmlns: 들이 접두사를 정의합니다. 태그 이름은 실제 C# 타입 이름입니다
    • name 은 코드에서 찾을 때, class 는 스타일을 걸 때 씁니다
    • uss 는 CSS 처럼 생겼지만 “analogous, but not identical” 입니다
    • VisualElement 는 오브젝트가 아니라 C# 객체입니다. rootVisualElement 에서 Q<T>() 로 찾습니다
    • UI Document 는 오브젝트가 켜질 때 uxml 을 읽습니다. 화면 여러 개는 UI Document 여러 개입니다
    • UI Builder 는 그 파일을 고치는 창일 뿐입니다. 결과가 텍스트라서 변경이 줄 단위로 남습니다