開発
開発
プラットフォームを選択

空間アンカーのチュートリアル

更新日時: 2024/08/02

概要

空間アンカーは、基準となるワールドロックされたフレームです。アプリでこれを使うことにより、セッションを超えてオブジェクトの配置や向きを持続させることができます。この一貫性によりユーザーは、ゲームやアプリを再開するたびに、前回からのつながりや、親しみを感じることができます。
空間アンカーを使うで詳しく説明されているように、空間アンカーをうまく配置する上で重要な要素は、OVRSpatialAnchorコンポーネントです。このチュートリアルでは、スターターサンプルである空間アンカーサンプルに似たプロジェクトを構成していきます。ただし、機能の数は少なくなります。このチュートリアルの内容は次のとおりです。
  • OVRManagerOVRPassthroughLayerを含むシーンを作成します。
  • 2つのprefabカプセル(1つはLeftControllerAnchor用、もう1つはRightControllerAnchor用)を作成します。これらは常にコントローラーに表示されます。
  • 各コントローラーにつき1つずつ、計2つの配置prefabを作成します。これらにはカプセルも含まれていますが、これらのカプセルが表示されるのは、その作成時点とスペースにアンカーした時点だけです。
  • 空間アンカーの作成、破棄、読み込み、消去の方法を指定する最小限のスクリプトを作成します。
  • 空間アンカーの管理コードをカプセルにリンクし、prefabを変換するためのGameObjectを作成します。
  • そのGameObjectにスクリプトを追加し、スクリプトの公開メンバーにカプセルと変換を割り当てます。
実行時にシーンに空間アンカーを追加するには、シーンを操作するをご覧ください。

前提条件

このチュートリアルを進める前に、パススルーの基本チュートリアルに記載された設定手順を完了して、パススルー用のUnity開発環境を設定してください。

新しいシーンを作成する

  1. [Project (プロジェクト)]タブの[Assets (アセット)]フォルダーの下に、SpatialAnchors Tutorialという名前の新しいフォルダーを作成します。次にそれを新しいオブジェクトの現行フォルダーとして選択します。
  2. [Project Hierarchy (プロジェクト階層)][SampleScene]を右クリックし、[Save Scene As (シーンを別名保存)]を選びます。新しいシーンに、SpatialAnchorsTutorialのような固有の名前を付けます。これが、階層内のアクティブなシーンになります。
  3. シーンから、[Directional Light (平行光源)][OVRCameraRig]以外のゲームオブジェクトをすべて削除します。

主要なシーンコンポーネントを設定する

このシーンを設定するために必要なコンポーネントは、OVRCameraRigOVRSceneManagerOVRPassthroughLayerです。

OVRCameraRigとOVRManagerを設定する

OVRCameraRigは、シーンのメインカメラです。これを次のように設定します。これらはパススルーシーンの典型的な設定です。
  1. [Main Camera (メインカメラ)]を選択してDeleteキーを押し、シーンから削除します(まだ削除していない場合)。
  2. [Project (プロジェクト)]ペインでOVRCameraRig prefabを検索します(まだ検索していない場合)。見つかったprefabをSpatialAnchorsTutorialシーンまでドラッグします。次に、そのprefabを選択すると、[Inspector (インスペクター)]にそのプロパティが表示されます。
  3. [Inspector (インスペクター)]タブで、次のようにします。
    a. [OVRManager]の下で、[Tracking Origin Type (トラッキング原点タイプ)][Eye Level (アイレベル)]に設定します。b. [OVRManager]の下で、[Quest Features (Questの機能)] > [General (一般)]に移動します。c. [Anchor Support (アンカーサポート)]ドロップダウンから、[Enabled (有効にする)]を選択してアンカーサポートをオンにします。d. [Insight Passthrough (インサイトパススルー)]の下で、[Enable Passthrough (パススルーを有効にする)]チェックボックスをオンにします。e. [Shared Spatial Anchor Support (共有空間アンカーサポート)]ドロップダウンから、[Supported (サポート)]を選択します。
Set up the OVRCameraRigSet up the OVRCameraRig

