Tutorial: Instruksi langkah demi langkah untuk membuat aplikasi HoloLens Unity baru menggunakan Azure Spatial Anchors

Tutorial ini akan menunjukkan cara membuat aplikasi HoloLens Unity baru dengan Azure Spatial Anchors.

Prasyarat

Untuk menyelesaikan tutorial ini, pastikan Anda memiliki:

  1. PC - PC yang menjalankan Windows
  2. Visual Studio - Visual Studio 2019 diinstal dengan beban kerja pengembangan Platform Windows Universal dan komponen Windows 10 SDK (10.0.18362.0 atau yang lebih baru). C++/WinRT Visual Studio Extension (VSIX) untuk Visual Studio harus diinstal dari Visual Studio Marketplace.
  3. HoloLens - Perangkat HoloLens dengan mode pengembang diaktifkan. Artikel ini memerlukan perangkat HoloLens dengan Pembaruan Windows 10 Mei 2020. Untuk memperbarui ke rilis terbaru HoloLens, buka aplikasi Pengaturan, buka Pembaruan & Keamanan, lalu pilih tombol Periksa pembaruan.
  4. Unity - Unity 2020.3.25 dengan modul Platform Windows Universal Dukungan Build dan Dukungan Build Windows (IL2CPP)

Membuat dan menyiapkan Proyek Unity

Buat Proyek Baru

  1. Di Unity Hub, pilih Proyek baru
  2. Pilih 3D
  3. Masukkan Nama proyek Anda dan masukkan Lokasi penyimpanan
  4. Pilih Buat proyek dan tunggu Unity membuat proyek Anda

Ubah Platform Build

  1. Di editor unity Anda, pilih File>Build Pengaturan
  2. Pilih Platform Windows Universal lalu Beralih Platform. Tunggu hingga Unity selesai memproses semua file.

Mengimpor ASA dan OpenXR

  1. Luncurkan Alat Fitur Mixed Reality
  2. Pilih jalur proyek Anda - folder yang berisi folder seperti Aset, Paket, Proyek Pengaturan, dan sebagainya - dan pilih Temukan Fitur
  3. Di bawah Azure Mixed Reality Services, pilih keduanya
    1. Azure Spatial Anchors SDK Core
    2. Azure Spatial Anchors SDK untuk Windows
  4. Di bawah Dukungan Platform, pilih
    1. Mixed Reality OpenXR Plugin

Catatan

Pastikan Anda telah menyegarkan katalog dan versi terbaru dipilih untuk masing-masing katalog

MRFT - Feature Selection

  1. Tekan Dapatkan Fitur -->Impor -->Setujui -->Exit
  2. Saat memfokuskan ulang jendela Unity Anda, Unity akan mulai mengimpor modul
  3. Jika Anda mendapatkan pesan tentang menggunakan sistem input baru, pilih Ya untuk memulai ulang Unity dan mengaktifkan backend.

Menyiapkan pengaturan proyek

Kami sekarang akan menetapkan beberapa pengaturan proyek Unity yang membantu kami menargetkan SDK Windows Holographic untuk pengembangan.

Mengubah Pengaturan OpenXR

  1. Pilih File>Build Pengaturan (mungkin masih terbuka dari langkah sebelumnya)
  2. Pilih Pemutar Pengaturan...
  3. Pilih Manajemen Plug-in XR
  4. Pastikan tab Platform Windows Universal Pengaturan dipilih dan centang kotak di samping OpenXR dan di samping grup fitur Microsoft HoloLens
  5. Pilih tanda peringatan kuning di samping OpenXR untuk menampilkan semua masalah OpenXR.
  6. Pilih Perbaiki semua
  7. Untuk memperbaiki masalah "Setidaknya satu profil interaksi harus ditambahkan", pilih Edit untuk membuka pengaturan Proyek OpenXR. Kemudian di bawah Profil Interaksi pilih + simbol dan pilih Profil Interaksi Tangan MicrosoftUnity - OpenXR Setup

Ubah Pengaturan Kualitas

  1. Pilih Edit>Pengaturan Proyek>Kualitas
  2. Di kolom di bawah logo Platform Windows Universal, pilih panah di baris Default dan pilih Sangat Rendah. Anda akan tahu pengaturan diterapkan dengan benar ketika kotak di kolom Universal Windows Platform dan baris Sangat Rendah berwarna hijau.

Mengatur kemampuan

  1. Buka Edit>Project Pengaturan> Player (Anda mungkin masih membukanya dari langkah sebelumnya).
  2. Pastikan tab Platform Windows Universal Pengaturan dipilih
  3. Di bagian Konfigurasi Pengaturan Penerbitan, aktifkan yang berikut ini
    1. InternetClient
    2. InternetClientServer
    3. PrivateNetworkClientServer
    4. SpatialPerception (mungkin sudah diaktifkan)

