Version: 2021.2
언어: 한국어
커스텀 인스펙터 생성
Unity UI(UGUI)에서 UI 툴킷으로의 전환

첫 런타임 UI 만들기

이 페이지는 UI 툴킷을 사용하여 간단한 캐릭터 선택 화면을 설정하는 방법을 단계별로 설명합니다. 또한 UI 요소 및 템플릿을 만드는 방법, 씬을 설정하는 방법 및 UI에 스크립팅 로직을 연결하는 방법도 다룹니다. 이 가이드는 USS를 통한 스타일링은 다루지 않으며, 기본 스타일과 테마만 사용합니다.

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

다루는 주제: UI 빌더, ListView, 레이블, PanelSettings, UIDocument, 선택 처리

이 가이드는 다음 단계를 설명합니다.

  • 메인 UI 뷰 만들기
  • 씬 설정
  • 표시할 샘플 데이터 만들기
  • 메인 뷰를 위한 컨트롤러 만들기
  • 리스트 엔트리 UI 템플릿 만들기
  • 리스트 엔트리를 위한 컨트롤러 만들기
  • 사용자 선택에 응답

메인 UI 뷰 만들기

최종 UI 화면은 두 개의 개별 UI 템플릿(UXML)로 구성됩니다. 메인 뷰 템플릿은 캐릭터 이름이 나열된 리스트, 선택한 캐릭터의 세부 사항이 표시되는 보다 작은 패널 및 버튼 하나를 포함합니다. 이 섹션에서는 UI 빌더를 사용하여 이 템플릿을 설정합니다.

참고
UI 빌더에 익숙하며 이 단계를 건너뛰고 싶다면 이 페이지 맨 아래에서 메인 뷰를 위한 UXML 코드를 복사하여 새 파일에 바로 붙여넣고 Assets/UI/MainView.uxml로 저장하십시오.
메인 뷰의 UI 레이아웃 설정
메인 뷰의 UI 레이아웃 설정

Window > UI Toolkit > UI Builder 메뉴에서 UI 빌더 창을 여십시오. 뷰포트 왼쪽 상단에 있는 파일 메뉴를 사용하여 새 UXML 문서를 만드십시오.

UI 빌더 파일 메뉴
UI 빌더 파일 메뉴

게임 UI를 개발할 때는 항상 UI 빌더 뷰포트 오른쪽 상단에 있는 Unity Default Runtime Theme을 선택하십시오. 에디터 및 런타임 테마의 기본 폰트 크기와 컬러는 서로 다르며, 이는 레이아웃에 영향을 미칩니다.

라이브러리에서 드래그하여 새 요소 만들기
라이브러리에서 드래그하여 새 요소 만들기

계층 구조에서 새 UXML 파일을 선택하고 Match Game View 체크박스를 활성화하십시오. 이미 설정되어 있지 않은 경우 Unity 에디터를 가로 해상도로 설정해야 할 수 있습니다.

매치 게임 뷰 활성화
매치 게임 뷰 활성화

이제 UI 요소를 만들어야 합니다. Library에서 계층 구조로 새 VisualElement를 드래그하여 생성하십시오.

라이브러리에서 드래그하여 새 요소 만들기
라이브러리에서 드래그하여 새 요소 만들기

새 요소는 화면 전체를 커버해야 하므로, flex-grow 프로퍼티를 1로 설정해야 합니다. 계층 구조에서 요소를 선택하고 오른쪽 인스펙터 패널에서 Flex라는 레이블이 있는 폴드아웃을 찾으십시오. Grow의 값을 0에서 1로 변경하십시오.

Flex 프로퍼티 설정
Flex 프로퍼티 설정

이 VisualElement의 모든 자식을 화면 중앙에 정렬하려면 VisualElement의 Align 프로퍼티를 변경하십시오. Align ItemsJustify Content를 모두 center로 설정해야 합니다.

자식을 중앙에 정렬
자식을 중앙에 정렬

마지막으로 Background > Color에서 배경색을 선택할 수 있습니다. 이 단계는 선택 사항입니다. 이 예에서는 #732526을 컬러로 사용했습니다.

루트 요소 배경색
루트 요소 배경색

다음으로 기존 VisualElement 아래에 새 VisualElement를 만드십시오. 이는 UI의 왼쪽 및 오른쪽 섹션을 위한 부모 컨테이너가 됩니다.

자식 VisualElement 추가
자식 VisualElement 추가

이 새 요소의 flex-direction 프로퍼티를 row로 설정하십시오(기본값: column). 또한 픽셀 높이를 350픽셀로 고정해야 합니다.

중앙 컨테이너 프로퍼티
중앙 컨테이너 프로퍼티

현재 UI는 이렇게 표시되어야 합니다. 화면은 게임 뷰의 해상도와 종횡비에 따라 다르게 보일 수 있습니다.

안에 빈 요소가 있는 배경 컨테이너
안에 빈 요소가 있는 배경 컨테이너

캐릭터 이름의 리스트를 만들려면 Library에서 ListView 컨트롤을 선택하고 방금 만든 VisualElement 아래에 자식으로 추가하십시오. 요소를 선택하고 인스펙터의 CharacterList 이름을 할당하십시오. 이 작업을 수행해야 추후 컨트롤러 스크립트를 통해 이 리스트에 액세스할 수 있습니다.

안에 빈 요소가 있는 배경 컨테이너
안에 빈 요소가 있는 배경 컨테이너

리스트의 너비를 230픽셀로 고정하십시오. 또한 다음으로 만들 요소와 어느 정도 간격을 둘 수 있도록 오른쪽에 6픽셀 너비의 마진을 설정하십시오.

캐릭터 리스트의 크기 및 마진 폴드아웃
캐릭터 리스트의 크기 및 마진 폴드아웃

리스트에 배경색을 할당하고 모서리가 둥근 테두리를 설정할 수도 있습니다. 이 가이드에서는 배경색을 #6E3925로, 테두리 컬러를 #311A11로 설정했으며, 테두리 너비는 4픽셀로, 반지름은 15픽셀로 설정했습니다. 이 단계는 선택 사항입니다.

스타일이 적용된 캐릭터 리스트
스타일이 적용된 캐릭터 리스트

CharacterList와 같은 부모 아래에 새 VisualElement를 추가하십시오. 이 VisualElement는 캐릭터 세부 사항 패널과 버튼을 포함합니다. Align 폴드아웃 아래에서 Align Items 설정을 flex-end로, Justify Contentspace-between으로 변경하십시오.

Justify Content 프로퍼티
Justify Content 프로퍼티

이 새 컨테이너에 새로운 VisualElement를 추가하십시오. 이 VisualElement는 캐릭터 세부 사항 패널이 됩니다. 사용자가 왼쪽 리스트에서 캐릭터를 선택하면 이 패널은 캐릭터의 초상, 이름과 클래스를 표시합니다.

요소의 너비를 276픽셀로 고정하고, Align ItemsJustify Contentcenter로 전환하십시오. 또한 자식들이 컨테이너 테두리와 최소한의 거리를 유지하도록 요소에 8픽셀 너비의 패딩을 추가하십시오.

캐릭터 세부 사항 컨테이너의 프로퍼티
캐릭터 세부 사항 컨테이너의 프로퍼티

패널에 스타일을 적용하여 배경색을 #AA5939로, 테두리 컬러를 #311A11로, 테두리의 너비와 반지름을 각각 4px와 15px로 설정할 수 있습니다. 이 단계는 선택 사항입니다.

그러면 UI 레이아웃이 아래 이미지처럼 표시됩니다.

빈 캐릭터 세부 사항 패널
빈 캐릭터 세부 사항 패널

다음으로 캐릭터 세부 사항에 개별 UI 컨트롤을 추가합니다. 첫 번째로 추가할 캐릭터 초상은 배경 프레임과 전경 이미지의 두 요소로 구성됩니다.

먼저 배경 프레임을 위해 캐릭터 세부 사항 컨테이너에 새 VisualElement를 추가하십시오. 120x120픽셀의 고정된 크기를 할당하고, 포함된 이미지가 테두리에 직접 닿지 않도록 4픽셀의 패딩을 설정하십시오.

컬러가 #311A11이고 배경색이 #FF8554이며, 너비와 반지름이 각각 2픽셀, 15픽셀인 테두리로 요소를 꾸밀 수 있습니다. 원하는 컬러와 스타일링을 적용해도 됩니다.

캐릭터 초상의 배경 프레임
캐릭터 초상의 배경 프레임

실제 이미지에서는 방금 만든 프레임에 새 VisualElement를 자식으로 추가하고, 나중에 컨트롤러 스크립트에서 액세스할 수 있도록 이름을 CharacterPortrait로 지정하십시오.

이미지가 가용 공간을 모두 사용하도록 Flex > Grow를 1로 설정하십시오. 또한 올바른 종횡비를 유지하면서 요소 크기에 맞춰 이미지 크기를 확대하거나 축소할 수 있도록 Background > Scale Mode에서 확대/축소 모드를 scale-to-fit으로 변경하십시오.

초상 이미지를 위한 VisualElement
초상 이미지를 위한 VisualElement

다음으로 캐릭터 세부 사항 컨테이너에 레이블 컨트롤 두 개를 추가하십시오. 이러한 컨트롤은 추후 선택한 캐릭터의 이름과 클래스를 표시하는 데 사용합니다. 두 컨트롤의 이름은 각각 CharacterNameCharacterClass로 지정하십시오.

이름과 클래스를 위한 레이블 추가
이름과 클래스를 위한 레이블 추가

캐릭터의 이름이 클래스보다 더 눈에 잘 띄게 하려면 해당 레이블의 폰트 크기를 18로 변경하고 스타일을 bold로 설정하십시오.

폰트 설정 변경
폰트 설정 변경

그러면 UI 화면이 아래 이미지처럼 표시됩니다.

