Unity UI 开发速查

这篇按 UI 开发常用操作来查:Canvas 怎么拆、按钮怎么绑、TMP 怎么赋值、弹窗怎么开关、不同分辨率怎么适配。


快速索引

我想做什么 常用组件 / API
放普通 2D UI Canvas: Screen Space Overlay
UI 跟相机层级配合 Canvas: Screen Space Camera
世界空间血条 Canvas: World Space
做自适应布局 RectTransform Anchor / Pivot
自动排列列表 Vertical / Horizontal Layout Group
文本显示 TextMeshPro TMP_Text
按钮点击 Button.onClick
开关选项 Toggle.onValueChanged
滑条 Slider.onValueChanged
输入框 TMP_InputField.onValueChanged
安全区适配 Screen.safeArea
UI 性能检查 Canvas Rebuild / Raycast Target

0. Canvas 速查

1
2
3
Screen Space Overlay        # 普通全屏 UI,最常用
Screen Space Camera # 需要 UI Camera、层级、后处理配合
World Space # 世界空间 UI,如血条、提示牌

推荐层级:

1
2
3
4
5
6
Canvas_Root
Canvas_StaticHUD # 低频变化:固定 HUD
Canvas_DynamicHUD # 高频变化:血条、计时器
Canvas_Window # 页面窗口
Canvas_Popup # 弹窗
Canvas_Top # Loading、Toast、系统提示

1. RectTransform 常用写法

拉满父节点:

1
2
3
4
5
RectTransform rect = transform as RectTransform;
rect.anchorMin = Vector2.zero; // 左下角锚点
rect.anchorMax = Vector2.one; // 右上角锚点
rect.offsetMin = Vector2.zero; // 左下偏移
rect.offsetMax = Vector2.zero; // 右上偏移

居中:

1
2
3
4
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.anchoredPosition = Vector2.zero; // UI 本地锚点坐标

常查属性:

1
2
3
4
rect.anchoredPosition                  // UI 锚点坐标
rect.sizeDelta // 尺寸差值
rect.rect.width // 实际宽
rect.rect.height // 实际高

2. Canvas Scaler 设置

1
2
3
4
UI Scale Mode: Scale With Screen Size   # 常用自适应
Reference Resolution: 1920 x 1080 # 设计分辨率
Screen Match Mode: Match Width Or Height
Match: 0.5 # 宽高折中

竖屏常见:

1
2
Reference Resolution: 1080 x 1920
Match: 0 或 1 按项目测试

3. Button 点击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[SerializeField] private Button startButton;

private void OnEnable()
{
startButton.onClick.AddListener(OnStartClicked); // 绑定点击
}

private void OnDisable()
{
startButton.onClick.RemoveListener(OnStartClicked); // 解绑点击
}

private void OnStartClicked()
{
Debug.Log("Start");
}

防重复点击:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private bool isClicking;

private async void OnStartClicked()
{
if (isClicking)
{
return;
}

isClicking = true;
startButton.interactable = false; // 禁止重复点击

await StartGameAsync();

startButton.interactable = true;
isClicking = false;
}

4. Toggle / Slider / InputField

1
2
3
4
5
6
toggle.onValueChanged.AddListener(OnToggleChanged); // 监听开关

private void OnToggleChanged(bool isOn)
{
Debug.Log(isOn);
}
1
2
3
4
5
6
slider.onValueChanged.AddListener(OnVolumeChanged); // 监听滑条

private void OnVolumeChanged(float value)
{
AudioListener.volume = value;
}
1
2
inputField.onValueChanged.AddListener(OnNameChanged); // 输入变化
inputField.onEndEdit.AddListener(OnNameEndEdit); // 输入结束

5. TextMeshPro

1
2
3
4
5
6
[SerializeField] private TMP_Text hpText;

hpText.text = "HP: 100"; // 设置文本
hpText.text = $"HP: {currentHp}/{maxHp}"; // 插值文本
hpText.color = Color.red; // 改颜色
hpText.fontSize = 32; // 改字号

数字格式:

1
2
3
scoreText.text = score.ToString("N0");      // 12,345
timeText.text = time.ToString("F1"); // 1.2
percentText.text = $"{ratio:P0}"; // 80%

TMP 中文检查:

1
2
3
4
字体 Asset 是否包含中文
Fallback Font Assets 是否配置
Dynamic Atlas 是否允许动态加入字符
富文本 Rich Text 是否按需开启

6. 弹窗打开 / 关闭

1
2
3
4
5
6
7
8
9
10
11
public void Open()
{
gameObject.SetActive(true); // 显示弹窗
Refresh(); // 每次打开刷新数据
}

public void Close()
{
gameObject.SetActive(false); // 隐藏弹窗
ClearState(); // 清理临时状态
}

弹窗栈常见操作:

1
2
3
4
5
Open(window)                 # 打开窗口
Close(window) # 关闭指定窗口
CloseTop() # 关闭最上层
CloseAll() # 关闭全部
Back() # 返回键

7. 动态列表

简单刷新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for (int i = 0; i < items.Count; i++)
{
GameObject itemObj;

if (i < contentRoot.childCount)
{
itemObj = contentRoot.GetChild(i).gameObject; // 复用已有 Item
}
else
{
itemObj = Instantiate(itemPrefab, contentRoot); // 不够再创建
}

itemObj.SetActive(true);
itemObj.GetComponent<ItemView>().Refresh(items[i]);
}

for (int i = items.Count; i < contentRoot.childCount; i++)
{
contentRoot.GetChild(i).gameObject.SetActive(false); // 多余隐藏
}

少用:

1
2
3
每次刷新 Destroy 全部 Item
每次刷新 Instantiate 全部 Item
大列表不用虚拟列表

8. Safe Area 适配

1
2
3
4
5
6
7
8
9
10
11
Rect safeArea = Screen.safeArea;             // 屏幕安全区
Vector2 anchorMin = safeArea.position;
Vector2 anchorMax = safeArea.position + safeArea.size;

anchorMin.x /= Screen.width;
anchorMin.y /= Screen.height;
anchorMax.x /= Screen.width;
anchorMax.y /= Screen.height;

panel.anchorMin = anchorMin; // 应用安全区
panel.anchorMax = anchorMax;

9. UI 点不到排查

1
2
3
4
5
6
7
场景里是否有 EventSystem
Canvas 上是否有 GraphicRaycaster
按钮是否被透明 Image 遮挡
遮挡 Image 的 Raycast Target 是否开启
Canvas Sorting Order 是否正确
Button interactable 是否为 true
父节点是否 SetActive(false)

10. UI 性能速查

1
2
3
4
5
动态 UI 和静态 UI 分 Canvas          # 减少大范围重建
纯装饰 Image 关闭 Raycast Target # 减少射线检测
列表 Item 复用 # 减少创建销毁
避免 Layout Group 复杂嵌套 # 减少 Layout Rebuild
文本高频刷新先判断值是否变化 # 不变就别赋值

关闭 Raycast:

1
2
Image -> Raycast Target: Off
TMP_Text -> Raycast Target: Off

11. 常见坑速查

1
2
3
4
5
6
UI 坐标用 transform.position 硬改       # 优先用 RectTransform
Button 多次 AddListener 不 Remove # 点击触发多次
TMP 中文显示方块 # 字体或 Fallback 缺失
UI 在某分辨率正常,换分辨率错位 # Anchor / Canvas Scaler 问题
全 UI 放一个 Canvas # 高频刷新导致大 Canvas Rebuild
透明遮罩 Raycast Target 开着 # 按钮点不到

系列导航