Menyiapkan kamera utama

  1. Di Panel Hierarki, pilih Kamera Utama.
  2. Dalam Pemeriksa, atur posisi transformasinya menjadi 0,0,0.
  3. Temukan properti Bersihkan Bendera, dan ubah menu dropdown dari Skybox menjadi Warna Solid.
  4. Pilih bidang Latar Belakang untuk membuka pemilih warna.
  5. Atur R, G, B, dan A ke 0.
  6. Pilih Tambahkan Komponen di bagian bawah dan tambahkan Komponen Driver Pose Terlacak ke kamera Unity - Camera Setup

Cobalah #1

Anda sekarang harus memiliki adegan kosong yang siap untuk disebarkan ke perangkat HoloLens Anda. Untuk menguji bahwa semuanya berfungsi, buat aplikasi Anda di Unity dan terapkan dari Visual Studio. Ikuti Menggunakan Visual Studio untuk menyebarkan dan men-debug untuk melakukannya. Anda akan melihat layar mulai Unity, lalu tampilan yang jelas.

Buat sumber daya Spatial Anchors

Buka portal Microsoft Azure.

Di panel kiri, pilih Buat sumber daya.

Gunakan kotak pencarian untuk mencari Spatial Anchors.

Screenshot showing the results of a search for Spatial Anchors.

Pilih Spatial Anchors, lalu pilih Buat.

Pada panel Akun Spatial Anchors, lakukan hal berikut ini:

  • Masukkan nama sumber daya unik dengan menggunakan karakter alfanumerik biasa.

  • Pilih langganan yang ingin Anda lampirkan sumber dayanya.

  • Buat grup sumber daya dengan memilih Buat baru. Beri nama myResourceGroup, lalu pilih OK.

    Grup sumber daya Azure adalah kontainer logis tempat sumber daya Azure seperti aplikasi web, database, dan akun penyimpanan disebarkan dan dikelola. Misalnya, Anda dapat memilih untuk menghapus seluruh grup sumber daya dalam satu langkah sederhana nanti.

  • Pilih lokasi (kawasan) tempat menempatkan sumber daya.

  • Pilih Buat untuk memulai pembuatan sumber daya.

Screenshot of the Spatial Anchors pane for creating a resource.

Setelah sumber daya dibuat, portal Azure menunjukkan bahwa penyebaran Anda selesai.

Screenshot showing that the resource deployment is complete.

Pilih Buka sumber daya. Anda sekarang dapat melihat properti sumber daya.

Salin nilai ID Akun sumber daya ke editor teks untuk digunakan nanti.

Screenshot of the resource properties pane.

Salin nilai Domain Akun sumber daya juga ke editor teks untuk digunakan nanti.

Screenshot showing the resource's account domain value.

Di Pengaturan, pilih Kunci akses. Salin nilai Kunci utama, Kunci Akun, ke editor teks untuk digunakan nanti.

Screenshot of the Keys pane for the account.

Membuat & Menambahkan Skrip

  1. Di Unity di panel Proyek, buat folder baru bernama Skrip, di folder Aset.
  2. Di folder klik kanan ->Create ->C# Script. Beri judul AzureSpatialAnchorsScript
  3. Buka GameObject ->Buat Kosong.
  4. Pilih, dan di Inspektur mengganti namanya dari GameObject ke AzureSpatialAnchors.
  5. Masih di GameObject
    1. Atur posisinya ke 0,0,0
    2. Pilih Tambahkan Komponen dan cari dan tambahkan AzureSpatialAnchorsScript
    3. Pilih Tambahkan Komponen lagi dan cari dan tambahkan AR Anchor Manager. Ini akan secara otomatis menambahkan Asal Sesi AR juga.
    4. Pilih Tambahkan Komponen lagi dan cari dan tambahkan skrip SpatialAnchorManager
    5. Dalam komponen SpatialAnchorManager yang ditambahkan, isi ID Akun, Kunci Akun, dan Domain Akun yang telah Anda salin di langkah sebelumnya dari sumber daya jangkar spasial di portal Azure.

Unity - ASA GameObject

Gambaran Umum Aplikasi

Aplikasi kami akan mendukung interaksi berikut:

Gerakan Perbuatan
Ketuk di mana saja Mulai/Lanjutkan Sesi + Buat jangkar di Posisi Tangan
Mengetuk jangkar Hapus GameObject + Hapus Jangkar di ASA Cloud Service
Ketuk + Tahan selama 2 detik (+ sesi sedang berjalan) Hentikan sesi dan hapus semua GameObjects. Simpan jangkar di ASA Cloud Service
Ketuk + Tahan selama 2 detik (+ sesi tidak berjalan) Mulai sesi dan cari semua jangkar.

Tambahkan Pengenalan Ketuk

Mari kita tambahkan beberapa kode ke skrip kita untuk dapat mengenali gerakan mengetuk pengguna.

  1. Buka AzureSpatialAnchorsScript.cs di Visual Studio dengan mengklik dua kali pada skrip di panel Proyek Unity Anda.
  2. Tambahkan array berikut ke kelas Anda
public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };
  1. Tambahkan dua metode berikut di bawah metode Update(). Kami akan menambahkan implementasi pada tahap selanjutnya
// Update is called once per frame
void Update()
{
}

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
}

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
}
  1. Tambahkan impor berikut
