콘텐츠로 이동

Unity UI Toolkit Runtime 활용

UI Toolkit(구 UIElements)은 Unity 2021.2부터 런타임에서 공식 지원되는 UI 시스템입니다. Unity Editor UI와 동일한 기술 기반으로, HTML/CSS와 유사한 UXML/USS를 사용해 UI를 선언적으로 정의합니다.

기존 uGUI 대비 장점:

  • UXML: 구조와 코드 분리
  • USS: CSS와 유사한 스타일 시트
  • 데이터 바인딩 내장
  • 에디터/런타임 통일된 API

  1. UI Document 컴포넌트를 GameObject에 추가
  2. .uxml 파일을 Panel Settings와 함께 연결
  3. C# 스크립트로 루트 요소 접근
using UnityEngine;
using UnityEngine.UIElements;
public class HUDController : MonoBehaviour
{
private UIDocument _document;
private VisualElement _root;
void OnEnable()
{
_document = GetComponent<UIDocument>();
_root = _document.rootVisualElement;
// UXML에서 정의한 요소 쿼리
var hpBar = _root.Q<ProgressBar>("hp-bar");
var mpBar = _root.Q<ProgressBar>("mp-bar");
var scoreLabel = _root.Q<Label>("score-label");
var settingsBtn = _root.Q<Button>("settings-btn");
settingsBtn.clicked += OnSettingsClicked;
}
void OnDisable()
{
var settingsBtn = _root.Q<Button>("settings-btn");
if (settingsBtn != null)
settingsBtn.clicked -= OnSettingsClicked;
}
private void OnSettingsClicked() => Debug.Log("Settings opened");
}

<!-- Assets/UI/HUD.uxml -->
<ui:UXML xmlns:ui="UnityEngine.UIElements"
xmlns:uie="UnityEditor.UIElements">
<ui:VisualElement name="hud-root" class="hud-container">
<!-- 체력 / 마나 바 -->
<ui:VisualElement name="stat-bars">
<ui:ProgressBar name="hp-bar" title="HP"
value="100" high-value="100"
class="stat-bar hp-bar" />
<ui:ProgressBar name="mp-bar" title="MP"
value="75" high-value="100"
class="stat-bar mp-bar" />
</ui:VisualElement>
<!-- 스코어 -->
<ui:Label name="score-label" text="Score: 0" class="score" />
<!-- 스킬 슬롯 -->
<ui:VisualElement name="skill-slots" class="skill-bar">
<ui:Button name="skill-1" class="skill-slot" />
<ui:Button name="skill-2" class="skill-slot" />
<ui:Button name="skill-3" class="skill-slot" />
<ui:Button name="skill-4" class="skill-slot" />
</ui:VisualElement>
<!-- 설정 버튼 -->
<ui:Button name="settings-btn" text="" class="icon-btn" />
</ui:VisualElement>
</ui:UXML>

/* Assets/UI/HUD.uss */
.hud-container {
position: absolute;
width: 100%;
height: 100%;
padding: 16px;
flex-direction: column;
justify-content: space-between;
}
.stat-bars {
width: 250px;
flex-direction: column;
gap: 4px;
}
.stat-bar {
height: 24px;
border-radius: 12px;
border-width: 2px;
border-color: rgba(0, 0, 0, 0.6);
}
.hp-bar > .unity-progress-bar__background {
background-color: rgba(0, 0, 0, 0.4);
border-radius: 12px;
}
.hp-bar > .unity-progress-bar__progress {
background-color: rgb(220, 50, 50);
border-radius: 12px;
transition-property: width;
transition-duration: 0.3s;
transition-timing-function: ease-out;
}
.mp-bar > .unity-progress-bar__progress {
background-color: rgb(50, 100, 220);
border-radius: 12px;
}
.score {
font-size: 24px;
color: white;
-unity-font-style: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
.skill-bar {
flex-direction: row;
justify-content: center;
gap: 8px;
padding-bottom: 16px;
}
.skill-slot {
width: 64px;
height: 64px;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.6);
border-color: rgba(255, 255, 255, 0.3);
border-width: 2px;
}
.skill-slot:hover {
border-color: rgb(255, 200, 50);
scale: 1.05;
}

