npx claudepluginhub meta-quest/agentic-tools --plugin meta-vrThis skill uses the workspace's default tool permissions.
Use this skill automatically when:
Designs game UI with Unity uGUI: Canvas, RectTransform, Anchors for HUD, health bars, inventory, skill bars. Supports mobile responsive design and Safe Area. Use for game UI, HUD, Canvas setup.
Guides Unity UI development using UGUI (Canvas, RectTransform, layouts) and UI Toolkit (USS, UXML), covering system selection, anchoring, performance, and common patterns.
Provides Apple Human Interface Guidelines for visionOS apps on Apple Vision Pro, covering spatial layout, eye/hand input, and immersive experiences. Use for RealityKit or spatial UI tasks.
Share bugs, ideas, or general feedback.
Use this skill automatically when:
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.
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 sizeDelta * scale = meters. Example: 1920 * 0.001 = 1.92m wide.All child elements (panels, buttons, text) must follow these rules:
[1, 1, 1]. Never scale children to compensate for canvas scale.0. Children must sit on the canvas plane.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);
}
}
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 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:
Only add interaction when your Canvas has interactive elements (buttons, sliders, dropdowns, toggles, input fields). Skip this for display-only UI (HUD, score, labels).
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.
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.
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.
Call ray first, then poke, for hybrid UIs where users can point OR touch.
After calling the Meta Unity extension MCP tool, verify 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 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.");
}
}
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.
All sizes assume canvas scale of 0.001 (1 unit = 1mm).
| 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 |
Minimum 50 units (5cm) between interactive elements to prevent mis-clicks.
| 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.
raycastTarget on non-interactive elements (labels, backgrounds, decorative images).| 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 meta_add_canvas_interaction_ray |
| Buttons work in Editor, not on device | Missing interaction rig | Call meta_add_interactionrig |
| UI too small/large | Wrong canvas scale | Set to 0.001, verify with validation script |
| Children offset in Z | Auto-positioning bug | Set localPosition.z = 0 on all children |
| Children scaled to 1000x | Auto-scale compensation | Set localScale = Vector3.one on all children |
| 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 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 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