using UnityEngine.XR;
  1. Tambahkan kode berikut di atas Update() metode . Ini akan memungkinkan aplikasi mengenali gerakan mengetuk tangan pendek dan panjang (2 detik)
// Update is called once per frame
void Update()
{

    //Check for any air taps from either hand
    for (int i = 0; i < 2; i++)
    {
        InputDevice device = InputDevices.GetDeviceAtXRNode((i == 0) ? XRNode.RightHand : XRNode.LeftHand);
        if (device.TryGetFeatureValue(CommonUsages.primaryButton, out bool isTapping))
        {
            if (!isTapping)
            {
                //Stopped Tapping or wasn't tapping
                if (0f < _tappingTimer[i] && _tappingTimer[i] < 1f)
                {
                    //User has been tapping for less than 1 sec. Get hand position and call ShortTap
                    if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                    {
                        ShortTap(handPosition);
                    }
                }
                _tappingTimer[i] = 0;
            }
            else
            {
                _tappingTimer[i] += Time.deltaTime;
                if (_tappingTimer[i] >= 2f)
                {
                    //User has been air tapping for at least 2sec. Get hand position and call LongTap
                    if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                    {
                        LongTap();
                    }
                    _tappingTimer[i] = -float.MaxValue; // reset the timer, to avoid retriggering if user is still holding tap
                }
            }
        }

    }
}

Menambahkan & Mengonfigurasi SpatialAnchorManager

ASA SDK menawarkan antarmuka sederhana yang disebut SpatialAnchorManager untuk melakukan panggilan ke layanan ASA. Mari kita tambahkan sebagai variabel ke AzureSpatialAnchorsScript.cs

Pertama tambahkan impor

using Microsoft.Azure.SpatialAnchors.Unity;

Kemudian deklarasikan variabel

public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };

    /// <summary>
    /// Main interface to anything Spatial Anchors related
    /// </summary>
    private SpatialAnchorManager _spatialAnchorManager = null;

Start() Dalam metode , tetapkan variabel ke komponen yang kami tambahkan di langkah sebelumnya

// Start is called before the first frame update
void Start()
{
    _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
}

Untuk menerima debug dan log kesalahan, kita perlu berlangganan panggilan balik yang berbeda

// Start is called before the first frame update
void Start()
{
    _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
    _spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
    _spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
}

Catatan

Untuk melihat log, pastikan setelah Anda membuat proyek dari Unity dan Anda membuka solusi .slnvisual studio, pilih Debug --> Jalankan dengan Debugging dan biarkan HoloLens anda tersambung ke komputer saat aplikasi berjalan.

Mulai Sesi

Untuk membuat dan menemukan jangkar, pertama-tama kita harus memulai sesi. Saat memanggil StartSessionAsync(), SpatialAnchorManager akan membuat sesi jika perlu lalu memulainya. Mari kita tambahkan ini ke metode kita ShortTap() .

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
    await _spatialAnchorManager.StartSessionAsync();
}

Buat Anchor

Sekarang setelah kita memiliki sesi yang berjalan, kita dapat membuat jangkar. Dalam aplikasi ini, kami ingin melacak jangkar GameObjects yang dibuat dan pengidentifikasi jangkar yang dibuat (ID jangkar). Mari kita tambahkan dua daftar ke kode kita.

using Microsoft.Azure.SpatialAnchors.Unity;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
    /// <summary>
    /// Main interface to anything Spatial Anchors related
    /// </summary>
    private SpatialAnchorManager _spatialAnchorManager = null;

    /// <summary>
    /// Used to keep track of all GameObjects that represent a found or created anchor
    /// </summary>
    private List<GameObject> _foundOrCreatedAnchorGameObjects = new List<GameObject>();

    /// <summary>
    /// Used to keep track of all the created Anchor IDs
    /// </summary>
    private List<String> _createdAnchorIDs = new List<String>();

Mari kita buat metode CreateAnchor yang membuat jangkar pada posisi yang ditentukan oleh parameternya.

using System.Threading.Tasks;
/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
}

Karena jangkar spasial tidak hanya memiliki posisi tetapi juga rotasi, mari kita atur rotasi untuk selalu berorientasi pada HoloLens pada pembuatan.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

}

Sekarang setelah kita memiliki posisi dan rotasi jangkar yang diinginkan, mari kita buat yang terlihat GameObject. Perhatikan bahwa Spatial Anchors tidak mengharuskan jangkar GameObject terlihat oleh pengguna akhir karena tujuan utama Spatial Anchors adalah untuk menyediakan bingkai referensi umum dan persisten. Untuk tujuan tutorial ini, kami akan memvisualisasikan jangkar sebagai kubus. Setiap jangkar akan diinisialisasi sebagai kubus putih , yang akan berubah menjadi kubus hijau setelah proses pembuatan berhasil.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

}

Catatan

Kami menggunakan shader warisan, karena disertakan dalam build Unity default. Shader lain seperti shader default hanya disertakan jika ditentukan secara manual atau secara langsung merupakan bagian dari adegan. Jika shader tidak disertakan dan aplikasi mencoba merendernya, itu akan menghasilkan bahan merah muda.