OVRPassthroughLayerを設定する

OVRPassthroughLayerを追加すると、自分の環境内のオブジェクトからボールが跳ね返るようになります。
  1. [Hierarchy (階層)]ペインで、OVRCameraRigゲームオブジェクトを選択します。
  2. [Inspector (インスペクター)]の下部で、[Add Component (コンポーネントを追加)]を選びます。[OVR Passthrough Layer (OVRパススルーレイヤー)]を検索して選択します。
  3. [OVR Passthrough Layer (OVRパススルーレイヤー)]コンポーネントを展開します。[Projection Surface (投影サーフェス)][Reconstructed (再構成)]に、[Placement (配置)][Underlay (アンダーレイ)]に設定します。
Set Up the OVRPassthroughLayer

prefabコントローラーカプセルを作成する

各コントローラーの近くに浮動表示される緑と赤のカプセルprefabを作成します。これらのカプセルは、それより小さいアンカーカプセルの作成場所を示します。
  1. [Project (プロジェクト)]タブで[SpatialAnchors Tutorial (SpatialAnchorsチュートリアル)]フォルダーを選択し、それを新しいオブジェクトの現行フォルダーにします。
  2. [Assets (アセット)]メニューから、[Create (作成)] > [Scene (シーン)] > [Prefab]を選択します。新しいprefabの名前として、SaveablePrefabを指定します。新しいprefabをダブルクリックして編集します。
  3. [Inspector (インスペクター)]で、Transformコンポーネントに以下の値を追加します。
    a. [Position (位置)]プロパティを、X = -0.2、Y = 1.03、Z = 0に設定します。b. [Rotation (回転)]プロパティを、X = 0、Y = 0、Z = 0に設定します。c. [Scale (スケール)]プロパティを、X = 0.1、Y = 0.1、Z = 0.1に設定します。
  4. [Hierarchy (階層)]SaveablePrefabオブジェクトを右クリックし、[3D Object (3Dオブジェクト)] > [Capsule (カプセル)]を選びます。新しいカプセルがSaveablePrefabオブジェクトの子であることを確認してください。
  5. [Hierarchy (階層)]で、新しいカプセルを選択します。
  6. [Project (プロジェクト)]ペインで、Green (緑)マテリアルを探します。マテリアルを[SpatialAnchors Tutorial (SpatialAnchorsチュートリアル)]のアセットフォルダーにドラッグします。
  7. [Inspector (インスペクター)][Materials (マテリアル)]プロパティで、[Green (緑)]マテリアルを選択します。
New SATutorialSceneNew SATutorialScene
前述の手順を繰り返して、NonSaveablePrefabという名前のprefabを作成します。ただし、次の変更を加えます。
  1. [Position (位置)]プロパティで、位置Xの値-0.2の代わりにX = 2.0と設定します。
  2. このprefabでは、Green (緑)マテリアルの代わりに、[Red (赤)]マテリアルを選びます。

SaveablePlacementとNonSaveablePlacementのprefabを作成する

コントローラーごとに別個の配置prefabが必要です。これらのprefabには、コントローラーの人差し指トリガーを押すと作成されるカプセルが含まれます。

