mcpField
H
hz-unity-meta-quest-ui

hz-unity-meta-quest-ui

github.com/meta-questデザイン・UI241239 回インストール5 週間前に更新
リポジトリ

hz-unity-meta-quest-ui について

Meta Quest向けUnityプロジェクトでVR UIを作るためのガイドです。Canvas設定、TextMesh Pro、レイアウト検証、操作設定を扱い、サイズ基準とトラブルシューティングも示します。

主な機能

  1. 01World Space Canvasの設定
  2. 02TextMesh ProとUIサイズの設定
  3. 03Canvasと子要素のレイアウト検証
  4. 04レイ・ポーク・視線操作の追加
  5. 05VR UIの一般的な問題の解決

活用シーン

  1. 01Questのメニューや操作パネルの作成
  2. 02文字色や鮮明さの問題の修正
  3. 03ヘッドセットで反応しないボタンの診断
  4. 04Canvas倍率と要素配置の調整

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/meta-quest/agentic-tools --skill hz-unity-meta-quest-ui

インストール後、Claude Code で/hz-unity-meta-quest-uiを入力して読み込まれたか確認します。

同梱ファイルreferences/unity-mcp-fallback.md

詳細

Meta VR UI のセットアップ

このスキルを使うタイミング

次の場合は、このスキルを自動的に使用します。

  • VR 用の Canvas をセットアップするとき
  • VR プロジェクトで TextMesh Pro を使って UI テキストを作成するとき
  • VR でボタン、スライダー、その他のインタラクティブな UI を追加するとき
  • VR でピンク/マゼンタのテキスト、クリックできないボタン、UI のサイズの問題が報告されたとき
  • Canvas で VR インタラクション(レイまたはポーク)を設定するとき

エディターの操作

シーンの YAML を手作業で編集するのではなく、開いているエディターに対して unity-cli を使って UI を構築してください。エディターは、実際にアクティブなシーンに変更を適用します。

unity status --format json          # look for state "ready"
unity command run_script --file AgentScripts/BuildUI.cs --entry BuildUI.Run --format json

Canvas の構築は複数のコンポーネントに関わる作業なので、単一コマンドを連続して実行するのではなく、run_script の .cs ファイル(Assets/ の外側に置き、プロジェクトルートからの相対パスで指定)で行います。シーンを変更する前にスクリプトをコンパイルチェックするには、--dry_run true を追加してください。ただし、判定結果は data.result.success から確認してください。コンパイルに失敗しても、外側の success: true と終了コード 0 が返され、エラーは data.result.diagnostics に含まれます。接続方法、--project-path、Safe Mode からの復旧については unity-cli スキルを、MCP の同等機能については references/unity-mcp-fallback.md を参照してください。

単一プロパティの読み書きは、スクリプトではなく直接コマンドを使うほうが効率的です。

unity command get_component_properties --target MenuUI --type Canvas --format json
unity command set_transform --target MenuUI --position "[0,1.5,2]" --format json
unity command find_gameobjects --name MenuUI --format json

配列パラメーターには、1 つの引数として JSON 配列(--position "[0,1.5,2]")を指定します。スペース区切りやカンマ区切りの値は INVALID_COMMAND_ARGS で拒否されます。set_transform が書き込むのはローカル位置です。親が設定された Canvas については、unity-placement スキルを参照してください。

前提条件:TMP Essential Resources

VR UI を作成する前に、TMP リソースがインポートされていることを必ず確認してください。

unity command find_assets --name "LiberationSans SDF" --format json

data.result.count が 0 の場合、リソースが見つかりません。先に tmp-resources スキルを使ってください。

ステップ 1:World Space Canvas を作成する

// AgentScripts/BuildUI.cs
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;

public static class BuildUI
{
    public static string Run()
    {
        // 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);

        Undo.RegisterCreatedObjectUndo(go, "Create VR Canvas");
        EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
        return $"Created VR Canvas '{go.name}'. Scale: {rt.localScale}, Size: {rt.sizeDelta}, Position: {rt.position}";
    }
}

形状が適切になったら、保存してください。

unity command save_scene --format json
Canvas のルール
  • Render Mode:必ず World Space にします。Screen Space モードではステレオレンダリングに問題が生じます。
  • スケール:すべての軸で 0.001 にします(Canvas 上の 1 単位 = ワールド空間の 1mm)。
  • CanvasScaler:削除します。物理的なサイズは画面への適応ではなく、ワールドスケールで制御します。
  • 距離:ユーザーから 1.5~3m の位置に配置します。0.5m より近づけないでください。テキストを読み取れる距離は最大 5m です。
  • 物理サイズの計算式:Canvas sizeDelta * scale = meters。例:1920 * 0.001 = 幅 1.92m。

ステップ 2:子 UI 要素を作成する

すべての子要素(パネル、ボタン、テキスト)は、次のルールに従う必要があります。

  • localScale:必ず [1, 1, 1] にします。Canvas のスケールを補うために子要素を拡大縮小しないでください。
  • localPosition.z:必ず 0 にします。子要素は Canvas の平面上に配置してください。
  • サイズの調整:スケールではなく、RectTransform.sizeDelta とアンカーを使います。
// AgentScripts/BuildPanel.cs
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;

public static class BuildPanel
{
    public static string Run()
    {
        // Replace "MenuUI" with the actual canvas name used in Step 1
        var canvas = GameObject.Find("MenuUI");
        if (canvas == null) return "ERROR: Canvas 'MenuUI' not found.";

        // 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;

        Undo.RegisterCreatedObjectUndo(panel, "Create VR Panel");
        EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
        return $"Created panel with button. Panel scale: {panelRT.localScale}, Button scale: {btnRT.localScale}";
    }
}

ステップ 3:作成した UI を検証する

Canvas 自体を直接コマンドで確認してください。

unity command get_component_properties --target MenuUI --type Canvas --format json
unity command get_scene_hierarchy --format json                # whole active scene, incl. children

get_scene_hierarchy --path には開いているシーンのパスを指定します。GameObject は指定できません。--path MenuUI を指定すると、"Scene 'MenuUI' is not open." というエラーになります。--path を省略し、アクティブなシーンから MenuUI のサブツリーを確認してください。

エディターでの確認だけでは不十分です。ビルドをデプロイし、ヘッドセットから metavr capture screenshot でスクリーンショットを取得して、ユーザーが見る状態の UI を確認してください。

すべての子要素を検査して自動修正するには、スクリプトを使います。

// AgentScripts/ValidateUI.cs
using System.Text;
using UnityEngine;

public static class ValidateUI
{
    public static string Run()
    {
        // Replace "MenuUI" with the actual canvas name
        var canvas = GameObject.Find("MenuUI");
        if (canvas == null) return "ERROR: Canvas not found.";

        var crt = canvas.GetComponent<RectTransform>();
        var c = canvas.GetComponent<Canvas>();
        var sb = new StringBuilder();

        if (c.renderMode != RenderMode.WorldSpace)
            sb.AppendLine("FAIL: Canvas renderMode is not World Space.");

        if (Mathf.Abs(crt.localScale.x - 0.001f) > 0.0001f)
            sb.AppendLine($"FAIL: Canvas scale is {crt.localScale}, expected 0.001.");

        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)
            {
                sb.AppendLine($"FIXED: '{child.name}' had localScale {rt.localScale}, expected (1,1,1).");
                rt.localScale = Vector3.one;
            }
            if (Mathf.Abs(rt.localPosition.z) > 0.01f)
            {
                sb.AppendLine($"FIXED: '{child.name}' had localPosition.z={rt.localPosition.z}, expected 0.");
                rt.localPosition = new Vector3(rt.localPosition.x, rt.localPosition.y, 0f);
            }
        }

        return sb.Length == 0 ? "Validation PASSED. All properties correct." : sb.ToString();
    }
}