완성된 캐릭터 세부 사항 패널
완성된 캐릭터 세부 사항 패널

마지막으로 오른쪽의 UI 컨테이너에 버튼 컨트롤을 추가하십시오. 추후 컨트롤러 스크립트에서 이 버튼에 액세스하고 캐릭터가 선택되거나 선택 취소되면 이 버튼을 활성화 또는 비활성화하게 됩니다. 버튼의 이름을 SelectCharButton으로 지정하고 너비를 150픽셀로 고정하십시오. 또한 버튼의 레이블 텍스트를 Select Character로 지정하십시오.

캐릭터 선택 버튼 추가
캐릭터 선택 버튼 추가

버튼을 스타일링하려면 배경색을 #FF8554로 설정하고, 너비 2픽셀, #311A11 컬러의 테두리를 추가하십시오. 이 단계는 선택 사항입니다.

그러면 완성된 메인 뷰가 아래 이미지처럼 표시됩니다.

최종 메인 뷰 레이아웃
최종 메인 뷰 레이아웃

UXML 템플릿을 Assets/UI/MainView.uxml로 저장하십시오. 여기의 페이지 하단에 이 템플릿의 최종 UXML 코드가 있습니다.

씬 설정

이 섹션에서는 이전 섹션에서 만든 UI 템플릿을 런타임 시 게임에서 로드하고 표시하는 방법을 알아봅니다.

시작하려면 PanelSettings 에셋을 만들어야 합니다. 이 에셋은 확대/축소 모드와 렌더링 순서와 같은 화면 설정을 정의하며, UI 툴킷 디버거에 UI가 어떤 이름으로 표시되는지도 결정합니다.

새 패널 설정 에셋 만들기
새 패널 설정 에셋 만들기

프로젝트 뷰에서 마우스 오른쪽 버튼을 클릭하여 새 Panel Settings Asset을 만드십시오. Create > UI Toolkit > Panel Settings Asset을 선택하십시오. 새로 만든 파일의 이름을 GameUI_Panel로 지정하십시오. 이 가이드에서는 모든 설정을 기본값으로 둡니다.

기본 PanelSettings 변경 불필요
기본 PanelSettings 변경 불필요

이전 섹션의 메인 뷰 UI 템플릿을 표시하려면 씬에서 새 게임 오브젝트를 만들어야 합니다. 해당 게임 오브젝트에 UIDocument 컴포넌트를 부착하십시오.

UIDocument는 Unity에서 플레이 모드에 진입할 때 할당된 VisualTreeAsset을 자동으로 로드합니다. VisualTreeAsset은 UXML 템플릿입니다. MainView.uxml과 새로운 GameUI_Panel 패널 설정을 모두 컴포넌트에 할당하십시오.

UI Document 컴포넌트
UI Document 컴포넌트
참고
PanelSettings 에셋을 UI Document 컴포넌트에 할당하지 않으면 프로젝트를 자동으로 검색하여 처음으로 찾은 패널 설정 에셋을 자동으로 사용합니다. 에셋의 이름을 변경하거나 에셋을 이동할 때 이 점에 유의하십시오.

이제 Unity 에디터에서 플레이 모드에 진입하여 게임 뷰에 표시된 UI를 확인할 수 있습니다.

런타임 시 표시되는 UI
런타임 시 표시되는 UI
참고
씬에 여러 개의 UI 문서가 있는 경우, 모든 UI 문서에 같은 패널 설정을 할당할 수 있습니다. 그러면 UI가 같은 패널에 렌더링되어 성능이 최적화됩니다.

표시할 샘플 데이터 만들기

이 섹션에서는 UI의 캐릭터 리스트에 데이터를 채우는 데 사용되는 샘플 데이터를 만들어 봅니다.

캐릭터 리스트를 위해 캐릭터 이름, 클래스와 초상 이미지가 포함된 간단한 클래스가 필요합니다. 새 ScriptableObject 스크립트 Assets/Scripts/CharacterData.cs를 만들고 파일에 다음 코드를 붙여넣으십시오.

using UnityEngine;

public enum ECharacterClass
{
  Knight, Ranger, Wizard
}

[CreateAssetMenu]
public class CharacterData : ScriptableObject
{
  public string m_CharacterName;
  public ECharacterClass m_Class;
  public Sprite m_PortraitImage;
}

[CreateAssetMenu] 속성이 Create 메뉴에 자동으로 엔트리를 추가합니다. 프로젝트 뷰의 폴더를 마우스 오른쪽 버튼으로 클릭하여 새 ScriptableObject의 인스턴스를 만드십시오.

새 생성 메뉴 엔트리
새 생성 메뉴 엔트리

이제 CharacterData 인스턴스를 몇 개 만들고 무작위 데이터로 채워야 합니다. 모든 CharacterData 인스턴스를 Resources/Characters 폴더에 넣으십시오. 추후 이 폴더에서 모든 캐릭터 데이터를 자동으로 파싱하고 로드하는 스크립트를 작성합니다.

몇 개의 샘플 캐릭터 만들기
몇 개의 샘플 캐릭터 만들기

