mcpField
U
unity-ui

unity-ui

github.com/nice-wolf-studio设计与 UI4068 次安装6 周前更新
代码仓库

关于 unity-ui

面向 Unity UI 开发的参考技能,介绍 UI Toolkit、uGUI 和 IMGUI 的适用场景与实现方式。适用于构建运行时界面和编辑器扩展,并涵盖布局、事件、数据绑定及文本渲染。

主要功能

  1. 01比较三种 Unity UI 系统
  2. 02使用 UXML、USS 与 C# 构建 UI Toolkit 界面
  3. 03介绍事件传播、数据绑定和交互操控器
  4. 04覆盖 uGUI Canvas、布局与锚点
  5. 05提供 UI 性能与事件清理建议
  6. 06推荐使用 TextMeshPro 渲染文本

适用场景

  1. 01为新项目选择运行时 UI 框架
  2. 02构建 Unity 编辑器窗口和 Inspector
  3. 03为 uGUI 项目规划渐进迁移
  4. 04实现菜单、HUD 与世界空间界面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-ui

安装后在 Claude Code 中输入/unity-ui确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
textmeshpro.md
ugui-legacy.md
ui-data-binding.md
ui-toolkit-guide.md

详细介绍

Unity UI 系统

Unity 提供三种 UI 框架。对于新项目,推荐使用 UI Toolkit。 uGUI 仍受支持,适用于旧项目和某些运行时场景。IMGUI 严格限用于 Editor 工具和调试。

UI 系统对比

特性UI ToolkituGUI(Canvas)IMGUI
推荐用于新项目是否(旧版)否
运行时游戏 UI是是不推荐
Editor 扩展是否是
实现方式受 Web 启发(UXML + USS + C#)GameObject + Component纯代码(OnGUI)
布局系统Flexbox(Yoga)RectTransform + 锚点即时模式
样式USS 样式表按组件设置属性GUIStyle / GUISkin
可视化编辑UI BuilderScene View无
性能优化的保留模式Canvas 批处理每帧重绘
数据绑定SerializedObject + 运行时绑定通过代码手动处理通过代码手动处理
世界空间 UI支持Canvas World Space 模式不支持
输入集成指针/键盘事件EventSystem + RaycastersEvent.current

选择指南:

  • 新建运行时 UI(菜单、HUD、物品栏)--> UI Toolkit
  • 新建 Editor 窗口/检查器 --> UI Toolkit
  • 使用 uGUI 的现有项目 --> 继续使用 uGUI,逐步迁移
  • 在 Editor 中快速创建调试叠加层 --> IMGUI
  • 3D 对象上的世界空间 UI --> 可使用 UI Toolkit 或 uGUI World Space Canvas

UI Toolkit

UI Toolkit 是 Unity 的现代 UI 框架,受 Web 技术启发。它使用 UXML 定义结构、使用 USS 设置样式,并使用 C# 编写逻辑。

核心架构
UIDocument (MonoBehaviour on GameObject)
  --> VisualTreeAsset (.uxml)  -- defines structure
  --> StyleSheet (.uss)         -- defines appearance
  --> C# script                 -- defines behavior

所有 UI 元素都继承自 VisualElement。可通过 rootVisualElement 访问根元素。

UXML 结构

UXML 以声明式方式定义 UI 层级结构:

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    <ui:Style src="MainMenu.uss" />
    <ui:VisualElement name="root-container" class="container">
        <ui:Label text="Game Menu" class="title" />
        <ui:Button text="Play" name="play-button" class="menu-btn" />
        <ui:Button text="Settings" name="settings-button" class="menu-btn" />
        <ui:Toggle label="Fullscreen" name="fullscreen-toggle" />
        <ui:Slider label="Volume" low-value="0" high-value="100" name="volume-slider" />
        <ui:TextField label="Player Name" name="player-name" />
    </ui:VisualElement>
</ui:UXML>

要点:

  • xmlns:ui="UnityEngine.UIElements" 是标准命名空间
  • 使用 <ui:Style src="..." /> 引用 USS 文件
  • 使用 name 属性供 C# 查询,使用 class 设置 USS 样式
  • 可导入模板:<ui:Template src="other.uxml" name="other" />
USS 样式

USS 使用类似 CSS 的语法,并扩展了 Unity 专用功能。所有 USS 属性都使用前缀 -unity- 来表示 Unity 专用功能。

/* Type selector */
Button {
    background-color: #2D2D2D;
    border-radius: 4px;
    padding: 8px 16px;
    -unity-font-style: bold;
}

/* Class selector */
.menu-btn {
    width: 200px;
    height: 40px;
    margin: 4px 0;
    font-size: 16px;
    color: #FFFFFF;
}

/* Name selector */
#play-button {
    background-color: #4CAF50;
}