Sekarang mari kita tambahkan dan konfigurasikan komponen Spatial Anchor. Kami mengatur kedaluwarsa jangkar menjadi 3 hari dari pembuatan jangkar. Setelah itu mereka akan secara otomatis dihapus dari cloud. Ingatlah untuk menambahkan impor

using Microsoft.Azure.SpatialAnchors;
/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

    //Add and configure ASA components
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
    await cloudNativeAnchor.NativeToCloud();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
    cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

}

Untuk menyimpan jangkar, pengguna harus mengumpulkan data lingkungan.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

    //Add and configure ASA components
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
    await cloudNativeAnchor.NativeToCloud();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
    cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

    //Collect Environment Data
    while (!_spatialAnchorManager.IsReadyForCreate)
    {
        float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
        Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
    }

}

Catatan

HoloLens mungkin dapat menggunakan kembali data lingkungan yang sudah diambil di sekitar jangkar, yang mengakibatkan benar sudah ketika dipanggil IsReadyForCreate untuk pertama kalinya.

Sekarang setelah jangkar spasial cloud telah disiapkan, kita dapat mencoba penyimpanan aktual di sini.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

    //Add and configure ASA components
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
    await cloudNativeAnchor.NativeToCloud();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
    cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

    //Collect Environment Data
    while (!_spatialAnchorManager.IsReadyForCreate)
    {
        float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
        Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
    }

    Debug.Log($"ASA - Saving cloud anchor... ");

    try
    {
        // Now that the cloud spatial anchor has been prepared, we can try the actual save here.
        await _spatialAnchorManager.CreateAnchorAsync(cloudSpatialAnchor);

        bool saveSucceeded = cloudSpatialAnchor != null;
        if (!saveSucceeded)
        {
            Debug.LogError("ASA - Failed to save, but no exception was thrown.");
            return;
        }

        Debug.Log($"ASA - Saved cloud anchor with ID: {cloudSpatialAnchor.Identifier}");
        _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
        _createdAnchorIDs.Add(cloudSpatialAnchor.Identifier);
        anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.green;
    }
    catch (Exception exception)
    {
        Debug.Log("ASA - Failed to save anchor: " + exception.ToString());
        Debug.LogException(exception);
    }
}

Terakhir mari kita tambahkan panggilan fungsi ke metode kita ShortTap

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
    await _spatialAnchorManager.StartSessionAsync();
        await CreateAnchor(handPosition);
}

Aplikasi kami sekarang dapat membuat beberapa jangkar. Perangkat apa pun sekarang dapat menemukan jangkar yang dibuat (jika belum kedaluwarsa) selama mereka mengetahui ID jangkar dan memiliki akses ke Sumber Daya Spatial Anchors yang sama di Azure.

Hentikan Sesi & Hancurkan GameObjects

Untuk meniru perangkat kedua yang menemukan semua jangkar, kami sekarang akan menghentikan sesi dan menghapus semua jangkar GameObjects (kami akan menyimpan ID jangkar). Setelah itu kita akan memulai sesi baru dan mengkueri jangkar menggunakan ID jangkar yang disimpan.

SpatialAnchorManager dapat mengurus sesi berhenti hanya dengan memanggil metodenya DestroySession() . Mari kita tambahkan ini ke metode kita LongTap()

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
        _spatialAnchorManager.DestroySession();
}

Mari kita buat metode untuk menghapus semua jangkar GameObjects

/// <summary>
/// Destroys all Anchor GameObjects
/// </summary>
private void RemoveAllAnchorGameObjects()
{
    foreach (var anchorGameObject in _foundOrCreatedAnchorGameObjects)
    {
        Destroy(anchorGameObject);
    }
    _foundOrCreatedAnchorGameObjects.Clear();
}

Dan sebut saja setelah menghancurkan sesi di LongTap()

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
        // Stop Session and remove all GameObjects. This does not delete the Anchors in the cloud
        _spatialAnchorManager.DestroySession();
        RemoveAllAnchorGameObjects();
        Debug.Log("ASA - Stopped Session and removed all Anchor Objects");
}

Temukan Jangkar

Kami sekarang akan mencoba menemukan jangkar lagi dengan posisi dan rotasi yang benar yang kami buat. Untuk melakukannya, kita perlu memulai sesi dan membuat Watcher yang akan mencari jangkar yang sesuai dengan kriteria yang diberikan. Sebagai kriteria, kita akan memberinya UMPAN ID jangkar yang sebelumnya kita buat. Mari kita buat metode LocateAnchor() dan gunakan SpatialAnchorManager untuk membuat Watcher. Untuk menemukan strategi selain menggunakan ID jangkar, lihat Strategi menemukan Jangkar

/// <summary>
/// Looking for anchors with ID in _createdAnchorIDs
/// </summary>
private void LocateAnchor()
{
    if (_createdAnchorIDs.Count > 0)
    {
        //Create watcher to look for all stored anchor IDs
        Debug.Log($"ASA - Creating watcher to look for {_createdAnchorIDs.Count} spatial anchors");
        AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria();
        anchorLocateCriteria.Identifiers = _createdAnchorIDs.ToArray();
        _spatialAnchorManager.Session.CreateWatcher(anchorLocateCriteria);
        Debug.Log($"ASA - Watcher created!");
    }
}

Setelah pengamat dimulai, pengamat akan mengaktifkan panggilan balik ketika menemukan jangkar yang sesuai dengan kriteria yang diberikan. Mari kita pertama-tama buat metode jangkar kita yang terletak disebut SpatialAnchorManager_AnchorLocated() bahwa kita akan mengonfigurasi untuk dipanggil ketika pengamat telah menemukan jangkar. Metode ini akan membuat visual GameObject dan melampirkan komponen jangkar asli ke dalamnya. Komponen jangkar asli akan memastikan posisi dan rotasi yang GameObject benar diatur.

Mirip dengan proses pembuatan, jangkar dilampirkan ke GameObject. GameObject ini tidak harus terlihat di adegan Anda agar jangkar spasial berfungsi. Untuk tujuan tutorial ini, kami akan memvisualisasikan setiap jangkar sebagai kubus biru setelah mereka ditemukan. Jika Anda hanya menggunakan jangkar untuk membuat sistem koordinat bersama, Anda tidak perlu memvisualisasikan GameObject yang dibuat.

/// <summary>
/// Callback when an anchor is located
/// </summary>
/// <param name="sender">Callback sender</param>
/// <param name="args">Callback AnchorLocatedEventArgs</param>
private void SpatialAnchorManager_AnchorLocated(object sender, AnchorLocatedEventArgs args)
{
    Debug.Log($"ASA - Anchor recognized as a possible anchor {args.Identifier} {args.Status}");

    if (args.Status == LocateAnchorStatus.Located)
    {
        //Creating and adjusting GameObjects have to run on the main thread. We are using the UnityDispatcher to make sure this happens.
        UnityDispatcher.InvokeOnAppThread(() =>
        {
            // Read out Cloud Anchor values
            CloudSpatialAnchor cloudSpatialAnchor = args.Anchor;

            //Create GameObject
            GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
            anchorGameObject.transform.localScale = Vector3.one * 0.1f;
            anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
            anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.blue;

            // Link to Cloud Anchor
            anchorGameObject.AddComponent<CloudNativeAnchor>().CloudToNative(cloudSpatialAnchor);
            _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
        });
    }
}

Sekarang mari kita berlangganan panggilan balik AnchorLocated dari SpatialAnchorManager untuk memastikan metode kami SpatialAnchorManager_AnchorLocated() dipanggil setelah pengamat menemukan jangkar.

// Start is called before the first frame update
void Start()
{
    _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
    _spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
    _spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
    _spatialAnchorManager.AnchorLocated += SpatialAnchorManager_AnchorLocated;
}

Terakhir, mari kita perluas metode kita LongTap() untuk menyertakan menemukan jangkar. Kita akan menggunakan IsSessionStarted boolean untuk memutuskan apakah kita mencari semua jangkar atau menghancurkan semua jangkar seperti yang dijelaskan dalam Gambaran Umum Aplikasi

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
    if (_spatialAnchorManager.IsSessionStarted)
    {
        // Stop Session and remove all GameObjects. This does not delete the Anchors in the cloud
        _spatialAnchorManager.DestroySession();
        RemoveAllAnchorGameObjects();
        Debug.Log("ASA - Stopped Session and removed all Anchor Objects");
    }
    else
    {
        //Start session and search for all Anchors previously created
        await _spatialAnchorManager.StartSessionAsync();
        LocateAnchor();
    }
}

Cobalah #2

Aplikasi Anda sekarang mendukung pembuatan jangkar dan menemukannya. Buat aplikasi Anda di Unity dan sebarkan dari Visual Studio dengan mengikuti Menggunakan Visual Studio untuk menyebarkan dan men-debug.

Pastikan HoloLens Anda terhubung ke internet. Setelah aplikasi dimulai dan pesan yang dibuat dengan Unity menghilang, ketukan singkat di sekitar Anda. Kubus putih akan muncul untuk menunjukkan posisi dan rotasi jangkar yang akan dibuat. Proses pembuatan jangkar secara otomatis dipanggil. Saat Anda perlahan-lahan melihat sekitar Anda, Anda menangkap data lingkungan. Setelah data lingkungan yang cukup dikumpulkan, aplikasi kami akan mencoba membuat jangkar di lokasi yang ditentukan. Setelah proses pembuatan jangkar selesai, kubus akan berubah menjadi hijau. Periksa log debug Anda di visual studio untuk melihat apakah semuanya berfungsi seperti yang dimaksudkan.

Ketukan panjang untuk menghapus semua GameObjects dari adegan Anda dan menghentikan sesi jangkar spasial.

Setelah adegan dibersihkan, Anda dapat mengetuk lagi, yang akan memulai sesi dan mencari jangkar yang telah Anda buat sebelumnya. Setelah ditemukan, mereka divisualisasikan oleh kubus biru pada posisi dan rotasi yang berlabuh. Jangkar ini (selama tidak kedaluwarsa) dapat ditemukan oleh perangkat yang didukung selama mereka memiliki ID jangkar yang benar dan memiliki akses ke sumber daya jangkar spasial Anda.

Hapus Anchor

Saat ini aplikasi kami dapat membuat dan menemukan jangkar. Meskipun menghapus GameObjects, itu tidak menghapus jangkar di cloud. Mari kita tambahkan fungsionalitas untuk juga menghapusnya di cloud jika Anda mengetuk jangkar yang ada.

Mari kita tambahkan metode DeleteAnchor yang menerima GameObject. Kami kemudian akan menggunakan SpatialAnchorManager bersama-sama dengan komponen objek CloudNativeAnchor untuk meminta penghapusan jangkar di cloud.

/// <summary>
/// Deleting Cloud Anchor attached to the given GameObject and deleting the GameObject
/// </summary>
/// <param name="anchorGameObject">Anchor GameObject that is to be deleted</param>
private async void DeleteAnchor(GameObject anchorGameObject)
{
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.GetComponent<CloudNativeAnchor>();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;

    Debug.Log($"ASA - Deleting cloud anchor: {cloudSpatialAnchor.Identifier}");

    //Request Deletion of Cloud Anchor
    await _spatialAnchorManager.DeleteAnchorAsync(cloudSpatialAnchor);

    //Remove local references
    _createdAnchorIDs.Remove(cloudSpatialAnchor.Identifier);
    _foundOrCreatedAnchorGameObjects.Remove(anchorGameObject);
    Destroy(anchorGameObject);

    Debug.Log($"ASA - Cloud anchor deleted!");
}

Untuk memanggil metode ini dari ShortTap, kita harus dapat menentukan apakah ketukan telah mendekati jangkar yang terlihat yang ada. Mari kita buat metode pembantu yang mengurusnya

using System.Linq;
/// <summary>
/// Returns true if an Anchor GameObject is within 15cm of the received reference position
/// </summary>
/// <param name="position">Reference position</param>
/// <param name="anchorGameObject">Anchor GameObject within 15cm of received position. Not necessarily the nearest to this position. If no AnchorObject is within 15cm, this value will be null</param>
/// <returns>True if a Anchor GameObject is within 15cm</returns>
private bool IsAnchorNearby(Vector3 position, out GameObject anchorGameObject)
{
    anchorGameObject = null;

    if (_foundOrCreatedAnchorGameObjects.Count <= 0)
    {
        return false;
    }

    //Iterate over existing anchor gameobjects to find the nearest
    var (distance, closestObject) = _foundOrCreatedAnchorGameObjects.Aggregate(
        new Tuple<float, GameObject>(Mathf.Infinity, null),
        (minPair, gameobject) =>
        {
            Vector3 gameObjectPosition = gameobject.transform.position;
            float distance = (position - gameObjectPosition).magnitude;
            return distance < minPair.Item1 ? new Tuple<float, GameObject>(distance, gameobject) : minPair;
        });

    if (distance <= 0.15f)
    {
        //Found an anchor within 15cm
        anchorGameObject = closestObject;
        return true;
    }
    else
    {
        return false;
    }
}

Kita sekarang dapat memperluas metode kita ShortTap untuk menyertakan DeleteAnchor panggilan

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
    await _spatialAnchorManager.StartSessionAsync();
    if (!IsAnchorNearby(handPosition, out GameObject anchorGameObject))
    {
        //No Anchor Nearby, start session and create an anchor
        await CreateAnchor(handPosition);
    }
    else
    {
        //Delete nearby Anchor
        DeleteAnchor(anchorGameObject);
    }
}

Coba #3

Buat aplikasi Anda di Unity dan sebarkan dari Visual Studio dengan mengikuti Menggunakan Visual Studio untuk menyebarkan dan men-debug.

Perhatikan bahwa lokasi gerakan mengetuk tangan Anda adalah bagian tengah tangan Anda di aplikasi ini dan bukan ujung jari Anda.

Saat Anda mengetuk jangkar, baik dibuat (hijau) atau terletak (biru) permintaan dikirim ke layanan jangkar spasial untuk menghapus jangkar ini dari akun. Hentikan sesi (ketukan panjang) dan mulai sesi lagi (ketuk panjang) untuk mencari semua jangkar. Jangkar yang dihapus tidak akan lagi ditemukan.

Menggabungkan semuanya

Berikut adalah tampak file kelas AzureSpatialAnchorsScript lengkap yang seharusnya, setelah semua elemen yang berbeda telah digabung. Anda dapat menggunakannya sebagai referensi untuk dibandingkan dengan file Anda sendiri, dan melihat jika Anda mungkin memiliki perbedaan yang tersisa.

Catatan

Anda akan melihat bahwa kami telah menyertakan [RequireComponent(typeof(SpatialAnchorManager))] ke skrip. Dengan ini, Unity akan memastikan bahwa GameObject tempat kita melampirkan AzureSpatialAnchorsScript , juga memiliki yang SpatialAnchorManager melekat padanya.