리스트 엔트리 UI 템플릿 만들기

이 섹션에서는 리스트의 개별 엔트리를 위한 UI 템플릿을 만듭니다. 런타임 시 컨트롤러 스크립트가 각 캐릭터에 대해 이 UI의 인스턴스를 만들고 리스트에 추가합니다. 캐릭터 리스트 엔트리의 UI는 컬러가 적용된 배경 프레임과 캐릭터 이름으로 구성됩니다.

캐릭터 이름이 표시된 리스트 엔트리
캐릭터 이름이 표시된 리스트 엔트리
참고
이 단계를 건너뛰고 싶다면 이 페이지 맨 아래에서 리스트 엔트리를 위한 UXML 코드를 복사하여 새 파일에 바로 붙여넣고 Assets/UI/ListEntry.uxml로 저장하십시오.

Window > UI Toolkit > UI Builder 메뉴에서 UI 빌더 창을 여십시오. File > New*를 선택하여 새 UXML 템플릿을 만드십시오.

UI 빌더에서 새 UXML 템플릿 만들기
UI 빌더에서 새 UXML 템플릿 만들기

배경을 위한 VisualElement를 추가하고, 높이를 41픽셀로 고정하십시오. 엔트리 내 텍스트는 왼쪽으로 정렬하여 요소 중앙에 배치해야 하므로, Align 폴드아웃을 열고 Align Itemsleft로, Justify Contentcenter로 설정하십시오. 또한 10픽셀의 왼쪽 패딩을 설정하여 레이블이 프레임의 왼쪽 테두리와 최소한의 간격을 유지하도록 하십시오.

스타일링의 경우, 배경색을 #AA5939로 설정하고 너비가 2픽셀, 반지름이 15픽셀이며 컬러가 #311A11로 설정된 테두리를 추가할 수 있습니다. 이 단계는 선택 사항이며, 원하는 컬러와 스타일링을 적용할 수 있습니다.

배경 VisualElement
배경 VisualElement

기존 VisualElement의 자식으로 레이블을 추가하고 향후 컨트롤러 스크립트에서 액세스할 수 있도록 이름을 CharacterName으로 지정하십시오. Font Stylebold로 설정하고 폰트 크기는 18로 설정하십시오.

캐릭터 이름을 위한 레이블 추가
캐릭터 이름을 위한 레이블 추가

UXML 템플릿을 Assets/UI/ListEntry.uxml로 저장하십시오. 여기의 페이지 하단에 이 템플릿의 최종 UXML 코드가 있습니다.

리스트 엔트리를 위한 컨트롤러 만들기

이 섹션에서는 리스트 엔트리를 위한 컨트롤러 스크립트를 만듭니다. 스크립트의 목적은 리스트 엔트리의 UI에 캐릭터 인스턴스의 데이터를 표시하는 것입니다. 이 스크립트는 캐릭터 이름을 위한 레이블에 액세스하여 주어진 캐릭터 인스턴스의 이름을 표시하도록 설정해야 합니다.

새 스크립트 Assets/Scripts/UI/CharacterListEntryController.cs를 만들고 다음 코드를 붙여넣으십시오.

using UnityEngine.UIElements;

public class CharacterListEntryController
{
  private Label m_NameLabel;

  public void SetVisualElement(VisualElement rootEleme)
  {
    m_NameLabel = visualElement.Q<Label>("CharacterName");
  }

  public void SetCharacterData(CharacterData characterData)
  {
    m_NameLabel.text = characterData.m_CharacterName;
  }
}

이 클래스에는 두 개의 함수가 있으며, 두 함수 모두 Set 함수입니다.

SetvisualElement(VisualElement visualElement) 이 함수는 이전 섹션에서 만든 ListEntry UI 템플릿의 인스턴스인 시각적 요소를 수신합니다. 메인 뷰 컨트롤러가 이 인스턴스를 만듭니다. 이 함수의 목적은 UI 요소 내에서 캐릭터 이름 레이블에 대한 레퍼런스를 검색하여 가져오는 것입니다.

SetCharacterData(CharacterData characterData) 이 함수는 이 리스트 요소가 표시해야 할 캐릭터 이름을 수신합니다. ListView의 요소 리스트는 모아서 재사용되므로, 표시할 캐릭터의 데이터를 변경하려면 Set 함수가 있어야 합니다.

CharacterListEntry 클래스는 MonoBehaviour가 아니라는 점에 유의하십시오. UI 툴킷의 시각적 요소는 게임 오브젝트가 아니므로, 이러한 시각적 요소에 컴포넌트를 부착할 수 없습니다. 대신 이 클래스는 다음 섹션의 userData 프로퍼티에 부착됩니다.

메인 뷰를 위한 컨트롤러 만들기

이 섹션에서는 메인 뷰의 캐릭터 리스트를 위한 컨트롤러 스크립트를 만들고, 이러한 컨트롤러 스크립트를 인스턴스화하고 시각적 트리에 할당하는 MonoBehaviour 스크립트를 만듭니다.