/* Pseudo-class */
.menu-btn:hover {
    background-color: #555555;
    scale: 1.05 1.05;
}

.menu-btn:active {
    background-color: #333333;
}

.menu-btn:disabled {
    opacity: 0.5;
}

/* Descendant selector */
.container > Label {
    -unity-text-align: middle-center;
}

/* USS variables */
:root {
    --primary-color: #4CAF50;
    --font-large: 24px;
}

.title {
    color: var(--primary-color);
    font-size: var(--font-large);
}

选择器类型: 类型(Button)、名称(#name)、类(.class)、通用(*)、后代(A B)、子代(A > B)、多重(A.class)、伪类(:hover、:active、:focus、:disabled、:checked)。

布局基于 Flexbox: 使用 flex-direction、flex-grow、flex-shrink、justify-content、align-items、align-self、flex-wrap。默认方向为 column。

C# 设置与交互
using UnityEngine;
using UnityEngine.UIElements;

public class MainMenuController : MonoBehaviour
{
    [SerializeField] private UIDocument uiDocument;

    private Button playButton;
    private Button settingsButton;
    private Toggle fullscreenToggle;
    private Slider volumeSlider;
    private TextField playerNameField;

    private void OnEnable()
    {
        var root = uiDocument.rootVisualElement;

        // Query single elements by name
        playButton = root.Q<Button>("play-button");
        settingsButton = root.Q<Button>("settings-button");
        fullscreenToggle = root.Q<Toggle>("fullscreen-toggle");
        volumeSlider = root.Q<Slider>("volume-slider");
        playerNameField = root.Q<TextField>("player-name");

        // Register click callbacks
        playButton.RegisterCallback<ClickEvent>(OnPlayClicked);
        settingsButton.RegisterCallback<ClickEvent>(OnSettingsClicked);

        // Register value change callbacks
        fullscreenToggle.RegisterValueChangedCallback(OnFullscreenChanged);
        volumeSlider.RegisterValueChangedCallback(OnVolumeChanged);

        // Query multiple elements by class
        var allButtons = root.Query<Button>(className: "menu-btn").ToList();
    }

    private void OnDisable()
    {
        playButton.UnregisterCallback<ClickEvent>(OnPlayClicked);
        settingsButton.UnregisterCallback<ClickEvent>(OnSettingsClicked);
        fullscreenToggle.UnregisterValueChangedCallback(OnFullscreenChanged);
        volumeSlider.UnregisterValueChangedCallback(OnVolumeChanged);
    }

    private void OnPlayClicked(ClickEvent evt) => Debug.Log("Play clicked");
    private void OnSettingsClicked(ClickEvent evt) => Debug.Log("Settings clicked");

    private void OnFullscreenChanged(ChangeEvent<bool> evt)
    {
        Screen.fullScreen = evt.newValue;
    }

    private void OnVolumeChanged(ChangeEvent<float> evt)
    {
        AudioListener.volume = evt.newValue / 100f;
    }
}

以编程方式创建 UI(无需 UXML):

private void CreateUIFromCode()
{
    var root = uiDocument.rootVisualElement;

    var container = new VisualElement();
    container.AddToClassList("container");
    root.Add(container);

    var label = new Label("Created from C#");
    container.Add(label);

    var button = new Button(() => Debug.Log("Clicked")) { text = "Click Me" };
    button.name = "dynamic-button";
    container.Add(button);
}
事件系统

UI Toolkit 事件分两个阶段传播:

  1. 向下传递 -- 从根元素传递到目标元素
  2. 向上冒泡 -- 从目标元素传回根元素
// Default: bubble-up phase
element.RegisterCallback<PointerDownEvent>(OnPointerDown);

// Trickle-down phase (parent reacts before children)
element.RegisterCallback<PointerDownEvent>(OnPointerDown, TrickleDown.TrickleDown);

// Pass custom data to callbacks
element.RegisterCallback<ClickEvent, string>(OnClickWithData, "my-data");

// Set value without triggering ChangeEvent
myControl.SetValueWithoutNotify(newValue);
数据绑定

SerializedObject 绑定(Editor / 检查器 UI):

// In UXML: <ui:IntegerField binding-path="m_Health" label="Health" />
// In C#:
var healthField = new IntegerField("Health") { bindingPath = "m_Health" };
root.Add(healthField);
root.Bind(new SerializedObject(targetComponent));

可绑定对象:MonoBehaviour、ScriptableObject、Unity 原生类型、基元类型。 只能绑定 INotifyValueChanged 元素的 value 属性。

运行时绑定可将普通 C# 对象连接到 UI 控件,在 Editor 和运行时环境中均可使用。在元素上设置数据源,并定义绑定模式以指定同步方向。

参见:references/ui-data-binding.md

操作器

操作器封装事件处理逻辑,将交互与 UI 代码分离:

public class DragManipulator : PointerManipulator
{
    private Vector3 startPosition;
    private bool isDragging;

    public DragManipulator(VisualElement target)
    {
        this.target = target;
    }

    protected override void RegisterCallbacksOnTarget()
    {
        target.RegisterCallback<PointerDownEvent>(OnPointerDown);
        target.RegisterCallback<PointerMoveEvent>(OnPointerMove);
        target.RegisterCallback<PointerUpEvent>(OnPointerUp);
    }

    protected override void UnregisterCallbacksFromTarget()
    {
        target.UnregisterCallback<PointerDownEvent>(OnPointerDown);
        target.UnregisterCallback<PointerMoveEvent>(OnPointerMove);
        target.UnregisterCallback<PointerUpEvent>(OnPointerUp);
    }

    private void OnPointerDown(PointerDownEvent evt)
    {
        startPosition = evt.position;
        isDragging = true;
        target.CapturePointer(evt.pointerId);
        evt.StopPropagation();
    }

    private void OnPointerMove(PointerMoveEvent evt)
    {
        if (!isDragging) return;
        var delta = evt.position - startPosition;
        target.transform.position += (Vector3)delta;
        startPosition = evt.position;
    }

    private void OnPointerUp(PointerUpEvent evt)
    {
        isDragging = false;
        target.ReleasePointer(evt.pointerId);
        evt.StopPropagation();
    }
}

// Usage:
myElement.AddManipulator(new DragManipulator(myElement));

内置操作器类: Manipulator(基类)、PointerManipulator、MouseManipulator、Clickable、ContextualMenuManipulator、KeyboardNavigationManipulator。

自定义控件
// Unity 6+ recommended pattern: [UxmlElement] attribute (replaces deprecated UxmlFactory/UxmlTraits)
[UxmlElement]
public partial class HealthBar : VisualElement
{
    [UxmlAttribute]
    public float MaxHealth { get; set; } = 100f;

    private VisualElement fillBar;
    private float currentHealth;

    public float CurrentHealth
    {
        get => currentHealth;
        set
        {
            currentHealth = Mathf.Clamp(value, 0, MaxHealth);
            fillBar.style.width = Length.Percent(currentHealth / MaxHealth * 100f);
        }
    }

    public HealthBar()
    {
        AddToClassList("health-bar");
        fillBar = new VisualElement();
        fillBar.AddToClassList("health-bar__fill");
        Add(fillBar);
    }
}

uGUI / Canvas 系统(旧版)

uGUI 是 Unity 较早的基于 GameObject 的 UI 系统。它使用 Canvas、RectTransform 和 EventSystem。

Canvas 渲染模式
模式描述使用场景
Screen Space - Overlay渲染在所有内容之上,并随屏幕缩放标准 HUD、菜单
Screen Space - Camera由指定摄像机渲染,受透视影响带深度效果的 UI
World Space将 Canvas 作为场景中的 3D 对象游戏内显示、VR UI
核心组件

视觉: Text、Image、RawImage 交互: Button、Toggle、ToggleGroup、Slider、Scrollbar、Dropdown、InputField、ScrollRect 布局: HorizontalLayoutGroup、VerticalLayoutGroup、GridLayoutGroup、ContentSizeFitter、AspectRatioFitter、LayoutElement

RectTransform 和锚点

所有 uGUI 元素都使用 RectTransform,而不是 Transform。锚点定义元素相对于父级的位置:

  • Anchor Min/Max 使用分数表示(0.0 = 左侧/底部,1.0 = 右侧/顶部)
  • 锚点重合:固定位置(Pos X、Pos Y、Width、Height)
  • 锚点分离:拉伸(Left、Right、Top、Bottom 内边距)
  • Pivot:用于旋转和缩放的中心点
uGUI 示例
using UnityEngine;
using UnityEngine.UI;

public class MenuManager : MonoBehaviour
{
    [SerializeField] private Button playButton;
    [SerializeField] private Slider volumeSlider;
    [SerializeField] private Toggle muteToggle;

    private void OnEnable()
    {
        playButton.onClick.AddListener(OnPlayClicked);
        volumeSlider.onValueChanged.AddListener(OnVolumeChanged);
        muteToggle.onValueChanged.AddListener(OnMuteToggled);
    }

    private void OnDisable()
    {
        playButton.onClick.RemoveListener(OnPlayClicked);
        volumeSlider.onValueChanged.RemoveListener(OnVolumeChanged);
        muteToggle.onValueChanged.RemoveListener(OnMuteToggled);
    }

    private void OnPlayClicked() => Debug.Log("Play");
    private void OnVolumeChanged(float value) => AudioListener.volume = value;
    private void OnMuteToggled(bool muted) => AudioListener.pause = muted;
}
绘制顺序

元素按照 Hierarchy 顺序渲染:先绘制第一个子元素,最后一个子元素绘制在最上层。使用 Transform.SetAsFirstSibling()、SetAsLastSibling()、SetSiblingIndex() 调整顺序。

参见:references/ugui-legacy.md


反模式

反模式问题正确做法
到处使用内联样式每个元素都会产生内存开销使用 USS 文件共享样式
在复杂 USS 中使用通用选择器(A * B)大规模使用时选择器性能较差使用 BEM 类命名和子选择器
在包含许多后代元素的元素上大量使用 :hover鼠标移动会导致整个层级失效仅在叶节点元素上使用 :hover
在 CreateInspectorGUI() 中调用 Bind()重复绑定,返回后会自动绑定交由自动绑定处理,或仅对手动创建的 UI 调用 Bind
每帧重建整个 UI无法发挥保留模式的优势仅更新发生变化的元素
在动态内容中使用多个 Canvas(uGUI)任一子元素发生变化都会触发 Canvas 重建批次将静态和动态 UI 拆分到不同的 Canvas 中
不注销回调内存泄漏、引用过期始终在 OnDisable 或 OnDestroy 中注销
在运行时游戏 UI 中使用 IMGUI每帧都会重绘,性能较差使用 UI Toolkit 或 uGUI
忘记在场景中添加 EventSystem(uGUI)不会处理输入事件确保场景中存在一个 EventSystem

关键 API 速查

UI Toolkit
API用途
UIDocument承载 VisualTreeAsset 的 MonoBehaviour
rootVisualElement视觉树的根节点
Q<T>("name")按名称查询单个元素
Q<T>(className: "cls")按类查询单个元素
Query<T>().ToList()查询多个元素
RegisterCallback<TEvent>(callback)注册事件处理程序
UnregisterCallback<TEvent>(callback)移除事件处理程序
RegisterValueChangedCallback(callback)监听值变化
SetValueWithoutNotify(value)静默设置值
AddToClassList("class")添加 USS 类
RemoveFromClassList("class")移除 USS 类
AddManipulator(manipulator)附加事件操纵器
style.display = DisplayStyle.None隐藏元素
style.display = DisplayStyle.Flex显示元素
VisualTreeAsset.Instantiate()从 UXML 创建实例
element.Bind(serializedObject)绑定到 SerializedObject
uGUI
API用途
Canvas所有 uGUI 元素的根容器
CanvasScaler控制 UI 在不同分辨率下的缩放
GraphicRaycaster启用对 Canvas 的输入检测
EventSystem集中分发输入事件
RectTransform支持锚点和尺寸设置的 Transform
Button.onClick用于点击的 UnityEvent
Toggle.onValueChanged用于切换状态变化的 UnityEvent
Slider.onValueChanged用于滑块变化的 UnityEvent
LayoutGroup为子元素自动布局

相关技能

  • unity-foundations -- GameObject、Component、MonoBehaviour 生命周期
  • unity-scripting -- C# 模式、SerializeField、事件
  • unity-input -- 与 UI 集成的 Input System

TextMeshPro

所有文本渲染都应使用 TextMeshPro(TMP),而非旧版 UI.Text。TMP 使用 SDF 渲染,确保文本在任意缩放比例下都清晰。Canvas UI 使用 TextMeshProUGUI,3D 世界文本使用 TextMeshPro。使用 SetText("Score: {0}", value) 实现零分配更新。完整 API、富文本标签、字体资源和模式请参阅 references/textmeshpro.md。

其他资源

评论

支持 Markdown