hz-unity-meta-quest-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Meta 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_RunCommand
:
csharp
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_RunCommand
:
csharp
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
Unity_RunCommand
to create and configure the canvas:
csharp
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_RunCommand
创建并配置画布:
csharp
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:
    Canvas sizeDelta * scale = meters
    . Example: 1920 * 0.001 = 1.92m wide.
  • 渲染模式:始终使用World Space。Screen Space模式会破坏立体渲染。
  • 缩放比例:所有轴均为0.001(画布中的1单位 = 世界中的1毫米)。
  • CanvasScaler:移除它。物理尺寸由世界缩放控制,而非屏幕适配。
  • 距离:放置在距离用户1.5-3米处。永远不要小于0.5米。文本可读的最大距离为5米。
  • 物理尺寸公式:
    Canvas sizeDelta * scale = 米
    。示例:1920 * 0.001 = 1.92米宽。

Step 2: Create child UI elements

步骤2:创建子UI元素

All child elements (panels, buttons, text) must follow these rules:
  • localScale: Always
    [1, 1, 1]
    . Never scale children to compensate for canvas scale.
  • localPosition.z: Always
    0
    . Children must sit on the canvas plane.
  • Size control: Use
    RectTransform.sizeDelta
    and anchors, never scale.
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_RunCommand
:
csharp
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_RunCommand
验证关键属性:
csharp
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
meta_add_canvas_interaction_ray
with the canvas name:
meta_add_canvas_interaction_ray  with  NameOrID: "<your canvas name>"
This adds:
RayInteractable
,
PointableCanvas
,
ISDK_RayCanvasInteraction
child, and a scene-level
Pointable Canvas Module
.
When to use: menus, settings panels, any UI beyond arm's reach.
适用于大多数VR UI——允许用户使用控制器或手部射线指向UI:
调用Meta Unity扩展MCP工具
meta_add_canvas_interaction_ray
并传入画布名称:
meta_add_canvas_interaction_ray  with  NameOrID: "<your canvas name>"
这会添加:
RayInteractable
、
PointableCanvas
、
ISDK_RayCanvasInteraction
子对象,以及场景级别的
Pointable 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
meta_add_canvas_interaction_poke
with the canvas name:
meta_add_canvas_interaction_poke  with  NameOrID: "<your canvas name>"
This adds:
PokeInteractable
,
PointableCanvas
, close-range collision detection.
When to use: control panels on surfaces, virtual keyboards, diegetic UI on props.
适用于手臂范围内(<0.8米)的物理触摸式交互:
调用Meta Unity扩展MCP工具
meta_add_canvas_interaction_poke
并传入画布名称:
meta_add_canvas_interaction_poke  with  NameOrID: "<your canvas name>"
这会添加:
PokeInteractable
、
PointableCanvas
、近距离碰撞检测。
适用场景:表面上的控制面板、虚拟键盘、道具上的嵌入式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_RunCommand
:
csharp
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_RunCommand
验证:
csharp
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
OVRInteractionComprehensive
as a child of the Camera Rig, providing all hand/controller interactors. Requires
OVRCameraRig
to be in the scene first.
确保场景中存在交互装置:
meta_add_interactionrig  (无参数)
这会在Camera Rig下添加
OVRInteractionComprehensive
,提供所有手部/控制器交互器。要求场景中已存在
OVRCameraRig
。

VR UI sizing reference

VR UI尺寸参考

All sizes assume canvas scale of 0.001 (1 unit = 1mm).
所有尺寸均假设画布缩放比例为0.001(1单位=1毫米)。

Element sizes (minimum)

元素最小尺寸

ElementsizeDelta (units)Physical size
Button (small)250 x 10025cm x 10cm
Button (standard)300 x 12030cm x 12cm
Button (large)400 x 15040cm x 15cm
Dropdown500 x 14050cm x 14cm
Slider600 x 8060cm x 8cm
Toggle120 x 12012cm x 12cm
Input Field800 x 14080cm x 14cm
Panel/Menu1200-2000 x 800-14001.2-2m x 0.8-1.4m
元素sizeDelta(单位)物理尺寸
小型按钮250 x 10025cm x 10cm
标准按钮300 x 12030cm x 12cm
大型按钮400 x 15040cm x 15cm
下拉菜单500 x 14050cm x 14cm
滑块600 x 8060cm x 8cm
切换按钮120 x 12012cm x 12cm
输入框800 x 14080cm x 14cm
面板/菜单1200-2000 x 800-14001.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时)

DistanceMinimumComfortableLarge/Title
1.5m32pt40-48pt60-72pt
2.0m36pt48-56pt72-84pt
2.5m40pt52-64pt84-96pt
3.0m48pt64-72pt96-120pt
Never use auto-sizing in VR. Never use legacy Text components — always TextMeshPro.
距离最小尺寸舒适尺寸大字体/标题
1.5m32pt40-48pt60-72pt
2.0m36pt48-56pt72-84pt
2.5m40pt52-64pt84-96pt
3.0m48pt64-72pt96-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
    raycastTarget
    on non-interactive elements (labels, backgrounds, decorative images).
  • 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

故障排除

SymptomCauseFix
Pink/magenta textTMP resources not importedUse tmp-resources skill
Buttons not clickable in VRMissing VR interaction on CanvasCall
meta_add_canvas_interaction_ray
Buttons work in Editor, not on deviceMissing interaction rigCall
meta_add_interactionrig
UI too small/largeWrong canvas scaleSet to 0.001, verify with validation script
Children offset in ZAuto-positioning bugSet
localPosition.z = 0
on all children
Children scaled to 1000xAuto-scale compensationSet
localScale = Vector3.one
on all children
Text blurryLow atlas resolution or small fontUse SDF 2048x2048+, font size 48+
Frame dropsCanvas rebuilding too oftenSplit into static/dynamic canvases
症状原因解决方法
粉色/品红色文本未导入TMP资源使用tmp-resources技能
VR中按钮无法点击Canvas上缺少VR交互调用
meta_add_canvas_interaction_ray
按钮在编辑器中可用但在设备上不可用缺少交互装置调用
meta_add_interactionrig
UI太小/太大画布缩放比例错误设置为0.001,使用验证脚本确认
子元素在Z轴偏移自动定位错误将所有子元素的
localPosition.z
设置为0
子元素缩放至1000倍自动缩放补偿将所有子元素的
localScale
设置为
Vector3.one
文本模糊图集分辨率低或字体过小使用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:用于相对于场景中的其他对象定位画布。