SaveablePlacement prefabを作成する

  1. [Project (プロジェクト)]タブで[SpatialAnchors Tutorial (SpatialAnchorsチュートリアル)]フォルダーを選択し、それを新しいオブジェクトの現行フォルダーにします。
  2. [Assets (アセット)]メニューから、[Create (作成)] > [Scene (シーン)] > [Prefab]を選択します。新しいprefabの名前として、SaveablePlacementを指定します。
  3. SaveablePlacementをダブルクリックして編集状態にします。
  4. [Inspector (インスペクター)]で、SaveablePlacement Transformコンポーネントに以下の値を追加します。a. [Position (位置)]プロパティを、X = 0.0、Y = 0.0、Z = 0.25に設定します。b. [Rotation (回転)]プロパティを、X = 0、Y = 0、Z = 0に設定します。c. [Scale (スケール)]プロパティを、X = 0.025、Y = 0.025、Z = 0.025に設定します。
  5. [Hierarchy (階層)]SaveablePlacementオブジェクトを右クリックし、[Create Empty (空を作成)]を選びます。
  6. 新しいオブジェクトがSaveablePlacementオブジェクトの子であることを確認してから、SaveableTransformという名前を付けます。
  7. [Inspector (インスペクター)]で、SaveableTransform Transformコンポーネントに以下の値を追加します。a. [Position (位置)]プロパティを、X = 0.0、Y = 0.1、Z = 0.1に設定します。b. [Rotation (回転)]プロパティを、X = 0、Y = 0、Z = 0に設定します。c. [Scale (スケール)]プロパティを、X = 0.025、Y = 0.025、Z = 0.025に設定します。
  8. [Hierarchy (階層)]でもう一度SaveablePlacementオブジェクトを右クリックし、[3D Object (3Dオブジェクト)] > [Capsule (カプセル)]を選びます。新しいカプセルがSaveablePlacementオブジェクトの子であることを確認します。
  9. [Hierarchy (階層)]で、新しいカプセルを選択します。
  10. [Inspector (インスペクター)]で、Capsule Transformコンポーネントに以下の値を追加します。a. [Position (位置)]プロパティを、X = 0、Y = 0、Z = 0.125に設定します。b. [Rotation (回転)]プロパティを、X = 0、Y = 0、Z = 0に設定します。c. [Scale (スケール)]プロパティを、X = 0.025、Y = 0.025、Z = 0.025に設定します。
  11. [Inspector (インスペクター)][Materials (マテリアル)]プロパティで、[Green (緑)]マテリアルを選択します。

NonSaveablePlacement prefabを作成する

  1. [Assets (アセット)]メニューから、[Create (作成)] > [Scene (シーン)] > [Prefab]を選択します。新しいprefabの名前として、NonSaveablePlacementを指定します。
  2. SaveablePlacement prefabを作成するの手順を繰り返しますが、今回は、prefabの名前としてNonSaveablePlacement、新しい空オブジェクトの名前としてNonSaveableTransformを使い、Green (緑)ではなくRed (赤)のマテリアルを選びます。

左右のコントローラーアンカーにアンカー配置prefabを追加する

アンカーを配置すると、カプセルprefabが表示されます。
  1. [Hierarchy (階層)]ウィンドウで、[OVRCameraRig] > [TrackingSpace] > [LeftHandAnchor]を展開し、[LeftControllerAnchor]を選択します。
  2. プロジェクトの検索ボックスで、SaveablePlacementを検索します。このオブジェクトを、LeftControllerAnchorの子になるようにドラッグします。
  3. [Hierarchy (階層)]ウィンドウで、[OVRCameraRig] > [TrackingSpace] > [RightHandAnchor]を展開し、[RightControllerAnchor]を選択します。
  4. プロジェクトの検索ボックスでNonSaveablePlacementを検索します。このオブジェクトを、RightControllerAnchorの子になるようにドラッグします。
Add the Anchor Prefab

