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 Min | Anchor Max | Pivot |
|---|---|---|---|
| 左上角 | (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}");
}
}
Dropdown(下拉菜单)
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 性能优化
优化建议
-
减少 Canvas 重建
- 将频繁变化的 UI 放在单独的 Canvas
- 使用
CanvasGroup控制显隐而非启用/禁用
-
避免使用 Layout Group
- 频繁添加/移除子对象时,Layout Group 会重建
- 考虑使用 Content Size Fitter + 代码布局
-
使用对象池
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);
}
} -
使用 TextMeshPro
- 更好的渲染性能
- 更丰富的功能
-
减少 Raycast Target
- 不需要交互的 UI 取消勾选 Raycast Target
下一步
掌握 UI 系统后,你可以: