跳到主要内容

UI 系统

Unity 提供两种 UI 系统:传统的 UGUI(Unity GUI)和新的 UI Toolkit。本章主要介绍使用最广泛的 UGUI。

UI 系统概述

UGUI 核心组件:
├── Canvas - 画布,所有 UI 元素的容器
├── EventSystem - 事件系统,处理输入
├── RectTransform - 矩形变换,控制位置和大小
└── UI 元素 - Button、Text、Image 等

Canvas(画布)

Canvas 是所有 UI 元素的渲染容器。

创建 Canvas

GameObject > UI > Canvas

创建 Canvas 时会自动创建 EventSystem。

Render Mode(渲染模式)

模式说明适用场景
Screen Space - Overlay覆盖在屏幕最上层2D 游戏 UI、主菜单
Screen Space - Camera在指定相机前渲染需要与 3D 场景混合的 UI
World Space在世界空间中渲染3D 游戏内 UI、血条
public class CanvasController : MonoBehaviour
{
public Canvas canvas;

void Start()
{
// 切换渲染模式
canvas.renderMode = RenderMode.ScreenSpaceOverlay;

// Screen Space - Camera 模式设置
// canvas.renderMode = RenderMode.ScreenSpaceCamera;
// canvas.worldCamera = Camera.main;

// World Space 模式设置
// canvas.renderMode = RenderMode.WorldSpace;
}
}

Canvas Scaler(画布缩放器)

控制 UI 在不同分辨率下的缩放行为:

UI Scale Mode说明
Constant Pixel Size固定像素大小
Scale With Screen Size随屏幕大小缩放(推荐)
Constant Physical Size固定物理大小

Scale With Screen Size 设置:

  • Reference Resolution:参考分辨率(如 1920x1080)
  • Screen Match Mode
    • Match Width Or Height - 匹配宽度或高度
    • Expand - 扩展以适应屏幕
    • Shrink - 收缩以适应屏幕

RectTransform(矩形变换)

RectTransform 是 UI 元素的变换组件,继承自 Transform。

锚点(Anchor)

锚点决定了 UI 元素如何随父对象缩放:

锚点设置示例:
┌─────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ │
│ │左上 │ │ 上 │ │
│ └─────┘ └─────┘ │
│ │
│ ┌─────────┐ │
│ │ 居中 │ │
│ └─────────┘ │
│ │
│ ┌─────┐ ┌─────┐ │
│ │左下 │ │右下 │ │
│ └─────┘ └─────┘ │
└─────────────────────────────────────┘
public class RectTransformController : MonoBehaviour
{
public RectTransform rectTransform;

void SetAnchor()
{
// 设置锚点(左下角)
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);

// 设置锚点(居中)
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);

// 设置锚点(拉伸填充)
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(1, 1);
}

void SetPosition()
{
// anchoredPosition:相对于锚点的位置
rectTransform.anchoredPosition = new Vector2(100, 100);

// sizeDelta:大小或偏移
rectTransform.sizeDelta = new Vector2(200, 100);

// 设置Pivot(中心点)
rectTransform.pivot = new Vector2(0.5f, 0.5f);
}
}

常用布局锚点

位置Anchor MinAnchor MaxPivot
左上角(0, 1)(0, 1)(0, 1)
顶部居中(0.5, 1)(0.5, 1)(0.5, 1)
右上角(1, 1)(1, 1)(1, 1)
左中(0, 0.5)(0, 0.5)(0, 0.5)
居中(0.5, 0.5)(0.5, 0.5)(0.5, 0.5)
右中(1, 0.5)(1, 0.5)(1, 0.5)
左下角(0, 0)(0, 0)(0, 0)
底部居中(0.5, 0)(0.5, 0)(0.5, 0)
右下角(1, 0)(1, 0)(1, 0)
拉伸填充(0, 0)(1, 1)(0.5, 0.5)

基础 UI 元素

Text(文本)

public class TextController : MonoBehaviour
{
public Text textComponent;

void Start()
{
// 设置文本
textComponent.text = "Hello World";

// 设置字体
textComponent.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");

// 设置字号
textComponent.fontSize = 24;

// 设置颜色
textComponent.color = Color.white;

// 设置对齐方式
textComponent.alignment = TextAnchor.MiddleCenter;

// 设置样式
textComponent.fontStyle = FontStyle.Bold;
}
}
TextMeshPro