アンカーマネージャスクリプトを作成する

  1. [Project (プロジェクト)]ペインで[SpatialAnchors Tutorial (SpatialAnchorsチュートリアル)]フォルダーをクリックし、そのフォルダーを新しいオブジェクトの現在の配置場所にします。
  2. [Assets (アセット)]メニューから、[Create (作成)] > [Scripting (スクリプト)] > [C# Script (C#スクリプト)]を選択します。新しいスクリプトの名前としてAnchorTutorialUIManagerを指定します。
  3. 新しいスクリプトをダブルクリックして編集状態にします。
このスクリプトでは、次の処理を実行します。
  • ボタンを押すと応答する。
    • 左人差し指トリガーで、緑の空間アンカーを作成し、保存する。
    • 右人差し指トリガーで、赤の空間アンカーを作成する(保存はしない)。
    • [X]ボタンで、表示されている空間アンカーをすべて破棄する。
    • [A]ボタンで、保存済みの空間アンカーをすべて読み込む。
    • [Y]ボタンで、保存済みの空間アンカーをすべて消去する。
  • 現在緑と赤のどちらのカプセルが表示されているかをトラッキングして、それらを破壊できるようにする。
  • どちらのカプセルが保存されているか(緑のカプセル)をトラッキングし、それを簡単に読み込んだり消去したりできるようにする。
  • 保存されているカプセルのUUIDを別個にトラッキングする。これらを外部の場所(PlayerPrefsなど)に保存しておけば、保存したカプセルを今後のセッションで簡単に参照できるようになります。

シリアライズオブジェクトと作業変数を宣言する

ここでは、6つのシリアライズフィールド(先ほど作った4つのprefabに対して1つずつ、配置prefabで作成された変換のためにさらに2つ)が必要です。
AnchorTutorialUIManagerクラスの先頭に以下を追加します。
[SerializeField]
private GameObject _saveableAnchorPrefab;

[SerializeField]
private GameObject _saveablePreview;

[SerializeField]
private Transform _saveableTransform;

[SerializeField]
private GameObject _nonSaveableAnchorPrefab;

[SerializeField]
private GameObject _nonSaveablePreview;

[SerializeField]
private Transform _nonSaveableTransform;
これらのフィールドを追加すると、後ほどUnityエディターに追加した時点でUnityインスペクターUIに表示されます。
プログラムを作成するうえで、同じクラスの中に非公開フィールドもいくつか必要になります。
private List<OVRSpatialAnchor> _anchorInstances = new(); // Active instances (red and green)

private HashSet<Guid> _anchorUuids = new(); // Simulated external location, like PlayerPrefs

private Action<bool, OVRSpatialAnchor.UnboundAnchor> _onLocalized;

Awake()メソッドを作成する

クラスにAwake()メソッドがあることを確認し、それを使って次のようにクラスを初期化します。
private void Awake()
{
    if (Instance == null)
    {
        Instance = this;
        _onLocalized = OnLocalized;
    }
    else
    {
        Destroy(this);
    }
}

赤または緑のカプセルをインスタンス化する

コントローラーの人差し指トリガーを使って、各カプセルを作成します。処理はどちらのカプセルの場合も同じで、空間アンカーを作成し、それをCreateAnchor()メソッドに渡します。緑のカプセルと赤のカプセルの大きな違いは、保存可能の緑のカプセルでは、trueの値がCreateAnchor()メソッドに渡されるという点です。
クラスのUpdate()メソッドに、以下のコントローラー入力チェックを追加します。
if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) // Create a green capsule
{
    // Create a green (savable) spatial anchor
    var go = Instantiate(_saveableAnchorPrefab, _saveableTransform.position, _saveableTransform.rotation); // Anchor A
    SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: true);
}
else if (OVRInput.GetDown(OVRInput.Button.SecondaryIndexTrigger)) // Create a red capsule
{
    // Create a red (non-savable) spatial anchor.
    var go = Instantiate(_nonSaveableAnchorPrefab, _nonSaveableTransform.position, _nonSaveableTransform.rotation); // Anchor b
    SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: false);
}
SetupAnchorAsyncメソッドは、アンカーが作成されてローカライズされるのを待ちます。さらに、任意でアンカーを保存します。

緑または赤のカプセルをトラッキングおよび保存する

アンカーの保存は非同期操作であるため、async/awaitを使うことによって確実にアンカー作成が完了してからカプセルを保存するようにします。便宜上、緑と赤のカプセルに対して同じメソッドを使います。SetupAnchorAsync()を呼び出すと、アカウンティングと保存処理の両方が開始されます。
アンカーが確立されるまで待ちます。saveAnchortrueになったら、SaveAnchorAsync()を使ってアンカーの保存を試みます。
このSetupAnchorAsyncメソッドをクラスに追加します。
private async void SetupAnchorAsync(OVRSpatialAnchor anchor, bool saveAnchor)
{
    // Keep checking for a valid and localized anchor state
    if (!await anchor.WhenLocalizedAsync())
    {
        Debug.LogError($"Unable to create anchor.");
        Destroy(anchor.gameObject);
        return;
    }

    // Add the anchor to the list of all instances
    _anchorInstances.Add(anchor);

    // save the savable (green) anchors only
    if (saveAnchor && (await anchor.SaveAnchorAsync()).Success)
    {
        // Remember UUID so you can load the anchor later
        _anchorUuids.Add(anchor.Uuid);
    }
}
アンカーが作成されたら、そのアンカーを既知の保存済みアンカーのリストに追加します。

表示されているアンカーを破棄する

2つの人差し指トリガーを何度か押すと、インスタンス化されたアンカーすべてが_anchorInstancesに含まれるようになります。このチュートリアルでは、[X]ボタンを押すことによって現在のシーンから緑と赤のカプセルをすべて破棄します。
Update()メソッドに以下を追加します。
if (OVRInput.GetDown(OVRInput.Button.Three)) // x button
{
    // Destroy all anchors from the scene, but don't erase them from storage
    foreach (var anchor in _anchorInstances)
    {
        Destroy(anchor.gameObject);
    }

    // Clear the list of running anchors
    _anchorInstances.Clear();
}
ここではシーンのすべてのカプセルを破棄しますが、すでに保存された、保存可能な緑のカプセルはまだ保持されています。

アンカーを読み込むメソッドを作成する

保存されているアンカーを読み込むには、[A]ボタンを使います。Update()メソッドに以下を追加します。
if (OVRInput.GetDown(OVRInput.Button.One)) // a button
{
    LoadAllAnchors(); // Load saved anchors
}
空間アンカーの概要で説明されているように、アンカーの読み込みは3段階の処理です。
  1. 永続ストレージから空間アンカーを、そのUUIDを使って読み込みます。この時点では、このアンカーはバインドされていない状態です。
  2. バインドされていない空間アンカーのそれぞれをローカライズして、意図された仮想場所に固定します。
  3. 各空間アンカーをOVRSpatialAnchor()にバインドします。

アンカーを読み込んでローカライズする

1つのメソッド内に各アンカーを読み込み、ローカライズします。まずOVRSpatialAnchor.LoadUnboundAnchors()を使ってアンカーをすべて読み込み、次にアンカーごとにローカライズします。
クラスに以下のメソッドを追加します。
public async void LoadAllAnchors()
{
    // Load and localize
    var unboundAnchors = new List<OVRSpatialAnchor.UnboundAnchor>();
    var result = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(_anchorUuids, unboundAnchors);

    if (result.Success)
    {
        foreach (var anchor in unboundAnchors)
        {
            anchor.LocalizeAsync().ContinueWith(_onLocalized, anchor);
        }
    }
    else
    {
        Debug.LogError($"Load anchors failed with {result.Status}.");
    }
}

アンカーをバインドする

このチュートリアルでは、デリゲートを使ってアンカーをバインドしてから、それを再びシーンに追加します。クラスに以下のメソッドを追加します。
private void OnLocalized(bool success, OVRSpatialAnchor.UnboundAnchor unboundAnchor)
{
    var pose = unboundAnchor.Pose;
    var go = Instantiate(_saveableAnchorPrefab, pose.position, pose.rotation);
    var anchor = go.AddComponent<OVRSpatialAnchor>();

    unboundAnchor.BindTo(anchor);

    // Add the anchor to the running total
    _anchorInstances.Add(anchor);
}

保存されているアンカーを消去する

[Y]ボタンを押すとすべてのアンカーが消去されます。シーンから削除されるわけではなく、ストレージから削除されるだけです。
Update()メソッドに以下を追加します。
// Erase all saved (green) anchors
if (OVRInput.GetDown(OVRInput.Button.Four)) // y button
{
    EraseAllAnchors();
}
OVRSpatialAnchor.EraseAnchorsAsync()は非同期メソッドなので、アンカーを保存する場合と同じようにして結果を待機することができます。アンカーをストレージから消去した後、保存済みアンカー配列をクリアすることも必要です。
クラスに以下のメソッドを追加します。
public async void EraseAllAnchors()
{
    var result = await OVRSpatialAnchor.EraseAnchorsAsync(anchors: null, uuids: _anchorUuids);
    if (result.Success)
    {
        // Erase our reference lists
        _anchorUuids.Clear();

        Debug.Log($"Anchors erased.");
    }
    else
    {
        Debug.LogError($"Anchors NOT erased {result.Status}");
    }
}
これでスクリプトは終わりです。スクリプトを保存して、Unityエディターに戻ります。