public class GameHUD : MonoBehaviour
{
private ProgressBar _hpBar;
private ProgressBar _mpBar;
private Label _scoreLabel;
private int _score;
void OnEnable()
{
var root = GetComponent<UIDocument>().rootVisualElement;
_hpBar = root.Q<ProgressBar>("hp-bar");
_mpBar = root.Q<ProgressBar>("mp-bar");
_scoreLabel = root.Q<Label>("score-label");
}
public void SetHP(float current, float max)
{
_hpBar.highValue = max;
_hpBar.value = current;
// HP 비율에 따라 색상 변경
float ratio = current / max;
var progress = _hpBar.Q(className: "unity-progress-bar__progress");
if (progress != null)
{
progress.style.backgroundColor = ratio > 0.5f
? Color.green
: ratio > 0.25f ? Color.yellow : Color.red;
}
}
public void AddScore(int points)
{
_score += points;
_scoreLabel.text = $"Score: {_score:N0}";
// 점수 증가 애니메이션 효과
_scoreLabel.RemoveFromClassList("score-pop");
_scoreLabel.schedule.Execute(() =>
_scoreLabel.AddToClassList("score-pop")).ExecuteLater(10);
}
}

// UXML 없이 코드로 UI 생성
public class InventoryUI : MonoBehaviour
{
[SerializeField] private InventoryData _inventory;
void OnEnable()
{
var root = GetComponent<UIDocument>().rootVisualElement;
BuildInventoryGrid(root);
}
void BuildInventoryGrid(VisualElement root)
{
var grid = new VisualElement();
grid.name = "inventory-grid";
grid.style.flexDirection = FlexDirection.Row;
grid.style.flexWrap = Wrap.Wrap;
foreach (var item in _inventory.Items)
{
var slot = CreateItemSlot(item);
grid.Add(slot);
}
root.Add(grid);
}
VisualElement CreateItemSlot(ItemData item)
{
var slot = new VisualElement();
slot.AddToClassList("item-slot");
slot.tooltip = item.Description;
var icon = new Image { sprite = item.Icon };
icon.AddToClassList("item-icon");
var count = new Label(item.Count > 1 ? item.Count.ToString() : "");
count.AddToClassList("item-count");
slot.Add(icon);
slot.Add(count);
// 클릭 이벤트
slot.RegisterCallback<ClickEvent>(_ => OnItemClicked(item));
// 드래그 앤 드롭
slot.RegisterCallback<MouseDownEvent>(e =>
{
if (e.button == 0) StartDrag(slot, item);
});
return slot;
}
private void OnItemClicked(ItemData item) => Debug.Log($"Clicked: {item.Name}");
private void StartDrag(VisualElement slot, ItemData item) { /* 드래그 구현 */ }
}

void RegisterEvents(VisualElement root)
{
var panel = root.Q("settings-panel");
// 마우스 이벤트
panel.RegisterCallback<MouseEnterEvent>(e =>
panel.style.backgroundColor = new StyleColor(new Color(0, 0, 0, 0.8f)));
panel.RegisterCallback<MouseLeaveEvent>(e =>
panel.style.backgroundColor = new StyleColor(new Color(0, 0, 0, 0.6f)));
// 키보드 이벤트 (포커스 필요)
panel.focusable = true;
panel.RegisterCallback<KeyDownEvent>(e =>
{
if (e.keyCode == KeyCode.Escape)
panel.style.display = DisplayStyle.None;
});
// 포인터 이벤트 (터치 지원)
panel.RegisterCallback<PointerDownEvent>(e =>
{
Debug.Log($"Pointer down at {e.position}");
e.StopPropagation(); // 이벤트 버블링 중단
});
}

개념역할
UIDocumentUXML 파일과 씬을 연결
VisualElement모든 UI 요소의 기반 클래스
UXMLHTML과 유사한 UI 구조 선언
USSCSS와 유사한 스타일 정의
Q<T>("name")요소 쿼리 (타입 + 이름)
RegisterCallback<T>이벤트 리스너 등록

UI Toolkit은 선언적 구조와 스타일 분리로 복잡한 UI를 체계적으로 관리할 수 있게 합니다. UGUI의 GameObject 기반 방식보다 대규모 UI 시스템에서 유리합니다.