这篇按 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 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;
|
常查属性:
1 2 3 4
| rect.anchoredPosition 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 按项目测试
|
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; }
|
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"); timeText.text = time.ToString("F1"); percentText.text = $"{ratio:P0}";
|
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; } 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 开着 # 按钮点不到
|
系列导航