TutorialManagerゲームオブジェクトを作成して設定する

TutorialManagerゲームオブジェクトは、アンカーprefabを、空間アンカーのローダーと、先ほど作成したスクリプトにリンクします。
  1. [Game Object (ゲームオブジェクト)]メニューから[Create Empty (空を作成)]を選びます。新しいオブジェクトの名前としてTutorialManagerを指定し、それをシーンのOVRCameraRigのピアオブジェクトにします。
  2. [Hierarchy (階層)]ウィンドウで、新しいTutorialManagerゲームオブジェクトを選択します。次に、[Inspector (インスペクター)]で、[Add Component (コンポーネントを追加)]を選択します。
  3. 先ほど作成したスクリプトAnchorTutorialUIManagerを検索して、選択します。設定が必要な6つのプロパティが表示されます。
  4. プロジェクトの検索ボックスで、SaveablePrefabを検索します。このオブジェクトを[Inspector (インスペクター)]にドラッグし、[Anchor Tutorial UI Manager (Script) (アンカーチュートリアルUIマネージャ(スクリプト))]コンポーネント内の[Saveable Anchor Prefab (保存可能アンカーprefab)]フィールドまでドラッグします。
  5. プロジェクトの検索ボックスで、SaveablePlacementを検索します。このオブジェクトを[Inspector (インスペクター)]にドラッグし、[Anchor Tutorial UI Manager (Script) (アンカーチュートリアルUIマネージャ(スクリプト))]コンポーネント内の[Saveable Preview (保存可能プレビュー)]フィールドまでドラッグします。
  6. [Hierarchy (階層)]ウィンドウで、LeftControllerAnchor > SaveablePlacementを展開します。SaveableTransform[Inspector (インスペクター)]にドラッグし、[Anchor Tutorial UI Manager (Script) (アンカーチュートリアルUIマネージャ(スクリプト))]コンポーネント内の[Saveable Transform (Transform) (保存可能変換(変換))]フィールドまでドラッグします。
  7. プロジェクトの検索ボックスで、NonSaveablePrefabを検索します。このオブジェクトを[Inspector (インスペクター)]にドラッグし、[Anchor Tutorial UI Manager (Script) (アンカーチュートリアルUIマネージャ(スクリプト))]コンポーネント内の[Non Saveable Anchor Prefab (保存不能アンカーprefab)]フィールドまでドラッグします。
  8. プロジェクトの検索ボックスでNonSaveablePlacementを検索します。このオブジェクトを[Inspector (インスペクター)]にドラッグし、[Anchor Tutorial UI Manager (Script) (アンカーチュートリアルUIマネージャ(スクリプト))]コンポーネント内の[Non Saveable Preview (保存不能プレビュー)]フィールドまでドラッグします。
  9. [Hierarchy (階層)]ウィンドウでRightControllerAnchor > Non SaveablePlacementを展開します。NonSaveableTransform[Inspector (インスペクター)]にドラッグし、[Anchor Tutorial UI Manager (Script) (アンカーチュートリアルUIマネージャ(スクリプト))]コンポーネント内の[Non Saveable Transform (Transform) (保存不能変換(変換))]フィールドまでドラッグします。
TutorialManager Game Object

プロジェクト設定ツールをチェックする

もう少しで完成です。ビルドの前に、プロジェクト設定ツールを実行する必要があります。これにより、新しいゲームオブジェクトと既存のゲームオブジェクトを組み合わせた結果として、何らかの複雑さが生じていないことを確認できます。
  1. プロジェクトとシーンを保存します。
  2. メニューで、[Edit (編集)] > [Project Settings (プロジェクト設定)] > [Meta XR]をクリックし、[Android]タブを選択します。
  3. [Checklist (チェックリスト)]で、警告やエラーの有無を確認します。[Apply All (すべて適用)][Fix (修正)]を選ぶと、Unityに対して問題解決を促すことになります。