시작하려면 Assets/Scripts/UI/CharacterListController.cs 아래에 새 스크립트를 만들고 다음 코드를 붙여넣으십시오.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class CharacterListController
{
  public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
  {
  }
}

InitializeCharacterList() 메서드는 나중에 입력합니다. 하지만 다음 섹션에서 호출할 수 있도록 지금 빈 메서드를 추가해야 합니다.

메인 뷰에 컨트롤러 스크립트 부착

CharacterListEntryController와 마찬가지로 CharacterListControllerMonoBehaviour가 아니며, 다른 방식으로 시각적 트리에 부착해야 합니다. UIDocument와 같은 게임 오브젝트에 부착할 수 있는 MonoBehaviour 스크립트를 만들어야 합니다. 이 스크립트는 CharacterListController를 인스턴스화하고 시각적 트리에 부착됩니다.

새 스크립트 Assets/Scripts/UI/MainView.cs를 만들고 다음 코드를 붙여넣으십시오.

using UnityEngine;
using UnityEngine.UIElements;

public class MainView : MonoBehaviour
{
  [SerializeField] private VisualTreeAsset m_ListEntryTemplate;

  void OnEnable()
  {
    // The UXML is already instantiated by the UIDocument component
    var uiDocument = GetComponent<UIDocument>();

    // Initialize the character list controller
    var characterListController = new CharacterListController();
    characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
  }
}

Unity 에디터로 이동하여 UIDocument가 있는 게임 오브젝트에 스크립트를 부착하십시오. ListEntry.uxmlList Entry Template 프로퍼티에 할당하십시오.

메인 뷰 스크립트 추가 및 레퍼런스 할당
메인 뷰 스크립트 추가 및 레퍼런스 할당

MainView UXML 인스턴스화는 같은 게임 오브젝트의 UIDocument 컴포넌트에서 자동으로 수행되므로, 스크립트 컴포넌트가 이를 수행할 필요가 없습니다. MainView 스크립트는 이미 인스턴스화된 시각적 트리의 레퍼런스를 얻기 위해 UI Document 컴포넌트에 액세스합니다. 그런 다음 이 스크립트는 CharacterListController의 인스턴스를 생성하여 시각적 트리의 루트 요소와 개별 리스트 요소에 사용되는 UXML 템플릿을 전달합니다.

참고
UI가 리로드되면 UIDocument 컴포넌트를 포함하는 같은 게임 오브젝트의 컴패니언 MonoBehaviour 컴포넌트가 리로드 전에 비활성화된 다음 리로드 후 다시 활성화됩니다. 따라서 이러한 MonoBehaviour의 OnEnableOnDisable 메서드에 UI와 상호작용하는 코드를 넣는 것이 좋습니다.

모든 캐릭터 데이터 인스턴스 열거

컨트롤러 스크립트에 가장 먼저 추가해야 할 기능은 앞서 만든 모든 캐릭터 데이터 인스턴스를 열거하는 함수입니다. 이러한 인스턴스는 리스트를 채우는 데 사용됩니다.

아래 코드를 복사하여 CharacterListController 클래스에 넣으십시오.

private List<CharacterData> m_AllCharacters;

private void EnumerateAllCharacters()
{
  m_AllCharacters = new List<CharacterData>();
  m_AllCharacters.AddRange(Resources.LoadAll<CharacterData>("Characters"));
}
참고
이 코드는 Resources/Characters 폴더에 캐릭터 인스턴스가 생성되었음을 가정합니다. 캐릭터를 다른 폴더에 넣었다면 폴더 이름을 Resources/Characters로 변경해야 할 수 있습니다.

이제 초기화 중 EnumerateAllCharacter 메서드를 호출해야 합니다. InitializeCharacterList 메서드의 맨 위에 EnumerateAllCharacter의 호출을 추가하십시오.

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  // Enumerate all characters
  EnumerateAllCharacters();
}

UI 요소에 대한 참조 획득하기

이 섹션에서는 InitializeCharacterList 메서드의 콘텐츠를 채워봅니다. 이 메서드에 필요한 첫 번째 사항은 정보를 표시하기 위해 액세스해야 하는 모든 개별 UI 컨트롤에 대한 레퍼런스를 획득하는 것입니다. 이름, USS 클래스, 타입 또는 이의 조합으로 개별 UI 컨트롤을 검색해서 가져오려면 UQuery API 패밀리를 사용하십시오.

CharacterListController 클래스 내 코드를 아래 코드로 확장하십시오.

// UXML template for list entries
private VisualTreeAsset m_ListEntryTemplate;

// UI element references
private ListView m_CharacterList;
private Label m_CharClassLabel;
private Label m_CharNameLabel;
private VisualElement m_CharPortrait;
private Button m_SelectCharButton;

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  // Enumerate all characters
  EnumerateAllCharacters();

  // Store a reference to the template for the list entries
  m_ListEntryTemplate = listElementTemplate;

  // Store a reference to the character list element
  m_CharacterList = root.Q<ListView>("CharacterList");

  // Store references to the selected character info elements
  m_CharClassLabel = root.Q<Label>("CharacterClass");
  m_CharNameLabel = root.Q<Label>("CharacterName");
  m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

  // Store a reference to the select button
  m_SelectCharButton = root.Q<Button>("SelectCharButton");
}