推荐使用 TextMeshPro 替代原生 Text,提供更好的渲染质量和更多功能。

Image(图片)

public class ImageController : MonoBehaviour
{
public Image image;

void Start()
{
// 设置 Sprite
image.sprite = someSprite;

// 设置颜色
image.color = new Color(1, 1, 1, 0.5f); // 半透明

// 设置填充类型
image.type = Image.Type.Filled;
image.fillMethod = Image.FillMethod.Horizontal;
image.fillAmount = 0.5f; // 填充 50%

// 设置 Preserve Aspect
image.preserveAspect = true;
}

// 制作血条
public void SetHealthBar(float current, float max)
{
image.fillAmount = current / max;
}
}

Raw Image(原始图片)

用于显示纹理(Texture)而非 Sprite:

public class RawImageController : MonoBehaviour
{
public RawImage rawImage;

void Start()
{
// 设置纹理
rawImage.texture = someTexture;

// 设置 UV 坐标(用于滚动效果)
rawImage.uvRect = new Rect(0, 0, 1, 1);
}

// 纹理滚动效果
void Update()
{
Rect uvRect = rawImage.uvRect;
uvRect.x += Time.deltaTime * 0.1f;
rawImage.uvRect = uvRect;
}
}

交互组件

Button(按钮)

public class ButtonController : MonoBehaviour
{
public Button button;

void Start()
{
// 添加点击事件
button.onClick.AddListener(OnButtonClick);

// 移除事件
// button.onClick.RemoveListener(OnButtonClick);

// 移除所有事件
// button.onClick.RemoveAllListeners();

// 设置可交互状态
button.interactable = true;
}

void OnButtonClick()
{
Debug.Log("按钮被点击!");
}

void OnDestroy()
{
// 记得移除事件监听
button.onClick.RemoveListener(OnButtonClick);
}
}

带参数的按钮事件:

public class ButtonWithParameter : MonoBehaviour
{
public Button[] levelButtons;

void Start()
{
for (int i = 0; i < levelButtons.Length; i++)
{
int levelIndex = i; // 必须捕获到局部变量
levelButtons[i].onClick.AddListener(() => LoadLevel(levelIndex));
}
}

void LoadLevel(int level)
{
Debug.Log($"加载关卡 {level}");
}
}

Toggle(开关)

public class ToggleController : MonoBehaviour
{
public Toggle toggle;
public ToggleGroup toggleGroup;

void Start()
{
// 添加到 Toggle Group
toggle.group = toggleGroup;

// 监听状态变化
toggle.onValueChanged.AddListener(OnToggleChanged);

// 设置状态
toggle.isOn = true;
}

void OnToggleChanged(bool isOn)
{
Debug.Log($"Toggle 状态: {isOn}");
}
}

Slider(滑块)

public class SliderController : MonoBehaviour
{
public Slider slider;
public Text valueText;

void Start()
{
// 设置范围
slider.minValue = 0;
slider.maxValue = 100;
slider.value = 50;

// 监听值变化
slider.onValueChanged.AddListener(OnSliderChanged);
}

void OnSliderChanged(float value)
{
valueText.text = $"{value:F0}%";
}
}

Scrollbar(滚动条)

public class ScrollbarController : MonoBehaviour
{
public Scrollbar scrollbar;

void Start()
{
// 设置值 (0-1)
scrollbar.value = 0.5f;

// 设置大小(可见比例)
scrollbar.size = 0.3f;

// 监听值变化
scrollbar.onValueChanged.AddListener(OnScrollbarChanged);
}

void OnScrollbarChanged(float value)
{
Debug.Log($"滚动位置: {value}");
}
}
public class DropdownController : MonoBehaviour
{
public Dropdown dropdown;

void Start()
{
// 清除选项
dropdown.options.Clear();

// 添加选项
dropdown.options.Add(new Dropdown.OptionData("选项 1"));
dropdown.options.Add(new Dropdown.OptionData("选项 2"));
dropdown.options.Add(new Dropdown.OptionData("选项 3"));

// 刷新显示
dropdown.RefreshShownValue();

// 监听选择
dropdown.onValueChanged.AddListener(OnDropdownChanged);
}

void OnDropdownChanged(int index)
{
Debug.Log($"选择了: {dropdown.options[index].text}");
}
}

Input Field(输入框)

public class InputFieldController : MonoBehaviour
{
public InputField inputField;

void Start()
{
// 设置占位符
inputField.placeholder.GetComponent<Text>().text = "请输入...";

// 设置内容类型
inputField.contentType = InputField.ContentType.Standard;
// inputField.contentType = InputField.ContentType.Password;
// inputField.contentType = InputField.ContentType.EmailAddress;

// 监听值变化
inputField.onValueChanged.AddListener(OnValueChanged);

// 监听编辑结束
inputField.onEndEdit.AddListener(OnEndEdit);
}

void OnValueChanged(string text)
{
Debug.Log($"当前输入: {text}");
}

void OnEndEdit(string text)
{
Debug.Log($"最终输入: {text}");
}
}

布局组件

Horizontal Layout Group(水平布局)

public class HorizontalLayoutExample : MonoBehaviour
{
public HorizontalLayoutGroup layoutGroup;

void Start()
{
// 子对象间距
layoutGroup.spacing = 10;

// 对齐方式
layoutGroup.childAlignment = TextAnchor.MiddleCenter;

// 控制子对象大小
layoutGroup.childControlWidth = true;
layoutGroup.childControlHeight = true;

// 强制扩展
layoutGroup.childForceExpandWidth = false;
}
}

Vertical Layout Group(垂直布局)

public class VerticalLayoutExample : MonoBehaviour
{
public VerticalLayoutGroup layoutGroup;

void Start()
{
layoutGroup.spacing = 5;
layoutGroup.padding = new RectOffset(10, 10, 10, 10);
}
}

Grid Layout Group(网格布局)

public class GridLayoutExample : MonoBehaviour
{
public GridLayoutGroup gridLayout;

void Start()
{
// 单元格大小
gridLayout.cellSize = new Vector2(100, 100);

// 间距
gridLayout.spacing = new Vector2(10, 10);

// 排列方式
gridLayout.startAxis = GridLayoutGroup.Axis.Horizontal;
gridLayout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
gridLayout.constraintCount = 4; // 固定 4 列
}
}

Content Size Fitter(内容大小适配器)

自动调整大小以适应内容:

public class ContentSizeFitterExample : MonoBehaviour
{
public ContentSizeFitter fitter;

void Start()
{
// 水平方向适配
fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;

// 垂直方向适配
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
}

Aspect Ratio Fitter(宽高比适配器)

保持特定的宽高比:

public class AspectRatioFitterExample : MonoBehaviour
{
public AspectRatioFitter aspectFitter;

void Start()
{
// 设置宽高比
aspectFitter.aspectRatio = 16f / 9f;

// 适配模式
aspectFitter.aspectMode = AspectRatioFitter.AspectMode.FitInParent;
}
}

Scroll View(滚动视图)

创建可滚动的内容区域:

public class ScrollViewController : MonoBehaviour
{
public ScrollRect scrollRect;
public Transform content;

void Start()
{
// 设置滚动方向
scrollRect.horizontal = true;
scrollRect.vertical = true;

// 设置惯性
scrollRect.inertia = true;
scrollRect.decelerationRate = 0.135f;

// 监听滚动
scrollRect.onValueChanged.AddListener(OnScroll);
}

void OnScroll(Vector2 position)
{
Debug.Log($"滚动位置: {position}");
}

// 滚动到顶部
public void ScrollToTop()
{
scrollRect.verticalNormalizedPosition = 1;
}

// 滚动到底部
public void ScrollToBottom()
{
scrollRect.verticalNormalizedPosition = 0;
}

// 动态添加内容
public void AddItem(GameObject item)
{
item.transform.SetParent(content, false);
}
}

事件系统

自定义事件处理

using UnityEngine.EventSystems;

public class CustomEventHandler : MonoBehaviour,
IPointerClickHandler,
IPointerEnterHandler,
IPointerExitHandler,
IPointerDownHandler,
IPointerUpHandler,
IDragHandler
{
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("点击");
}

public void OnPointerEnter(PointerEventData eventData)
{
Debug.Log("鼠标进入");
}

public void OnPointerExit(PointerEventData eventData)
{
Debug.Log("鼠标离开");
}

public void OnPointerDown(PointerEventData eventData)
{
Debug.Log("鼠标按下");
}

public void OnPointerUp(PointerEventData eventData)
{
Debug.Log("鼠标抬起");
}

public void OnDrag(PointerEventData eventData)
{
Debug.Log("拖拽中");
transform.position = eventData.position;
}
}

射线检测