Check the Project Setup Tool

プロジェクトを保存して実行する

  1. [File (ファイル)]メニューから、[Save (保存)]を選んでシーンを保存し、[Save Project (プロジェクトを保存)]を選んでプロジェクトを保存します。
  2. メニューから[File (ファイル)] > [Build Profiles (ビルドプロフィール)]を選択します。
  3. 自分の使っているMeta Questヘッドセットが、[Run Device (デバイスを実行)]ドロップダウンの中で選択されていることを確認します。ヘッドセットがリストにない場合は、[Refresh (更新)]をクリックしてください。
  4. [Add Open Scenes (オープンシーンを追加)]をクリックして、シーンをビルドに追加します。その他のシーンについては、選択ウィンドウで選択を解除して削除します。
  5. [Build and Run (ビルドして実行)]ボタンをクリックして、ヘッドセット上でプログラムを起動します。

シーンを操作する

アプリを起動すると、左のコントローラーには緑のカプセル、右のコントローラーには赤いカプセルが表示されます。緑のカプセルは作成時に保存されますが、赤いカプセルが保存されることはありません。
  1. 左の人差し指トリガーを1回以上押すと、小さな緑色のカプセルが作成されます。各カプセルのアンカーは、自動的にヘッドセットに保存されます。
  2. 右の人差し指トリガーを1回以上押すと、小さな赤色のカプセルが作成されます。これらのアンカーはヘッドセットに保存されません。
  3. [X]ボタンを押すと、すべてのカプセルが破棄されます。カプセルはすべてビューから削除されます。
  4. [A]ボタンを押すと、保存されているすべてのカプセルが読み込まれます。緑のカプセルだけが再び表示されます。
  5. [Y]ボタンを押すと、緑のアンカーがすべて消去されます。緑のカプセルは画面上に残ります。
  6. [X]ボタンを押すと、すべてのカプセルが破棄されます。カプセルはすべてビューから削除されます。
  7. [A]ボタンを押すと、保存されているすべてのカプセルが読み込まれます。緑のカプセルは消去されたため、もう表示されません。
Spatial anchors added at runtime

詳しくはこちら

空間アンカーについて詳しくは、このドキュメントの他のページをご覧ください。
Meta Questで空間アンカーを利用する方法を示すその他のサンプルについては、oculus-samplesのGitHubリポジトリをご覧ください。
Unity APIリファレンスについては、こちらをご覧ください。
UnityでのMeta Quest開発のスタートガイドについては、以下のドキュメントをご覧ください。

付録: 完全なAnchorTutorialUIManager.csファイル

using System;
using System.Collections.Generic;
using UnityEngine;

public class AnchorTutorialUIManager : MonoBehaviour
{
    /// <summary>
    /// Anchor Tutorial UI manager singleton instance
    /// </summary>
    public static AnchorTutorialUIManager Instance;

    [SerializeField]
    private GameObject _saveableAnchorPrefab;

    [SerializeField]
    private GameObject _saveablePreview;

    [SerializeField]
    private Transform _saveableTransform;

    [SerializeField]
    private GameObject _nonSaveableAnchorPrefab;

    [SerializeField]
    private GameObject _nonSaveablePreview;

    [SerializeField]
    private Transform _nonSaveableTransform;

    private List<OVRSpatialAnchor> _anchorInstances = new(); // Active instances (red and green)

    private HashSet<Guid> _anchorUuids = new(); // Simulated external location, like PlayerPrefs