엔트리로 리스트 채우기

다음으로 화면의 리스트를 앞서 열거하고 로드한 캐릭터로 채워야 합니다. 이를 수행하려면 CharacterListController 클래스 안에 FillCharacterList 메서드를 새로 만들어야 합니다.

ListView를 요소로 채우는 작업은 다음 4단계를 따르십시오.

  1. makeItem 함수를 만듭니다.
  2. bindItem 함수를 만듭니다.
  3. 항목의 높이를 설정합니다.
  4. 항목의 소스를 설정합니다.

makeItem 콜백 함수의 목적은 UI에 하나의 리스트 항목을 나타내는 작은 시각적 트리를 만들고, 이 트리의 루트 VisualElement를 반환하는 것입니다.

이 경우에는 makeItem 콜백이 리스트 엔트리를 위해 만든 UXML 템플릿을 인스턴스화해야 합니다. 또한 UI를 CharacterData의 데이터로 채우는 CharacterListEntryController 컨트롤러 스크립트의 인스턴트도 만들어야 합니다.

클래스 안에 FillCharacterList 메서드를 만들고 아래 코드를 붙여넣으십시오.

private void FillCharacterList()
{
  // Set up a make item function for a list entry
  m_CharacterList.makeItem = () =>
  {
    // Instantiate the UXML template for the entry
    var newListEntry = m_ListEntryTemplate.Instantiate();

    // Instantiate a controller for the data
    var newListEntryLogic = new CharacterListEntryController();

    // Assign the controller script to the visual element
    newListEntry.userData = newListEntryLogic;
    
    // Initialize the controller script
    newListEntryLogic.SetVisualElement(newListEntry);

    // Return the root of the instantiated visual tree
    return newListEntry;
  };
}

makeItem 콜백의 일부로 인스턴스화된 시각적 요소의 userData 프로퍼티 안에 컨트롤러 스크립트를 저장하십시오. 그러면 추후 스크립트에 액세스하여 리스트 요소에 여러 캐릭터를 할당할 수 있습니다.

// Assign the controller script to the visual element
newListEntry.userData = newListEntryLogic;

ListView는 메모리 및 성능 최적화를 위해 리스트의 엔트리마다 별도의 요소를 인스턴스화하는 대신 리스트 요소를 재사용합니다. 또한 보이는 영역을 채울 만큼의 시각적 요소만 만들고, 리스트를 스크롤하면 만든 시각적 요소를 모아 재사용합니다.

따라서 데이터의 인스턴스(이 경우에는 CharacterData)를 개별 리스트 요소에 바인드하는 bindItem 콜백을 제공해야 합니다.

하단의 코드를 추가하여 FillCharacterList 메서드를 확장하십시오.

private void FillCharacterList()
{
  ...

  // Set up bind function for a specific list entry
  m_CharacterList.bindItem = (item, index) =>
  {
    (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
  };
}

bindItem 콜백은 리스트 엔트리의 시각적 트리를 위한 루트 시각적 요소의 레퍼런스를 수신하며, 데이터를 위한 인덱스도 수신합니다. 시각적 요소의 userData 프로퍼티에 CharacterListEntryController에 대한 레퍼런스를 저장했으므로, 코드가 이에 액세스하여 CharacterData를 바로 설정할 수 있습니다.

마지막으로 요소의 항목 높이를 설정하고 리스트를 위한 데이터 소스의 레퍼런스를 제공해야 합니다. 이는 리스트에 포함된 요소의 수를 리스트에 알립니다.

하단의 코드를 추가하여 FillCharacterList 메서드를 확장하십시오.

private void FillCharacterList()
{
  ...

  // Set a fixed item height
  m_CharacterList.fixedItemHeight = 45;
  // For Unity versions earlier than 2021.2 use this:
  //m_CharacterList.itemHeight = 45; 

  // Set the actual item's source list/array
  m_CharacterList.itemsSource = m_AllCharacters;
}

마지막으로 초기화 끝에 FillCharacterList 메서드를 호출해야 합니다. 아래대로 InitializeCharacterList 메서드의 하단에 호출을 추가하십시오.

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  ...

  FillCharacterList();
}

지금 플레이 모드에 진입하면 캐릭터 리스트가 만든 캐릭터의 이름으로 채워집니다.

더 이상 비어있지 않은 캐릭터 리스트
더 이상 비어있지 않은 캐릭터 리스트

여기 이 가이드의 하단에서 CharacterListController 스크립트의 최종 코드를 확인하실 수 있습니다.

사용자 선택에 응답