この方法で検出できる一般的な問題:

  • Canvas のスケールが 0.001 ではなく 1,1,1 のままになっている
  • 親を補うために子要素が自動的に拡大縮小されている(例:1000x)
  • 子要素が Z 軸方向にずれている(例:localPosition.z = -2500)

ステップ 4:VR インタラクションを追加する(インタラクティブ UI のみ)

Canvas にインタラクティブな要素(ボタン、スライダー、ドロップダウン、トグル、入力フィールド)がある場合にのみ、インタラクションを追加してください。 表示専用 UI(HUD、スコア、ラベル)には追加しません。

判断フロー
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.
接続する — ISDK クイックアクション

サポートされているエントリポイントは ISDK クイックアクション API で、run_script を通じて呼び出します。シグネチャと、これらの呼び出しに組み込まれている動作については、hz-unity-meta-interaction-sdk スキルを参照してください。

// AgentScripts/AddCanvasInteraction.cs
using Oculus.Interaction.Editor.QuickActions;   // Editor-only assembly
using UnityEngine;

public static class AddCanvasInteraction
{
    public static string Run()
    {
        var target = GameObject.Find("MenuUI");
        QuickActionsAPI.AddRayCanvasInteraction(target);    // or AddPokeCanvasInteraction
        return "ok";
    }
}

これらの呼び出しに渡すのは、キャンバスの GameObject であり、Canvas コンポーネントではありません — Canvas を渡すとコンパイルエラーになります(cannot convert from 'UnityEngine.Canvas' to 'UnityEngine.GameObject')。確認済みのシグネチャは次のとおりです。

インタラクションシグネチャ使用する場面
Ray(デフォルト)AddRayCanvasInteraction(GameObject target, bool fixPointableCanvasModule = true)メニュー、設定パネル、手の届かない範囲にある UI
Poke(近距離)AddPokeCanvasInteraction(GameObject target, bool fixPointableCanvasModule = true)表面上のコントロールパネル、仮想キーボード、小道具上のダイエジェティック UI(< 0.8m)
GazeAddGazeCanvasInteraction(GameObject target, bool enableRayFallback = true, bool addToChildCanvases = true, bool fixPointableCanvasModule = true)視線追跡による選択
両方(上級)Ray を先に追加し、次に pokeユーザーがポインティングまたはタッチできるハイブリッド UI

Ray は RayInteractable、PointableCanvas、ISDK_RayCanvasInteraction の子オブジェクト、およびシーンレベルの Pointable Canvas Module を追加します。Poke は PokeInteractable、PointableCanvas、および近距離用の衝突検出を追加します。

PointableCanvasModule がプレハブまたは未ロードの加算シーンにある場合は、fixPointableCanvasModule: false を渡してください。そうしないと、開いているシーンにもう一つ追加されます。

Meta Unity 拡張機能を備えた Unity MCP サーバーを使用している場合は、同等のツールとして meta_add_canvas_interaction_ray / meta_add_canvas_interaction_poke を使います — references/unity-mcp-fallback.md を参照してください。

インタラクションが追加されたことを確認する
unity command get_component_properties --target MenuUI --type PointableCanvas --format json
unity command find_gameobjects --name "Pointable Canvas Module" --format json

get_component_properties では、--target が GameObject の場合、必ず --type が必要です(指定しないと呼び出しは失敗します:"'type' is required when 'target' is a GameObject.")。また、コンポーネントの一覧ではなく1つのコンポーネントを対象とするため、確認対象のコンポーネント名を指定してください。成功すればコンポーネントが存在し、型名を示すエラーが返れば存在しません。

キャンバス上の PointableCanvas と、シーン内の Pointable Canvas Module が最低限必要です。PointableElement の配線は Awake() で割り当てられるため、プレハブアセットでは常に null と表示されます。代わりにシリアライズされたフィールドを確認してください。

unity command get_serialized_fields --target <interactable> --field _pointableElement --format json
ボタンが Editor では動作するのに Meta VR デバイスでは動作しない場合

実行時にシーンへインタラクションリグが必要です。