    private Action<bool, OVRSpatialAnchor.UnboundAnchor> _onLocalized;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            _onLocalized = OnLocalized;
        }
        else
        {
            Destroy(this);
        }
    }

    // This script responds to five button events:
    //
    // Left trigger: Create a saveable (green) anchor.
    // Right trigger: Create a non-saveable (red) anchor.
    // A: Load, Save and display all saved anchors (green only)
    // X: Destroy all runtime anchors (red and green)
    // Y: Erase all anchors (green only)
    // others: no action
    void Update()
    {
        if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) // Create a green capsule
        {
            // Create a green (savable) spatial anchor
            var go = Instantiate(_saveableAnchorPrefab, _saveableTransform.position, _saveableTransform.rotation); // Anchor A
            SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: true);
        }
        else if (OVRInput.GetDown(OVRInput.Button.SecondaryIndexTrigger)) // Create a red capsule
        {
            // Create a red (non-savable) spatial anchor.
            var go = Instantiate(_nonSaveableAnchorPrefab, _nonSaveableTransform.position, _nonSaveableTransform.rotation); // Anchor b
            SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: false);
        }
        else if (OVRInput.GetDown(OVRInput.Button.One)) // a button
        {
            LoadAllAnchors();
        }
        else if (OVRInput.GetDown(OVRInput.Button.Three)) // x button
        {
            // Destroy all anchors from the scene, but don't erase them from storage
            foreach (var anchor in _anchorInstances)
            {
                Destroy(anchor.gameObject);
            }

            // Clear the list of running anchors
            _anchorInstances.Clear();
        }
        else if (OVRInput.GetDown(OVRInput.Button.Four)) // y button
        {
            EraseAllAnchors();
        }
    }

    // You need to make sure the anchor is ready to use before you save it.
    // Also, only save if specified
    private async void SetupAnchorAsync(OVRSpatialAnchor anchor, bool saveAnchor)
    {
        // Keep checking for a valid and localized anchor state
        if (!await anchor.WhenLocalizedAsync())
        {
            Debug.LogError($"Unable to create anchor.");
            Destroy(anchor.gameObject);
            return;
        }

        // Add the anchor to the list of all instances
        _anchorInstances.Add(anchor);

        // Save the saveable (green) anchors only
        if (saveAnchor && (await anchor.SaveAnchorAsync()).Success)
        {
            // Remember UUID so you can load the anchor later
            _anchorUuids.Add(anchor.Uuid);
        }
    }

    /******************* Load Anchor Methods **********************/
    public async void LoadAllAnchors()
    {
        // Load and localize
        var unboundAnchors = new List<OVRSpatialAnchor.UnboundAnchor>();
        var result = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(_anchorUuids, unboundAnchors);

        if (result.Success)
        {
            foreach (var anchor in unboundAnchors)
            {
                anchor.LocalizeAsync().ContinueWith(_onLocalized, anchor);
            }
        }
        else
        {
            Debug.LogError($"Load anchors failed with {result.Status}.");
        }
    }

    private void OnLocalized(bool success, OVRSpatialAnchor.UnboundAnchor unboundAnchor)
    {
        var pose = unboundAnchor.Pose;
        var go = Instantiate(_saveableAnchorPrefab, pose.position, pose.rotation);
        var anchor = go.AddComponent<OVRSpatialAnchor>();

        unboundAnchor.BindTo(anchor);

        // Add the anchor to the running total
        _anchorInstances.Add(anchor);
    }

    /******************* Erase Anchor Methods *****************/
    // If the Y button is pressed, erase all anchors saved
    // in the headset, but don't destroy them. They should remain displayed.
    public async void EraseAllAnchors()
    {
        var result = await OVRSpatialAnchor.EraseAnchorsAsync(anchors: null, uuids: _anchorUuids);
        if (result.Success)
        {
            // Erase our reference lists
            _anchorUuids.Clear();

            Debug.Log($"Anchors erased.");
        }
        else
        {
            Debug.LogError($"Anchors NOT erased {result.Status}");
        }
    }
}

詳しくはこちら

空間アンカーについて、詳しくは以下のページをご覧ください。
Meta Questで空間アンカーを利用する方法を示すその他のサンプルについては、oculus-samplesのGitHubリポジトリをご覧ください。
APIについて詳しくは、Unity APIリファレンスをご覧ください。
UnityでのMeta Quest開発を開始するには、はじめにをご覧ください。
ナビゲーションロゴ
日本語
© 2026 Meta