Skip to content
Development
Skill

/hz-unity-meta-quest-ui

Configures Unity UI for Meta Quest and Horizon OS VR development — world-space canvases, TextMesh Pro setup, comfortable sizing, viewing distances, and interaction readiness.

From plugin
meta-vr
17629 skills1 hook1 MCP
Install
$ npx -y skills add meta-quest/agentic-tools --skill hz-unity-meta-quest-ui --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/hz-unity-meta-quest-ui

Context preview

The summary Claude sees to decide when to auto-load this skill.

Configures Unity UI for Meta Quest and Horizon OS VR development — world-space canvases, TextMesh Pro setup, comfortable sizing, viewing distances, and interaction readiness.

SKILL.md

hz-unity-meta-quest-ui.SKILL.md
name: hz-unity-meta-quest-ui
license: Apache-2.0
description: Configures Unity UI for Meta Quest and Horizon OS VR development — world-space canvases, TextMesh Pro setup, comfortable sizing, viewing distances, and interaction readiness.

Meta Quest VR UI Setup

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

Prerequisite: TMP Essential Resources

Before creating ANY VR UI, verify TMP resources are imported. Use `Unity_RunCommand`:

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.

Step 1: Create World Space Canvas

Use `Unity_RunCommand` to create and configure the canvas:

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);
    }
}

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.

Step 2: Create child UI elements

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.
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);
    }
}

Step 3: Validate created UI

After creating UI, verify critical properties with `Unity_RunCommand`:

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 fou
Read more
Ships withmeta-vr

Agentic skills and tools for Meta Quest and Horizon OS development.

Get the whole plugin