hz-unity-meta-quest-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMeta Quest VR UI Setup
Meta Quest VR UI 设置
When to use this skill
何时使用此技能
Use this skill automatically when:
- Setting up a Canvas for VR
- Creating UI text with TextMesh Pro in a VR project
- Adding buttons, sliders, or other interactive UI in VR
- User reports pink/magenta text, unclickable buttons, or UI sizing issues in VR
- Configuring VR interaction (ray or poke) on a Canvas
在以下场景自动使用此技能:
- 为VR设置Canvas
- 在VR项目中使用TextMesh Pro创建UI文本
- 在VR中添加按钮、滑块或其他交互式UI
- 用户反馈VR中出现粉色/品红色文本、按钮无法点击或UI尺寸问题
- 在Canvas上配置VR交互(射线或触摸)
Prerequisite: TMP Essential Resources
前置条件:TMP Essential Resources
Before creating ANY VR UI, verify TMP resources are imported. Use :
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
using System.IO;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
string fontPath = "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF.asset";
var font = AssetDatabase.LoadAssetAtPath<Object>(fontPath);
if (font != null)
result.Log("TMP Essential Resources: IMPORTED. Default font present.");
else
result.LogError("TMP Essential Resources: NOT IMPORTED. Use tmp-resources skill first.");
}
}If not imported, use the tmp-resources skill before proceeding.
在创建任何VR UI之前,请确认已导入TMP资源。使用:
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
using System.IO;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
string fontPath = "Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF.asset";
var font = AssetDatabase.LoadAssetAtPath<Object>(fontPath);
if (font != null)
result.Log("TMP Essential Resources: IMPORTED. Default font present.");
else
result.LogError("TMP Essential Resources: NOT IMPORTED. Use tmp-resources skill first.");
}
}如果未导入,请先使用tmp-resources技能。
Step 1: Create World Space Canvas
步骤1:创建世界空间画布
Use to create and configure the canvas:
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// Adapt the name to match your canvas (e.g., "MainMenu", "SettingsUI")
var go = new GameObject("MenuUI");
var canvas = go.AddComponent<Canvas>();
canvas.renderMode = RenderMode.WorldSpace;
go.AddComponent<GraphicRaycaster>();
// Remove CanvasScaler — not appropriate for VR
var scaler = go.GetComponent<CanvasScaler>();
if (scaler != null)
Object.DestroyImmediate(scaler);
var rt = go.GetComponent<RectTransform>();
rt.localScale = new Vector3(0.001f, 0.001f, 0.001f);
rt.sizeDelta = new Vector2(1920f, 1080f);
rt.position = new Vector3(0f, 1.5f, 2f);
result.RegisterObjectCreation(go);
result.Log("Created VR Canvas '{0}'. Scale: {1}, Size: {2}, Position: {3}",
go.name, rt.localScale, rt.sizeDelta, rt.position);
}
}使用创建并配置画布:
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// 根据你的画布调整名称(例如:"MainMenu"、"SettingsUI")
var go = new GameObject("MenuUI");
var canvas = go.AddComponent<Canvas>();
canvas.renderMode = RenderMode.WorldSpace;
go.AddComponent<GraphicRaycaster>();
// 移除CanvasScaler——不适用于VR
var scaler = go.GetComponent<CanvasScaler>();
if (scaler != null)
Object.DestroyImmediate(scaler);
var rt = go.GetComponent<RectTransform>();
rt.localScale = new Vector3(0.001f, 0.001f, 0.001f);
rt.sizeDelta = new Vector2(1920f, 1080f);
rt.position = new Vector3(0f, 1.5f, 2f);
result.RegisterObjectCreation(go);
result.Log("Created VR Canvas '{0}'. Scale: {1}, Size: {2}, Position: {3}",
go.name, rt.localScale, rt.sizeDelta, rt.position);
}
}Canvas rules
画布规则
- Render Mode: Always World Space. Screen Space modes break stereo rendering.
- Scale: 0.001 on all axes (1 unit in canvas = 1mm in world).
- CanvasScaler: Remove it. Physical size is controlled by world scale, not screen adaptation.
- Distance: Place 1.5-3m from user. Never closer than 0.5m. Max 5m for readable text.
- Physical size formula: . Example: 1920 * 0.001 = 1.92m wide.
Canvas sizeDelta * scale = meters
- 渲染模式:始终使用World Space。Screen Space模式会破坏立体渲染。
- 缩放比例:所有轴均为0.001(画布中的1单位 = 世界中的1毫米)。
- CanvasScaler:移除它。物理尺寸由世界缩放控制,而非屏幕适配。
- 距离:放置在距离用户1.5-3米处。永远不要小于0.5米。文本可读的最大距离为5米。
- 物理尺寸公式:。示例:1920 * 0.001 = 1.92米宽。
Canvas sizeDelta * scale = 米
Step 2: Create child UI elements
步骤2:创建子UI元素
All child elements (panels, buttons, text) must follow these rules:
- localScale: Always . Never scale children to compensate for canvas scale.
[1, 1, 1] - localPosition.z: Always . Children must sit on the canvas plane.
0 - Size control: Use and anchors, never scale.
RectTransform.sizeDelta
csharp
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using TMPro;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// Replace "MenuUI" with the actual canvas name used in Step 1
var canvas = GameObject.Find("MenuUI");
if (canvas == null) { result.LogError("Canvas 'MenuUI' not found."); return; }
// Panel
var panel = new GameObject("ButtonPanel");
panel.transform.SetParent(canvas.transform, false);
var panelRT = panel.AddComponent<RectTransform>();
panelRT.localScale = Vector3.one;
panelRT.sizeDelta = new Vector2(800f, 600f);
var panelImg = panel.AddComponent<Image>();
panelImg.color = new Color(0.1f, 0.1f, 0.1f, 0.95f);
panelImg.raycastTarget = false;
var layout = panel.AddComponent<VerticalLayoutGroup>();
layout.spacing = 50f;
layout.padding = new RectOffset(80, 80, 100, 100);
layout.childAlignment = TextAnchor.MiddleCenter;
// Button
var btnGO = new GameObject("StartButton");
btnGO.transform.SetParent(panel.transform, false);
var btnRT = btnGO.AddComponent<RectTransform>();
btnRT.localScale = Vector3.one;
btnRT.sizeDelta = new Vector2(400f, 120f);
var btnImg = btnGO.AddComponent<Image>();
btnImg.color = new Color(0.2f, 0.6f, 1f, 1f);
btnGO.AddComponent<Button>();
// Button text
var textGO = new GameObject("Text");
textGO.transform.SetParent(btnGO.transform, false);
var textRT = textGO.AddComponent<RectTransform>();
textRT.localScale = Vector3.one;
textRT.anchorMin = Vector2.zero;
textRT.anchorMax = Vector2.one;
textRT.sizeDelta = Vector2.zero;
var tmp = textGO.AddComponent<TextMeshProUGUI>();
tmp.text = "Start";
tmp.fontSize = 48f;
tmp.color = new Color(0.95f, 0.95f, 0.95f, 1f);
tmp.alignment = TextAlignmentOptions.Center;
tmp.raycastTarget = false;
result.RegisterObjectCreation(panel);
result.Log("Created panel with button. Panel scale: {0}, Button scale: {1}",
panelRT.localScale, btnRT.localScale);
}
}所有子元素(面板、按钮、文本)必须遵循以下规则:
- localScale:始终为。永远不要通过缩放子元素来补偿画布缩放。
[1, 1, 1] - localPosition.z:始终为。子元素必须位于画布平面上。
0 - 尺寸控制:使用和锚点,不要使用缩放。
RectTransform.sizeDelta
csharp
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using TMPro;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// 替换为步骤1中使用的实际画布名称"MenuUI"
var canvas = GameObject.Find("MenuUI");
if (canvas == null) { result.LogError("Canvas 'MenuUI' not found."); return; }
// 面板
var panel = new GameObject("ButtonPanel");
panel.transform.SetParent(canvas.transform, false);
var panelRT = panel.AddComponent<RectTransform>();
panelRT.localScale = Vector3.one;
panelRT.sizeDelta = new Vector2(800f, 600f);
var panelImg = panel.AddComponent<Image>();
panelImg.color = new Color(0.1f, 0.1f, 0.1f, 0.95f);
panelImg.raycastTarget = false;
var layout = panel.AddComponent<VerticalLayoutGroup>();
layout.spacing = 50f;
layout.padding = new RectOffset(80, 80, 100, 100);
layout.childAlignment = TextAnchor.MiddleCenter;
// 按钮
var btnGO = new GameObject("StartButton");
btnGO.transform.SetParent(panel.transform, false);
var btnRT = btnGO.AddComponent<RectTransform>();
btnRT.localScale = Vector3.one;
btnRT.sizeDelta = new Vector2(400f, 120f);
var btnImg = btnGO.AddComponent<Image>();
btnImg.color = new Color(0.2f, 0.6f, 1f, 1f);
btnGO.AddComponent<Button>();
// 按钮文本
var textGO = new GameObject("Text");
textGO.transform.SetParent(btnGO.transform, false);
var textRT = textGO.AddComponent<RectTransform>();
textRT.localScale = Vector3.one;
textRT.anchorMin = Vector2.zero;
textRT.anchorMax = Vector2.one;
textRT.sizeDelta = Vector2.zero;
var tmp = textGO.AddComponent<TextMeshProUGUI>();
tmp.text = "Start";
tmp.fontSize = 48f;
tmp.color = new Color(0.95f, 0.95f, 0.95f, 1f);
tmp.alignment = TextAlignmentOptions.Center;
tmp.raycastTarget = false;
result.RegisterObjectCreation(panel);
result.Log("Created panel with button. Panel scale: {0}, Button scale: {1}",
panelRT.localScale, btnRT.localScale);
}
}Step 3: Validate created UI
步骤3:验证创建的UI
After creating UI, verify critical properties with :
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// Replace "MenuUI" with the actual canvas name
var canvas = GameObject.Find("MenuUI");
if (canvas == null) { result.LogError("Canvas not found."); return; }
var crt = canvas.GetComponent<RectTransform>();
var c = canvas.GetComponent<Canvas>();
bool pass = true;
if (c.renderMode != RenderMode.WorldSpace)
{ result.LogError("Canvas renderMode is not World Space."); pass = false; }
if (Mathf.Abs(crt.localScale.x - 0.001f) > 0.0001f)
{ result.LogError("Canvas scale is {0}, expected 0.001.", crt.localScale); pass = false; }
// Check all children
foreach (Transform child in canvas.GetComponentsInChildren<Transform>(true))
{
if (child == canvas.transform) continue;
var rt = child.GetComponent<RectTransform>();
if (rt == null) continue;
if (rt.localScale != Vector3.one)
{
result.LogError("Child '{0}' has localScale {1}, expected (1,1,1).", child.name, rt.localScale);
rt.localScale = Vector3.one;
pass = false;
}
if (Mathf.Abs(rt.localPosition.z) > 0.01f)
{
result.LogError("Child '{0}' has localPosition.z={1}, expected 0.", child.name, rt.localPosition.z);
rt.localPosition = new Vector3(rt.localPosition.x, rt.localPosition.y, 0f);
pass = false;
}
}
if (pass)
result.Log("Validation PASSED. All properties correct.");
else
result.LogWarning("Validation found issues. Auto-fixed child scale/position where possible.");
}
}Common issues this catches:
- Canvas scale stuck at 1,1,1 instead of 0.001
- Child elements auto-scaled to compensate for parent (e.g., 1000x)
- Child elements offset on Z axis (e.g., localPosition.z = -2500)
创建UI后,使用验证关键属性:
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// 替换为实际的画布名称"MenuUI"
var canvas = GameObject.Find("MenuUI");
if (canvas == null) { result.LogError("Canvas not found."); return; }
var crt = canvas.GetComponent<RectTransform>();
var c = canvas.GetComponent<Canvas>();
bool pass = true;
if (c.renderMode != RenderMode.WorldSpace)
{ result.LogError("Canvas renderMode is not World Space."); pass = false; }
if (Mathf.Abs(crt.localScale.x - 0.001f) > 0.0001f)
{ result.LogError("Canvas scale is {0}, expected 0.001.", crt.localScale); pass = false; }
// 检查所有子元素
foreach (Transform child in canvas.GetComponentsInChildren<Transform>(true))
{
if (child == canvas.transform) continue;
var rt = child.GetComponent<RectTransform>();
if (rt == null) continue;
if (rt.localScale != Vector3.one)
{
result.LogError("Child '{0}' has localScale {1}, expected (1,1,1).", child.name, rt.localScale);
rt.localScale = Vector3.one;
pass = false;
}
if (Mathf.Abs(rt.localPosition.z) > 0.01f)
{
result.LogError("Child '{0}' has localPosition.z={1}, expected 0.", child.name, rt.localPosition.z);
rt.localPosition = new Vector3(rt.localPosition.x, rt.localPosition.y, 0f);
pass = false;
}
}
if (pass)
result.Log("Validation PASSED. All properties correct.");
else
result.LogWarning("Validation found issues. Auto-fixed child scale/position where possible.");
}
}此脚本捕获的常见问题:
- 画布缩放停留在1,1,1而非0.001
- 子元素自动缩放以补偿父元素(例如1000倍)
- 子元素在Z轴上偏移(例如localPosition.z = -2500)
Step 4: Add VR interaction (interactive UI only)
步骤4:添加VR交互(仅适用于交互式UI)
Only add interaction when your Canvas has interactive elements (buttons, sliders, dropdowns, toggles, input fields). Skip this for display-only UI (HUD, score, labels).
仅当你的Canvas包含交互式元素时才添加交互(按钮、滑块、下拉菜单、切换按钮、输入框)。仅用于显示的UI(HUD、分数、标签)可跳过此步骤。
Decision tree
决策树
Does your Canvas have buttons, dropdowns, sliders, toggles, or input fields?
YES -> Add VR interaction. Without it, interactive elements won't work in VR.
NO -> Skip. Display-only UI doesn't need interaction overhead.你的Canvas是否包含按钮、下拉菜单、滑块、切换按钮或输入框?
是 -> 添加VR交互。没有它,交互式元素在VR中将无法工作。
否 -> 跳过。仅显示的UI不需要交互开销。Ray interaction (default choice)
射线交互(默认选择)
Use for most VR UI — lets users point at UI with controller or hand rays:
Call the Meta Unity extension MCP tool with the canvas name:
meta_add_canvas_interaction_raymeta_add_canvas_interaction_ray with NameOrID: "<your canvas name>"This adds: , , child, and a scene-level .
RayInteractablePointableCanvasISDK_RayCanvasInteractionPointable Canvas ModuleWhen to use: menus, settings panels, any UI beyond arm's reach.
适用于大多数VR UI——允许用户使用控制器或手部射线指向UI:
调用Meta Unity扩展MCP工具并传入画布名称:
meta_add_canvas_interaction_raymeta_add_canvas_interaction_ray with NameOrID: "<your canvas name>"这会添加:、、子对象,以及场景级别的。
RayInteractablePointableCanvasISDK_RayCanvasInteractionPointable Canvas Module适用场景:菜单、设置面板、任何超出手臂范围的UI。
Poke interaction (close-range only)
触摸交互(仅近距离)
Use for physical touch-style interaction within arm's reach (< 0.8m):
Call the Meta Unity extension MCP tool with the canvas name:
meta_add_canvas_interaction_pokemeta_add_canvas_interaction_poke with NameOrID: "<your canvas name>"This adds: , , close-range collision detection.
PokeInteractablePointableCanvasWhen to use: control panels on surfaces, virtual keyboards, diegetic UI on props.
适用于手臂范围内(<0.8米)的物理触摸式交互:
调用Meta Unity扩展MCP工具并传入画布名称:
meta_add_canvas_interaction_pokemeta_add_canvas_interaction_poke with NameOrID: "<your canvas name>"这会添加:、、近距离碰撞检测。
PokeInteractablePointableCanvas适用场景:表面上的控制面板、虚拟键盘、道具上的嵌入式UI。
Both (advanced)
同时添加两种交互(高级)
Call ray first, then poke, for hybrid UIs where users can point OR touch.
先调用射线交互,再调用触摸交互,适用于用户既可以指向也可以触摸的混合UI。
Verify interaction was added
验证交互已添加
After calling the Meta Unity extension MCP tool, verify with :
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// Replace "MenuUI" with the actual canvas name
var canvas = GameObject.Find("MenuUI");
if (canvas == null) { result.LogError("Canvas not found."); return; }
var components = canvas.GetComponents<Component>();
bool hasPointable = false;
foreach (var c in components)
{
if (c != null && c.GetType().Name.Contains("PointableCanvas"))
hasPointable = true;
}
if (hasPointable)
result.Log("VR interaction verified: PointableCanvas present on '{0}'.", canvas.name);
else
result.LogError("VR interaction MISSING on '{0}'. Call meta_add_canvas_interaction_ray.", canvas.name);
// Check scene-level Pointable Canvas Module
var module = GameObject.Find("Pointable Canvas Module");
if (module != null)
result.Log("Pointable Canvas Module found in scene.");
else
result.LogWarning("Pointable Canvas Module not found. It should be auto-created by the Meta Unity extension MCP tool.");
}
}调用Meta Unity扩展MCP工具后,使用验证:
Unity_RunCommandcsharp
using UnityEngine;
using UnityEditor;
internal class CommandScript : IRunCommand
{
public void Execute(ExecutionResult result)
{
// 替换为实际的画布名称"MenuUI"
var canvas = GameObject.Find("MenuUI");
if (canvas == null) { result.LogError("Canvas not found."); return; }
var components = canvas.GetComponents<Component>();
bool hasPointable = false;
foreach (var c in components)
{
if (c != null && c.GetType().Name.Contains("PointableCanvas"))
hasPointable = true;
}
if (hasPointable)
result.Log("VR interaction verified: PointableCanvas present on '{0}'.", canvas.name);
else
result.LogError("VR interaction MISSING on '{0}'. Call meta_add_canvas_interaction_ray.", canvas.name);
// 检查场景级别的Pointable Canvas Module
var module = GameObject.Find("Pointable Canvas Module");
if (module != null)
result.Log("Pointable Canvas Module found in scene.");
else
result.LogWarning("Pointable Canvas Module not found. It should be auto-created by the Meta Unity extension MCP tool.");
}
}If buttons work in Editor but not on Quest device
如果按钮在编辑器中可用但在Quest设备上不可用
Ensure the interaction rig exists in the scene:
meta_add_interactionrig (no parameters)This adds as a child of the Camera Rig, providing all hand/controller interactors. Requires to be in the scene first.
OVRInteractionComprehensiveOVRCameraRig确保场景中存在交互装置:
meta_add_interactionrig (无参数)这会在Camera Rig下添加,提供所有手部/控制器交互器。要求场景中已存在。
OVRInteractionComprehensiveOVRCameraRigVR UI sizing reference
VR UI尺寸参考
All sizes assume canvas scale of 0.001 (1 unit = 1mm).
所有尺寸均假设画布缩放比例为0.001(1单位=1毫米)。
Element sizes (minimum)
元素最小尺寸
| Element | sizeDelta (units) | Physical size |
|---|---|---|
| Button (small) | 250 x 100 | 25cm x 10cm |
| Button (standard) | 300 x 120 | 30cm x 12cm |
| Button (large) | 400 x 150 | 40cm x 15cm |
| Dropdown | 500 x 140 | 50cm x 14cm |
| Slider | 600 x 80 | 60cm x 8cm |
| Toggle | 120 x 120 | 12cm x 12cm |
| Input Field | 800 x 140 | 80cm x 14cm |
| Panel/Menu | 1200-2000 x 800-1400 | 1.2-2m x 0.8-1.4m |
| 元素 | sizeDelta(单位) | 物理尺寸 |
|---|---|---|
| 小型按钮 | 250 x 100 | 25cm x 10cm |
| 标准按钮 | 300 x 120 | 30cm x 12cm |
| 大型按钮 | 400 x 150 | 40cm x 15cm |
| 下拉菜单 | 500 x 140 | 50cm x 14cm |
| 滑块 | 600 x 80 | 60cm x 8cm |
| 切换按钮 | 120 x 120 | 12cm x 12cm |
| 输入框 | 800 x 140 | 80cm x 14cm |
| 面板/菜单 | 1200-2000 x 800-1400 | 1.2-2m x 0.8-1.4m |
Button spacing
按钮间距
Minimum 50 units (5cm) between interactive elements to prevent mis-clicks.
交互式元素之间的最小间距为50单位(5厘米),以防止误触。
Font sizes (at 0.001 canvas scale)
字体尺寸(画布缩放比例为0.001时)
| Distance | Minimum | Comfortable | Large/Title |
|---|---|---|---|
| 1.5m | 32pt | 40-48pt | 60-72pt |
| 2.0m | 36pt | 48-56pt | 72-84pt |
| 2.5m | 40pt | 52-64pt | 84-96pt |
| 3.0m | 48pt | 64-72pt | 96-120pt |
Never use auto-sizing in VR. Never use legacy Text components — always TextMeshPro.
| 距离 | 最小尺寸 | 舒适尺寸 | 大字体/标题 |
|---|---|---|---|
| 1.5m | 32pt | 40-48pt | 60-72pt |
| 2.0m | 36pt | 48-56pt | 72-84pt |
| 2.5m | 40pt | 52-64pt | 84-96pt |
| 3.0m | 48pt | 64-72pt | 96-120pt |
在VR中永远不要使用自动调整大小。永远不要使用旧版Text组件——始终使用TextMeshPro。
Colors
颜色
- Text: off-white (0.9, 0.9, 0.9) or dark gray (0.1, 0.1, 0.1). Avoid pure white/black.
- Backgrounds: dark (0.1, 0.1, 0.1, 0.95) or light (0.9, 0.9, 0.9, 0.95).
- Contrast ratio: minimum 4.5:1, prefer 7:1+.
- Prefer opaque backgrounds over transparent (cheaper to render).
- 文本:米白色(0.9, 0.9, 0.9)或深灰色(0.1, 0.1, 0.1)。避免纯白色/纯黑色。
- 背景:深色(0.1, 0.1, 0.1, 0.95)或浅色(0.9, 0.9, 0.9, 0.95)。
- 对比度:最小4.5:1,优先选择7:1及以上。
- 优先选择不透明背景而非透明背景(渲染成本更低)。
Performance tips
性能提示
- Disable on non-interactive elements (labels, backgrounds, decorative images).
raycastTarget - Split static and dynamic content onto separate canvases to minimize rebuilds.
- Canvas rebuild cost should be < 1-2ms to maintain 72/90Hz.
- Share one SDF font asset per font family across all text elements.
- Disable canvases or GameObjects when not visible.
- 在非交互式元素(标签、背景、装饰图像)上禁用。
raycastTarget - 将静态和动态内容拆分到不同的画布上,以最小化重建次数。
- 画布重建成本应<1-2ms,以维持72/90Hz帧率。
- 每个字体家族在所有文本元素中共享一个SDF字体资源。
- 当不可见时禁用画布或GameObject。
Troubleshooting
故障排除
| Symptom | Cause | Fix |
|---|---|---|
| Pink/magenta text | TMP resources not imported | Use tmp-resources skill |
| Buttons not clickable in VR | Missing VR interaction on Canvas | Call |
| Buttons work in Editor, not on device | Missing interaction rig | Call |
| UI too small/large | Wrong canvas scale | Set to 0.001, verify with validation script |
| Children offset in Z | Auto-positioning bug | Set |
| Children scaled to 1000x | Auto-scale compensation | Set |
| Text blurry | Low atlas resolution or small font | Use SDF 2048x2048+, font size 48+ |
| Frame drops | Canvas rebuilding too often | Split into static/dynamic canvases |
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 粉色/品红色文本 | 未导入TMP资源 | 使用tmp-resources技能 |
| VR中按钮无法点击 | Canvas上缺少VR交互 | 调用 |
| 按钮在编辑器中可用但在设备上不可用 | 缺少交互装置 | 调用 |
| UI太小/太大 | 画布缩放比例错误 | 设置为0.001,使用验证脚本确认 |
| 子元素在Z轴偏移 | 自动定位错误 | 将所有子元素的 |
| 子元素缩放至1000倍 | 自动缩放补偿 | 将所有子元素的 |
| 文本模糊 | 图集分辨率低或字体过小 | 使用2048x2048+的SDF图集,字体尺寸48+ |
| 帧率下降 | 画布重建过于频繁 | 拆分为静态/动态画布 |
Checklists
检查清单
Interactive UI (buttons, sliders, etc.)
交互式UI(按钮、滑块等)
[ ] TMP Essential Resources imported
[ ] Canvas: renderMode = World Space
[ ] Canvas: localScale = [0.001, 0.001, 0.001]
[ ] Canvas: positioned 1.5-3m from user
[ ] All children: localScale = [1, 1, 1]
[ ] All children: localPosition.z = 0
[ ] Sizes via sizeDelta, not scale
[ ] meta_add_canvas_interaction_ray called on Canvas
[ ] PointableCanvas component verified on Canvas
[ ] Pointable Canvas Module exists in scene
[ ] Interaction rig present (meta_add_interactionrig)
[ ] Buttons >= 250x100 units
[ ] Button spacing >= 50 units
[ ] Text >= 48pt, raycastTarget = false on non-interactive text
[ ] Validation script run and passed[ ] 已导入TMP Essential Resources
[ ] Canvas:renderMode = World Space
[ ] Canvas:localScale = [0.001, 0.001, 0.001]
[ ] Canvas:放置在距离用户1.5-3米处
[ ] 所有子元素:localScale = [1, 1, 1]
[ ] 所有子元素:localPosition.z = 0
[ ] 通过sizeDelta设置尺寸,而非缩放
[ ] 已对Canvas调用meta_add_canvas_interaction_ray
[ ] 已验证Canvas上存在PointableCanvas组件
[ ] 场景中存在Pointable Canvas Module
[ ] 存在交互装置(已调用meta_add_interactionrig)
[ ] 按钮尺寸≥250x100单位
[ ] 按钮间距≥50单位
[ ] 文本尺寸≥48pt,非交互式文本的raycastTarget = false
[ ] 已运行验证脚本并通过Display-only UI (HUD, score, labels)
仅显示UI(HUD、分数、标签)
[ ] TMP Essential Resources imported
[ ] Canvas: renderMode = World Space
[ ] Canvas: localScale = [0.001, 0.001, 0.001]
[ ] All children: localScale = [1, 1, 1]
[ ] All children: localPosition.z = 0
[ ] Text >= 48pt
[ ] raycastTarget = false on all elements
[ ] NO interaction components needed[ ] 已导入TMP Essential Resources
[ ] Canvas:renderMode = World Space
[ ] Canvas:localScale = [0.001, 0.001, 0.001]
[ ] 所有子元素:localScale = [1, 1, 1]
[ ] 所有子元素:localPosition.z = 0
[ ] 文本尺寸≥48pt
[ ] 所有元素的raycastTarget = false
[ ] 不需要任何交互组件Integration with other skills
与其他技能的集成
- tmp-resources: Use first to import TMP Essential Resources before creating any UI.
- unity-placement: Use for positioning canvases relative to other scene objects.
- tmp-resources:在创建任何UI之前,先使用此技能导入TMP Essential Resources。
- unity-placement:用于相对于场景中的其他对象定位画布。