using Oculus.Interaction.OVR.Editor.QuickActions;
OVRQuickActionsAPI.AddOVRInteractionRig();      // also adds an OVRCameraRig if the scene has none

これにより、すべてのハンド/コントローラーインタラクターが提供されます。構築されるリグと、「リグがない状態でインタラクタブルを作成した場合」の注意事項については、hz-unity-meta-interaction-sdk を参照してください。

VR UI サイズのリファレンス

すべてのサイズはキャンバスのスケール 0.001(1 単位 = 1mm)を前提としています。

要素のサイズ(最小)
要素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
ボタンの間隔

誤操作を防ぐため、インタラクティブな要素の間は最低 50 単位(5cm)空けてください。

フォントサイズ(キャンバススケール 0.001 の場合)
距離最小快適大/タイトル
1.5m32pt40-48pt60-72pt
2.0m36pt48-56pt72-84pt
2.5m40pt52-64pt84-96pt
3.0m48pt64-72pt96-120pt

VR では自動サイズ調整を使用しないでください。レガシーの Text コンポーネントは使用せず、常に TextMeshPro を使用してください。

色
  • テキスト:オフホワイト(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 以上を推奨します。
  • 透明な背景より、不透明な背景を優先してください(レンダリングコストが低いため)。

パフォーマンスに関するヒント

  • ラベル、背景、装飾用画像など、操作できない要素では raycastTarget を無効にします。
  • 再構築を最小限に抑えるため、静的コンテンツと動的コンテンツを別々のキャンバスに分けます。
  • 72/90Hzを維持するには、キャンバスの再構築コストを1~2ms未満にします。
  • すべてのテキスト要素で、フォントファミリーごとにSDFフォントアセットを1つ共有します。
  • 表示されていないキャンバスまたはGameObjectを無効にします。

トラブルシューティング

症状原因解決方法
テキストがピンク/マゼンタになるTMPリソースがインポートされていないtmp-resourcesスキルを使用します
VRでボタンをクリックできないCanvasにVRインタラクションがないQuickActionsAPI.AddRayCanvasInteraction(go)
Editorではボタンが動作するが、デバイスでは動作しないインタラクションリグがないOVRQuickActionsAPI.AddOVRInteractionRig()
UIが小さすぎる/大きすぎるキャンバスのスケールが正しくない0.001に設定し、検証スクリプトで確認します
子要素のZ位置がずれている自動配置の不具合すべての子要素で localPosition.z = 0 に設定します
子要素のスケールが1000倍になっている自動スケール補正すべての子要素で localScale = Vector3.one に設定します
テキストがぼやけるアトラス解像度が低い、またはフォントサイズが小さいSDF 2048x2048以上、フォントサイズ48以上を使用します
フレームレートが低下するキャンバスが頻繁に再構築されるキャンバスを静的/動的に分けます
unity command が接続できないコンパイルエラーによりEditorがSafe Modeになっているunity pipeline list を実行し、エラーを修正してUnityを再起動します

チェックリスト

インタラクティブ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
[ ] AddRayCanvasInteraction called on Canvas
[ ] PointableCanvas component verified on Canvas
[ ] Pointable Canvas Module exists in scene
[ ] Interaction rig present (AddOVRInteractionRig)
[ ] Buttons >= 250x100 units
[ ] Button spacing >= 50 units
[ ] Text >= 48pt, raycastTarget = false on non-interactive text
[ ] Validation script run and passed
[ ] Scene saved (unity command save_scene)
表示専用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
[ ] Scene saved (unity command save_scene)

他のスキルとの連携

  • tmp-resources:UIを作成する前にTMP Essential Resourcesをインポートするため、最初に使用します。
  • hz-unity-meta-interaction-sdk:ステップ4で使用するQuick Actions APIを担当します。
  • unity-placement:他のシーンオブジェクトに対するキャンバスの配置に使用します。
  • unity-cli:Editorへの接続、--project-path、Safe Modeからの復旧に使用します。

コメント