사용자가 캐릭터를 선택하면 캐릭터의 세부 사항, 즉 초상, 성명과 클래스가 화면 오른쪽의 캐릭터 세부 사항 섹션에 표시되어야 합니다. 또한 캐릭터가 선택되면 선택 버튼이 활성화되어야 합니다. 선택된 캐릭터가 없으면 버튼이 다시 비활성화되어야 합니다.

이미 리스트의 캐릭터를 클릭하고 선택할 수 있음에 유의하십시오. 선택 및 강조 표시 기능은 ListView 컨트롤의 일부입니다. 사용자가 리스트의 선택 항목을 변경하면 응답하는 콜백 함수만 추가하면 됩니다. ListView 컨트롤은 이를 위한 onSelectionChange 이벤트를 포함합니다.

다음 코드를 InitializeCharacterList 메서드 하단에 추가하십시오.

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  ...

  // Register to get a callback when an item is selected
  m_CharacterList.onSelectionChange += OnCharacterSelected;
}

이제 상기 코드에서 설정한 OnCharacterSelected 콜백 함수를 구현해야 합니다. 이 함수는 리스트에서 선택한 모든 항목을 수신합니다. 그러나 리스트는 단일 항목 선택만 허용하므로, 리스트의 selectedItem 프로퍼티를 통해 선택한 항목에 직접 액세스할 수 있습니다.

아래 코드를 복사하여 클래스에 넣으십시오.

private void OnCharacterSelected(IEnumerable<object> selectedItems)
{
  // Get the currently selected item directly from the ListView
  var selectedCharacter = m_CharacterList.selectedItem as CharacterData;
}

selectedItem 프로퍼티가 null을 반환할 수 있습니다. 아무것도 선택되지 않았거나 사용자가 모든 항목을 선택 취소하기 위해 ESC 키를 누르는 경우가 이에 해당합니다. 이 사례는 우선적으로 처리해야 합니다.

아래대로 OnCharacterSelected 메서드를 확장하십시오.

private void OnCharacterSelected(IEnumerable<object> selectedItems)
{
  // Get the currently selected item directly from the ListView
  var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

  // Handle none-selection (Escape to deselect everything)
  if (selectedCharacter == null)
  {
    // Clear
    m_CharClassLabel.text = "";
    m_CharNameLabel.text = "";
    m_CharPortrait.style.backgroundImage = null;

    // Disable the select button
    m_SelectCharButton.SetEnabled(false);

    return;
  }
}

선택이 유효하면 UI에 캐릭터의 세부 사항을 표시해야 합니다. 클래스의 InitializeCharacterList 메서드에서 검색해서 가져온 레퍼런스를 통해 레이블 및 세로 이미지 시각적 요소에 액세스할 수 있습니다.

아래 코드를 복사하여 OnCharacterSelected 메서드에 넣으십시오.

private void OnCharacterSelected(IEnumerable<object> selectedItems)
{
  ...

  // Fill in character details
  m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
  m_CharNameLabel.text = selectedCharacter.m_CharacterName;
  m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

  // Enable the select button
  m_SelectCharButton.SetEnabled(true);
}

이제 플레이 모드에 진입하여 실제 캐릭터 선택 리스트를 확인할 수 있습니다. 캐릭터를 선택 취소하려면 Escape 키를 누르십시오.

최종 런타임 UI
최종 런타임 UI

Final scripts

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

MainView.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement style="flex-grow: 1; align-items: center; justify-content: center; background-color: rgb(115, 37, 38);">
        <ui:VisualElement style="flex-direction: row; height: 350px;">
            <ui:ListView focusable="true" name="CharacterList" style="width: 230px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-left-width: 4px; border-right-width: 4px; border-top-width: 4px; border-bottom-width: 4px; background-color: rgb(110, 57, 37); border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; margin-right: 6px;" />
            <ui:VisualElement style="justify-content: space-between; align-items: flex-end;">
                <ui:VisualElement style="align-items: center; background-color: rgb(170, 89, 57); border-left-width: 4px; border-right-width: 4px; border-top-width: 4px; border-bottom-width: 4px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; width: 276px; justify-content: center; padding-left: 8px; padding-right: 8px; padding-top: 8px; padding-bottom: 8px;">
                    <ui:VisualElement style="border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; height: 120px; width: 120px; border-top-left-radius: 13px; border-bottom-left-radius: 13px; border-top-right-radius: 13px; border-bottom-right-radius: 13px; padding-left: 4px; padding-right: 4px; padding-top: 4px; padding-bottom: 4px; background-color: rgb(255, 133, 84);">
                        <ui:VisualElement name="CharacterPortrait" style="flex-grow: 1; -unity-background-scale-mode: scale-to-fit;" />
                    </ui:VisualElement>
                    <ui:Label text="Label" name="CharacterName" style="-unity-font-style: bold; font-size: 18px;" />
                    <ui:Label text="Label" display-tooltip-when-elided="true" name="CharacterClass" style="margin-top: 2px; margin-bottom: 8px; padding-top: 0; padding-bottom: 0;" />
                </ui:VisualElement>
                <ui:Button text="Select Character" display-tooltip-when-elided="true" name="SelectCharButton" style="width: 150px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); background-color: rgb(255, 133, 84); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px;" />
            </ui:VisualElement>
        </ui:VisualElement>
    </ui:VisualElement>