public class UIRaycastExample : MonoBehaviour
{
void Update()
{
// 检测 UI 点击
if (Input.GetMouseButtonDown(0))
{
PointerEventData pointerData = new PointerEventData(EventSystem.current);
pointerData.position = Input.mousePosition;

List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);

foreach (RaycastResult result in results)
{
Debug.Log($"点击了: {result.gameObject.name}");
}
}
}
}

UI 动画

使用 Animator

public class UIAnimation : MonoBehaviour
{
public Animator animator;

public void ShowPanel()
{
animator.SetTrigger("Show");
}

public void HidePanel()
{
animator.SetTrigger("Hide");
}
}

代码动画

public class UITween : MonoBehaviour
{
public RectTransform panel;

// 滑入动画
public void SlideIn()
{
StartCoroutine(SlideInCoroutine());
}

IEnumerator SlideInCoroutine()
{
Vector2 startPos = new Vector2(-Screen.width, 0);
Vector2 endPos = Vector2.zero;
float duration = 0.3f;
float elapsed = 0;

panel.anchoredPosition = startPos;

while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
// 使用缓动函数
t = Mathf.SmoothStep(0, 1, t);
panel.anchoredPosition = Vector2.Lerp(startPos, endPos, t);
yield return null;
}

panel.anchoredPosition = endPos;
}
}

实践:游戏 HUD 系统

public class GameHUD : MonoBehaviour
{
[Header("生命值")]
public Image healthBar;
public Text healthText;

[Header("分数")]
public Text scoreText;
private int score = 0;

[Header("弹药")]
public Text ammoText;
public Text maxAmmoText;

[Header("消息")]
public Text messageText;
public Animator messageAnimator;

[Header("暂停菜单")]
public GameObject pausePanel;

void Update()
{
// 暂停
if (Input.GetKeyDown(KeyCode.Escape))
{
TogglePause();
}
}

// 更新生命值
public void UpdateHealth(float current, float max)
{
float ratio = current / max;
healthBar.fillAmount = ratio;
healthText.text = $"{current}/{max}";

// 根据血量改变颜色
if (ratio > 0.5f)
healthBar.color = Color.green;
else if (ratio > 0.25f)
healthBar.color = Color.yellow;
else
healthBar.color = Color.red;
}

// 更新分数
public void AddScore(int points)
{
score += points;
scoreText.text = $"Score: {score:D6}";
}

// 更新弹药
public void UpdateAmmo(int current, int max)
{
ammoText.text = current.ToString();
maxAmmoText.text = $"/ {max}";
}

// 显示消息
public void ShowMessage(string message)
{
messageText.text = message;
messageAnimator.SetTrigger("Show");
}

// 暂停
public void TogglePause()
{
bool isPaused = !pausePanel.activeSelf;
pausePanel.SetActive(isPaused);
Time.timeScale = isPaused ? 0 : 1;
}

// 重新开始
public void Restart()
{
Time.timeScale = 1;
UnityEngine.SceneManagement.SceneManager.LoadScene(
UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
}

// 退出游戏
public void QuitGame()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}

UI 性能优化

优化建议

  1. 减少 Canvas 重建

    • 将频繁变化的 UI 放在单独的 Canvas
    • 使用 CanvasGroup 控制显隐而非启用/禁用
  2. 避免使用 Layout Group

    • 频繁添加/移除子对象时,Layout Group 会重建
    • 考虑使用 Content Size Fitter + 代码布局
  3. 使用对象池

    public class UIObjectPool : MonoBehaviour
    {
    public GameObject prefab;
    private Queue<GameObject> pool = new Queue<GameObject>();

    public GameObject Get()
    {
    if (pool.Count > 0)
    {
    GameObject obj = pool.Dequeue();
    obj.SetActive(true);
    return obj;
    }
    return Instantiate(prefab);
    }

    public void Return(GameObject obj)
    {
    obj.SetActive(false);
    pool.Enqueue(obj);
    }
    }
  4. 使用 TextMeshPro

    • 更好的渲染性能
    • 更丰富的功能
  5. 减少 Raycast Target

    • 不需要交互的 UI 取消勾选 Raycast Target

下一步

掌握 UI 系统后,你可以:

  1. 学习 音频系统 添加音效和音乐
  2. 探索 粒子系统 制作视觉特效
  3. 了解场景管理实现多场景切换
  4. 学习资源管理优化游戏资源