using Microsoft.Azure.SpatialAnchors;
using Microsoft.Azure.SpatialAnchors.Unity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR;


[RequireComponent(typeof(SpatialAnchorManager))]
public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };

    /// <summary>
    /// Main interface to anything Spatial Anchors related
    /// </summary>
    private SpatialAnchorManager _spatialAnchorManager = null;

    /// <summary>
    /// Used to keep track of all GameObjects that represent a found or created anchor
    /// </summary>
    private List<GameObject> _foundOrCreatedAnchorGameObjects = new List<GameObject>();

    /// <summary>
    /// Used to keep track of all the created Anchor IDs
    /// </summary>
    private List<String> _createdAnchorIDs = new List<String>();

    // <Start>
    // Start is called before the first frame update
    void Start()
    {
        _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
        _spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
        _spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
        _spatialAnchorManager.AnchorLocated += SpatialAnchorManager_AnchorLocated;
    }
    // </Start>

    // <Update>
    // Update is called once per frame
    void Update()
    {

        //Check for any air taps from either hand
        for (int i = 0; i < 2; i++)
        {
            InputDevice device = InputDevices.GetDeviceAtXRNode((i == 0) ? XRNode.RightHand : XRNode.LeftHand);
            if (device.TryGetFeatureValue(CommonUsages.primaryButton, out bool isTapping))
            {
                if (!isTapping)
                {
                    //Stopped Tapping or wasn't tapping
                    if (0f < _tappingTimer[i] && _tappingTimer[i] < 1f)
                    {
                        //User has been tapping for less than 1 sec. Get hand position and call ShortTap
                        if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                        {
                            ShortTap(handPosition);
                        }
                    }
                    _tappingTimer[i] = 0;
                }
                else
                {
                    _tappingTimer[i] += Time.deltaTime;
                    if (_tappingTimer[i] >= 2f)
                    {
                        //User has been air tapping for at least 2sec. Get hand position and call LongTap
                        if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                        {
                            LongTap();
                        }
                        _tappingTimer[i] = -float.MaxValue; // reset the timer, to avoid retriggering if user is still holding tap
                    }
                }
            }

        }
    }
    // </Update>


    // <ShortTap>
    /// <summary>
    /// Called when a user is air tapping for a short time 
    /// </summary>
    /// <param name="handPosition">Location where tap was registered</param>
    private async void ShortTap(Vector3 handPosition)
    {
        await _spatialAnchorManager.StartSessionAsync();
        if (!IsAnchorNearby(handPosition, out GameObject anchorGameObject))
        {
            //No Anchor Nearby, start session and create an anchor
            await CreateAnchor(handPosition);
        }
        else
        {
            //Delete nearby Anchor
            DeleteAnchor(anchorGameObject);
        }
    }
    // </ShortTap>

    // <LongTap>
    /// <summary>
    /// Called when a user is air tapping for a long time (>=2 sec)
    /// </summary>
    private async void LongTap()
    {
        if (_spatialAnchorManager.IsSessionStarted)
        {
            // Stop Session and remove all GameObjects. This does not delete the Anchors in the cloud
            _spatialAnchorManager.DestroySession();
            RemoveAllAnchorGameObjects();
            Debug.Log("ASA - Stopped Session and removed all Anchor Objects");
        }
        else
        {
            //Start session and search for all Anchors previously created
            await _spatialAnchorManager.StartSessionAsync();
            LocateAnchor();
        }
    }
    // </LongTap>

    // <RemoveAllAnchorGameObjects>
    /// <summary>
    /// Destroys all Anchor GameObjects
    /// </summary>
    private void RemoveAllAnchorGameObjects()
    {
        foreach (var anchorGameObject in _foundOrCreatedAnchorGameObjects)
        {
            Destroy(anchorGameObject);
        }
        _foundOrCreatedAnchorGameObjects.Clear();
    }
    // </RemoveAllAnchorGameObjects>

    // <IsAnchorNearby>
    /// <summary>
    /// Returns true if an Anchor GameObject is within 15cm of the received reference position
    /// </summary>
    /// <param name="position">Reference position</param>
    /// <param name="anchorGameObject">Anchor GameObject within 15cm of received position. Not necessarily the nearest to this position. If no AnchorObject is within 15cm, this value will be null</param>
    /// <returns>True if a Anchor GameObject is within 15cm</returns>
    private bool IsAnchorNearby(Vector3 position, out GameObject anchorGameObject)
    {
        anchorGameObject = null;

        if (_foundOrCreatedAnchorGameObjects.Count <= 0)
        {
            return false;
        }

        //Iterate over existing anchor gameobjects to find the nearest
        var (distance, closestObject) = _foundOrCreatedAnchorGameObjects.Aggregate(
            new Tuple<float, GameObject>(Mathf.Infinity, null),
            (minPair, gameobject) =>
            {
                Vector3 gameObjectPosition = gameobject.transform.position;
                float distance = (position - gameObjectPosition).magnitude;
                return distance < minPair.Item1 ? new Tuple<float, GameObject>(distance, gameobject) : minPair;
            });

        if (distance <= 0.15f)
        {
            //Found an anchor within 15cm
            anchorGameObject = closestObject;
            return true;
        }
        else
        {
            return false;
        }
    }
    // </IsAnchorNearby>
  
    // <CreateAnchor>
    /// <summary>
    /// Creates an Azure Spatial Anchor at the given position rotated towards the user
    /// </summary>
    /// <param name="position">Position where Azure Spatial Anchor will be created</param>
    /// <returns>Async Task</returns>
    private async Task CreateAnchor(Vector3 position)
    {
        //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
        if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
        {
            headPosition = Vector3.zero;
        }

        Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

        GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
        anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
        anchorGameObject.transform.position = position;
        anchorGameObject.transform.rotation = orientationTowardsHead;
        anchorGameObject.transform.localScale = Vector3.one * 0.1f;

        //Add and configure ASA components
        CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
        await cloudNativeAnchor.NativeToCloud();
        CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
        cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

        //Collect Environment Data
        while (!_spatialAnchorManager.IsReadyForCreate)
        {
            float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
            Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
        }

        Debug.Log($"ASA - Saving cloud anchor... ");

        try
        {
            // Now that the cloud spatial anchor has been prepared, we can try the actual save here.
            await _spatialAnchorManager.CreateAnchorAsync(cloudSpatialAnchor);

            bool saveSucceeded = cloudSpatialAnchor != null;
            if (!saveSucceeded)
            {
                Debug.LogError("ASA - Failed to save, but no exception was thrown.");
                return;
            }

            Debug.Log($"ASA - Saved cloud anchor with ID: {cloudSpatialAnchor.Identifier}");
            _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
            _createdAnchorIDs.Add(cloudSpatialAnchor.Identifier);
            anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.green;
        }
        catch (Exception exception)
        {
            Debug.Log("ASA - Failed to save anchor: " + exception.ToString());
            Debug.LogException(exception);
        }
    }
    // </CreateAnchor>

    // <LocateAnchor>
    /// <summary>
    /// Looking for anchors with ID in _createdAnchorIDs
    /// </summary>
    private void LocateAnchor()
    {
        if (_createdAnchorIDs.Count > 0)
        {
            //Create watcher to look for all stored anchor IDs
            Debug.Log($"ASA - Creating watcher to look for {_createdAnchorIDs.Count} spatial anchors");
            AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria();
            anchorLocateCriteria.Identifiers = _createdAnchorIDs.ToArray();
            _spatialAnchorManager.Session.CreateWatcher(anchorLocateCriteria);
            Debug.Log($"ASA - Watcher created!");
        }
    }
    // </LocateAnchor>

    // <SpatialAnchorManagerAnchorLocated>
    /// <summary>
    /// Callback when an anchor is located
    /// </summary>
    /// <param name="sender">Callback sender</param>
    /// <param name="args">Callback AnchorLocatedEventArgs</param>
    private void SpatialAnchorManager_AnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        Debug.Log($"ASA - Anchor recognized as a possible anchor {args.Identifier} {args.Status}");

        if (args.Status == LocateAnchorStatus.Located)
        {
            //Creating and adjusting GameObjects have to run on the main thread. We are using the UnityDispatcher to make sure this happens.
            UnityDispatcher.InvokeOnAppThread(() =>
            {
                // Read out Cloud Anchor values
                CloudSpatialAnchor cloudSpatialAnchor = args.Anchor;

                //Create GameObject
                GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
                anchorGameObject.transform.localScale = Vector3.one * 0.1f;
                anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
                anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.blue;

                // Link to Cloud Anchor
                anchorGameObject.AddComponent<CloudNativeAnchor>().CloudToNative(cloudSpatialAnchor);
                _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
            });
        }
    }
    // </SpatialAnchorManagerAnchorLocated>

    // <DeleteAnchor>
    /// <summary>
    /// Deleting Cloud Anchor attached to the given GameObject and deleting the GameObject
    /// </summary>
    /// <param name="anchorGameObject">Anchor GameObject that is to be deleted</param>
    private async void DeleteAnchor(GameObject anchorGameObject)
    {
        CloudNativeAnchor cloudNativeAnchor = anchorGameObject.GetComponent<CloudNativeAnchor>();
        CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;

        Debug.Log($"ASA - Deleting cloud anchor: {cloudSpatialAnchor.Identifier}");

        //Request Deletion of Cloud Anchor
        await _spatialAnchorManager.DeleteAnchorAsync(cloudSpatialAnchor);

        //Remove local references
        _createdAnchorIDs.Remove(cloudSpatialAnchor.Identifier);
        _foundOrCreatedAnchorGameObjects.Remove(anchorGameObject);
        Destroy(anchorGameObject);

        Debug.Log($"ASA - Cloud anchor deleted!");
    }
    // </DeleteAnchor>

}

Langkah berikutnya

Dalam tutorial ini, Anda mempelajari cara menerapkan aplikasi Spatial Anchors dasar untuk HoloLens menggunakan Unity. Untuk mempelajari selengkapnya tentang cara menggunakan Azure Spatial Anchors di aplikasi Android baru, lanjutkan ke tutorial berikutnya.