</ui:UXML>

ListEntry.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement style="height: 41px; align-items: flex-start; justify-content: center; padding-left: 10px; background-color: rgba(170, 89, 57, 255); border-left-color: rgba(49, 26, 17, 255); border-right-color: rgba(49, 26, 17, 255); border-top-color: rgba(49, 26, 17, 255); border-bottom-color: rgba(49, 26, 17, 255); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px;">
        <ui:Label text="Label" display-tooltip-when-elided="true" name="CharacterName" style="-unity-font-style: bold; font-size: 18px;" />
    </ui:VisualElement>
</ui:UXML>

CharacterData.cs

using UnityEngine;

public enum ECharacterClass
{
  Knight, Ranger, Wizard
}

[CreateAssetMenu]
public class CharacterData : ScriptableObject
{
  public string m_CharacterName;
  public ECharacterClass m_Class;
  public Sprite m_PortraitImage;
}

CharacterListEntryController.cs

using UnityEngine.UIElements;

public class CharacterListEntryController
{
  private Label m_NameLabel;

  public void SetVisualElement(VisualElement visualElement)
  {
    m_NameLabel = visualElement.Q<Label>("CharacterName");
  }

  public void SetCharacterData(CharacterData characterData)
  {
    m_NameLabel.text = characterData.m_CharacterName;
  }
}

MainView.cs

using UnityEngine;
using UnityEngine.UIElements;

public class MainView : MonoBehaviour
{
  [SerializeField] private VisualTreeAsset m_ListEntryTemplate;

  void OnEnable()
  {
    // The UXML is already instantiated by the UIDocument component
    var uiDocument = GetComponent<UIDocument>();

    // Initialize the character list controller
    var characterListController = new CharacterListController();
    characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
  }
}

CharacterListController.cs

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class CharacterListController
{
  private List<CharacterData> m_AllCharacters;

  // UXML template for list entries
  private VisualTreeAsset m_ListEntryTemplate;

  // UI element references
  private ListView m_CharacterList;
  private Label m_CharClassLabel;
  private Label m_CharNameLabel;
  private VisualElement m_CharPortrait;
  private Button m_SelectCharButton;

  private void EnumerateAllCharacters()
  {
    m_AllCharacters = new List<CharacterData>();
    m_AllCharacters.AddRange(Resources.LoadAll<CharacterData>("Characters"));
  }

  public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
  {
    // Enumerate all characters
    EnumerateAllCharacters();

    // Store a reference to the template for the list entries
    m_ListEntryTemplate = listElementTemplate;

    // Store a reference to the character list element
    m_CharacterList = root.Q<ListView>("CharacterList");

    // Store references to the selected character info elements
    m_CharClassLabel = root.Q<Label>("CharacterClass");
    m_CharNameLabel = root.Q<Label>("CharacterName");
    m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

    // Store a reference to the select button
    m_SelectCharButton = root.Q<Button>("SelectCharButton");

    FillCharacterList();

    // Register to get a callback when an item is selected
    m_CharacterList.onSelectionChange += OnCharacterSelected;
  }

  private void OnCharacterSelected(IEnumerable<object> selectedItems)
  {
    // Get the currently selected item directly from the ListView
    var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

    // Handle none-selection (Escape to deselect everything)
    if (selectedCharacter == null)
    {
      // Clear
      m_CharClassLabel.text = "";
      m_CharNameLabel.text = "";
      m_CharPortrait.style.backgroundImage = null;

      // Disable the select button
      m_SelectCharButton.SetEnabled(false);

      return;
    }

    // Fill in character details
    m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
    m_CharNameLabel.text = selectedCharacter.m_CharacterName;
    m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

    // Enable the select button
    m_SelectCharButton.SetEnabled(true);
  }

  private void FillCharacterList()
  {
    // Set up a make item function for a list entry
    m_CharacterList.makeItem = () =>
    {
      // Instantiate the UXML template for the entry
      var newListEntry = m_ListEntryTemplate.Instantiate();

      // Instantiate a controller for the data
      var newListEntryLogic = new CharacterListEntryController();

      // Assign the controller script to the visual element
      newListEntry.userData = newListEntryLogic;

      // Initialize the controller script
      newListEntryLogic.SetVisualElement(newListEntry);

      // Return the root of the instantiated visual tree
      return newListEntry;
    };

    // Set up bind function for a specific list entry
    m_CharacterList.bindItem = (item, index) =>
    {
      (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
    };

    // Set a fixed item height
    m_CharacterList.fixedItemHeight = 45;
    // For Unity versions earlier than 2021.2 use this:
    //m_CharacterList.itemHeight = 45; 

    // Set the actual item's source list/array
    m_CharacterList.itemsSource = m_AllCharacters;
  }
}
커스텀 인스펙터 생성
Unity UI(UGUI)에서 UI 툴킷으로의 전환