Aracılığıyla paylaş


Öğretici: Azure Spatial Anchors kullanarak yeni bir HoloLens Unity uygulaması oluşturmaya yönelik adım adım yönergeler

Bu öğreticide, Azure Spatial Anchors ile yeni bir HoloLens Unity uygulamasının nasıl oluşturulacağı gösterilir.

Önkoşullar

Bu öğreticiyi tamamlamak için şunlar sahip olduğunuzdan emin olun:

  1. Bilgisayar - Windows çalıştıran bir bilgisayar
  2. Visual Studio - Visual Studio 2019, Evrensel Windows Platformu geliştirme iş yükü ve Windows 10 SDK (10.0.18362.0 veya üzeri) bileşeniyle birlikte yüklenir. Visual Studio için C++/WinRT Visual Studio Uzantısı (VSIX), Visual Studio Market'ten yüklenmelidir.
  3. HoloLens - Geliştirici modu etkinleştirilmiş bir HoloLens cihazı. Bu makale, Windows 10 Mayıs 2020 Güncelleştirmesi'ne sahip bir HoloLens cihazı gerektirir. HoloLens'in en son sürümüne güncelleştirmek için Ayarlar uygulamasını açın, Güncelleştir ve Güvenlik'e gidin ve Güncelleştirmeleri denetle düğmesini seçin.
  4. Derleme Desteği ve Windows Derleme Desteği (IL2CPP) Evrensel Windows Platformu modülleriyle Unity - Unity 2020.3.25

Unity Projesi oluşturma ve ayarlama

Yeni Proje Oluştur yazıp seçin

  1. Unity Hub'da Yeni proje'yi seçin
  2. 3B'yi seçin
  3. Proje adınızı girin ve bir kaydetme Konumu girin
  4. Proje oluştur'u seçin ve Unity'nin projenizi oluşturmasını bekleyin

Derleme Platformlarını Değiştirme

  1. Unity düzenleyicinizde Dosya>Derleme Ayarları'nı seçin
  2. Evrensel Windows Platformu'ı ve ardından Platform Değiştir'i seçin. Unity'nin tüm dosyaları işlemeyi bitirmesini bekleyin.

ASA ve OpenXR'ı içeri aktarma

  1. Karma Gerçeklik Özellik Aracı'nı başlatma
  2. Varlıklar, Paketler, ProjectSettings vb. klasörleri içeren klasör olan proje yolunuzu seçin ve Özellikleri Keşfet'i seçin
  3. Azure Karma Gerçeklik Hizmetleri'nin altında her ikisini de seçin
    1. Azure Spatial Anchors SDK Core
    2. Windows için Azure Spatial Anchors SDK'sı
  4. Platform Desteği'nin altında
    1. OpenXR Eklenti Karma Gerçeklik

Not

Kataloğu yenilediğinizden ve her biri için en yeni sürümün seçildiğinden emin olun

MRFT - Özellik Seçimi

  1. Get Features --Import -->>Approve -->Exit tuşlarına basın
  2. Unity pencerenizi yeniden odaklarken Unity modülleri içeri aktarmaya başlar
  3. Yeni giriş sistemini kullanma hakkında bir ileti alırsanız Unity'yi yeniden başlatmak ve arka uçları etkinleştirmek için Evet'i seçin.

Proje ayarlarını ayarlama

Şimdi geliştirme için Windows Holographic SDK'sını hedeflememize yardımcı olacak bazı Unity proje ayarları ayarlayacağız.

OpenXR Ayarlarını Değiştirme

  1. Dosya>Derleme Ayarları'nı seçin (önceki adımda açık olabilir)
  2. Player Ayarları'nı seçin ...
  3. XR Eklenti Yönetimi'ne tıklayın
  4. Evrensel Windows Platformu Ayarlar sekmesinin seçili olduğundan emin olun ve OpenXR'nin yanındaki ve Microsoft HoloLens özellik grubunun yanındaki kutuyu işaretleyin
  5. Tüm OpenXR sorunlarını görüntülemek için OpenXR'nin yanındaki sarı uyarı işaretini seçin.
  6. Tümünü düzelt'i seçin
  7. "En az bir etkileşim profili eklenmelidir" sorununu düzeltmek için Düzenle'yi seçerek OpenXR Projesi ayarlarını açın. Ardından Etkileşim Profilleri'nin altında simgeyi + seçin ve Microsoft El Etkileşimi Profili'ni seçin Unity - OpenXR Kurulumu

Kalite Ayarlarını Değiştir

  1. Proje Ayarları>Kalitesini Düzenle'yi seçin >
  2. Evrensel Windows Platformu logosunun altındaki sütunda Varsayılan satırdaki oku seçin ve Çok Düşük'e tıklayın. Evrensel Windows Platformu sütunundaki kutu yeşil ve Çok Düşük satırında olduğunda ayarın doğru uygulandığını bilirsiniz.

Özellikleri ayarlama

  1. Proje Ayarlarını>Düzenle>Yürütücüsü'ne gidin (önceki adımda açık olabilir).
  2. Evrensel Windows Platformu Ayarları sekmesinin seçili olduğundan emin olun
  3. Yayımlama Ayarları Yapılandırması bölümünde aşağıdakileri etkinleştirin
    1. InternetClient
    2. InternetClientServer
    3. PrivateNetworkClientServer
    4. SpatialPerception (zaten etkinleştirilmiş olabilir)

Ana kamerayı ayarlama

  1. Hiyerarşi Paneli'nde Ana Kamera'yı seçin.
  2. Denetçide dönüşüm konumunu 0,0,0 olarak ayarlayın.
  3. Bayrakları Temizle özelliğini bulun ve skybox olan açılan listeyi Düz Renk olarak değiştirin.
  4. Renk seçiciyi açmak için Arka plan alanını seçin.
  5. R, G, B ve A'yı 0 olarak ayarlayın.
  6. En alttaki Bileşen Ekle'yi seçin ve İzlenen Poz Sürücüsü Bileşenini kameraya ekleyin Unity - Kamera Kurulumu

Deneyin #1

Artık HoloLens cihazınıza dağıtılmaya hazır boş bir sahneniz olmalıdır. Her şeyin çalıştığını test etmek için uygulamanızı Unity'de derleyin ve Visual Studio'dan dağıtın. Dağıtmak ve hata ayıklamak için Visual Studio kullanma'yı izleyin. Unity başlangıç ekranını ve ardından net bir ekran görmeniz gerekir.

Spatial Anchors kaynağı oluşturma

Azure portalına gidin.

Sol bölmede Kaynak oluştur'u seçin.

Spatial Anchors'ı aramak için arama kutusunu kullanın.

Spatial Anchors aramasının sonuçlarını gösteren ekran görüntüsü.

Uzamsal Tutturucular'ı ve ardından Oluştur'u seçin.

Spatial Anchors Hesabı bölmesinde aşağıdakileri yapın:

  • Normal alfasayısal karakterleri kullanarak benzersiz bir kaynak adı girin.

  • Kaynağı eklemek istediğiniz aboneliği seçin.

  • Yeni oluştur'u seçerek bir kaynak grubu oluşturun. MyResourceGroup olarak adlandırın ve tamam'ı seçin.

    Kaynak grubu, web uygulamaları, veritabanları ve depolama hesapları gibi Azure kaynaklarının dağıtıldığı ve yönetildiği mantıksal bir kapsayıcıdır. Örneğin, daha sonra tek bir basit adımda kaynak grubun tamamını silmeyi seçebilirsiniz.

  • Kaynağın yerleştirildiği konumu (bölgeyi) seçin.

  • Kaynağı oluşturmaya başlamak için Oluştur'u seçin.

Kaynak oluşturmaya yönelik Uzamsal Tutturucular bölmesinin ekran görüntüsü.

Kaynak oluşturulduktan sonra Azure portalı dağıtımınızın tamamlandığını gösterir.

Kaynak dağıtımının tamamlandığını gösteren ekran görüntüsü.

Kaynağa git’i seçin. Artık kaynak özelliklerini görüntüleyebilirsiniz.

Kaynağın Hesap Kimliği değerini daha sonra kullanmak üzere bir metin düzenleyicisine kopyalayın.

Kaynak özellikleri bölmesinin ekran görüntüsü.

Ayrıca kaynağın Hesap Etki Alanı değerini daha sonra kullanmak üzere bir metin düzenleyicisine kopyalayın.

Kaynağın hesap etki alanı değerini gösteren ekran görüntüsü.

Ayarlar'ın altında Erişim Anahtarı'nı seçin. Birincil anahtar değeri olan Hesap Anahtarı'nı daha sonra kullanmak üzere bir metin düzenleyicisine kopyalayın.

Hesabın Anahtarlar bölmesinin ekran görüntüsü.

Betik Oluşturma ve Ekleme

  1. Proje bölmesindeki Unity'de, Varlıklar klasöründe Betikler adlı yeni bir klasör oluşturun.
  2. Klasörde -Oluştur ->>C# Betiği'ne sağ tıklayın. Başlık: AzureSpatialAnchorsScript
  3. GameObject ->Create Empty'a gidin.
  4. Bunu seçin ve Denetçi'de GameObject'ten AzureSpatialAnchors olarak yeniden adlandırın.
  5. Hala GameObject
    1. Konumunu 0,0,0 olarak ayarlayın
    2. Bileşen Ekle'yi seçin ve AzureSpatialAnchorsScript'i arayın ve ekleyin
    3. Bileşen Ekle'yi yeniden seçin ve AR Anchor Manager'ı arayın ve ekleyin. Bu, AR Oturum Kaynağı'nın da otomatik olarak eklenmesini sağlayacaktır.
    4. Bileşen Ekle'yi yeniden seçin ve SpatialAnchorManager betiğini arayıp ekleyin
    5. Eklenen SpatialAnchorManager bileşeninde, Azure portalındaki uzamsal tutturucu kaynağından önceki adımda kopyaladığınız Hesap Kimliği, Hesap Anahtarı ve Hesap Etki Alanı'nı doldurun.

Unity - ASA GameObject

Uygulamaya Genel Bakış

Uygulamamız aşağıdaki etkileşimleri destekleyecektir:

Hareket Eylem
Herhangi bir yere dokunun Başlangıç/Devam Oturumu + El Konumunda yer işareti oluşturma
Bir tutturucuya dokunma ASA Bulut Hizmeti'nde Yer İşareti Silme GameObject + Sil
2 saniye boyunca + Ayrı Tut'a dokunun (+ oturum çalışıyor) Oturumu durdurun ve tümünü GameObjectskaldırın. ASA Bulut Hizmeti'nde yer işaretleri tutma
2 saniye boyunca + Ayrı Tut'a dokunun (+ oturum çalışmıyor) Oturumu başlatın ve tüm bağlantıları arayın.

Dokunma tanıma ekleme

Kullanıcının dokunma hareketini tanıyabilmek için betiğimize kod ekleyelim.

  1. Unity Projesi bölmenizdeki betiklere çift tıklayarak Visual Studio'da açınAzureSpatialAnchorsScript.cs.
  2. Sınıfınıza aşağıdaki diziyi ekleyin
public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };
  1. Update() yönteminin altına aşağıdaki iki yöntemi ekleyin. Uygulamayı daha sonraki bir aşamada ekleyeceğiz
// 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. Aşağıdaki içeri aktarmayı ekleyin
using UnityEngine.XR;
  1. Aşağıdaki kodu yönteminin Update() üstüne ekleyin. Bu, uygulamanın kısa ve uzun (2 sn) elle dokunma hareketlerini tanımasını sağlar
// 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
                }
            }
        }

    }
}

SpatialAnchorManager Ekle ve Yapılandır

ASA SDK'sı, ASA hizmetine çağrı yapmak için adlı SpatialAnchorManager basit bir arabirim sunar. Şimdi bunu bir değişken olarak ekleyelim AzureSpatialAnchorsScript.cs

İlk olarak içeri aktarmayı ekleyin

using Microsoft.Azure.SpatialAnchors.Unity;

Ardından değişkenini bildirin

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;

yönteminde Start() değişkenini önceki adımda eklediğimiz bileşene atayın

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

Hata ayıklama ve hata günlüklerini almak için farklı geri çağırmalara abone olmamız gerekir

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

Not

Günlükleri görüntülemek için Projeyi Unity'den oluşturup visual studio çözümünü .slnaçtıktan sonra Hata Ayıkla --> Hata Ayıklama ile Çalıştır'ı seçin ve uygulama çalışırken HoloLens'i bilgisayarınıza bağlı bırakın.

Oturumu Başlat

Yer işaretleri oluşturmak ve bulmak için öncelikle bir oturum başlatmamız gerekir. çağrısı StartSessionAsync()SpatialAnchorManager yapıldığında gerekirse bir oturum oluşturulur ve ardından başlatılır. Bunu yöntemimize ShortTap() ekleyelim.

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

Bağlayıcı Oluştur

Artık çalışan bir oturum olduğuna göre yer işaretleri oluşturabiliriz. Bu uygulamada, oluşturulan tutturucuyu ve oluşturulan yer işareti GameObjects tanımlayıcılarını (yer işareti kimlikleri) izlemek istiyoruz. Şimdi kodumuza iki liste ekleyelim.

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

Şimdi parametresi tarafından tanımlanan bir konumda yer işareti oluşturan bir yöntem CreateAnchor oluşturalım.

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

Uzamsal yer işaretleri yalnızca bir konuma değil, aynı zamanda döndürmeye de sahip olduğundan, döndürmeyi her zaman holoLens'e doğru olacak şekilde oluşturalım.

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

}

Artık istenen tutturucunun konumuna ve döndürmesine sahip olduğumuza göre, görünür GameObjectbir oluşturalım. Spatial Anchors'ın temel amacı ortak ve kalıcı bir başvuru çerçevesi sağlamak olduğundan, Uzamsal Tutturucuların tutturucunun GameObject son kullanıcıya görünür olmasını gerektirmediğini unutmayın. Bu öğreticinin amacı doğrultusunda yer işaretleri küp olarak görselleştireceğiz. Her tutturucu beyaz bir küp olarak başlatılır ve oluşturma işlemi başarılı olduktan sonra yeşil bir küp haline gelecek.

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

}

Not

Varsayılan Unity derlemesinde yer aldığı için eski bir gölgelendirici kullanıyoruz. Varsayılan gölgelendirici gibi diğer gölgelendiriciler yalnızca el ile belirtilmişse veya sahnenin doğrudan bir parçasıysa eklenir. Bir gölgelendirici dahil değilse ve uygulama bunu işlemeye çalışıyorsa, pembe bir malzemeyle sonuçlanır.

Şimdi Spatial Anchor bileşenlerini ekleyelim ve yapılandıralım. Tutturucunun süre sonunu yer işareti oluşturma işleminden itibaren 3 gün olarak ayarlıyoruz. Bundan sonra otomatik olarak buluttan silinirler. İçeri aktarmayı eklemeyi unutmayın

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

}

Yer işareti kaydetmek için kullanıcının ortam verilerini toplaması gerekir.

/// <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%}");
    }

}

Not

HoloLens büyük olasılıkla tutturucuyu çevreleyen önceden yakalanan ortam verilerini yeniden kullanabilir ve bu da ilk kez çağrıldığında zaten doğru olmasına neden IsReadyForCreate olur.

Artık bulut uzamsal yer işareti hazırlandığına göre, burada gerçek kaydetmeyi deneyebiliriz.

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

Son olarak yöntemimize ShortTap işlev çağrısını ekleyelim

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

Uygulamamız artık birden çok yer işareti oluşturabilir. Artık tüm cihazlar, yer işareti kimliklerini bildikleri ve Azure'da aynı Spatial Anchors Kaynağına erişimi olduğu sürece oluşturulan tutturucuları (henüz süresi dolmadıysa) bulabilir.

Oturumu Durdur ve GameObjects'i Yok Et

Tüm yer işaretleri bulunan ikinci bir cihaza öykünmek için şimdi oturumu durduracak ve tüm çapa GameObject'leri kaldıracağız (yer işareti kimliklerini saklayacağız). Bundan sonra yeni bir oturum başlatacak ve depolanan yer işareti kimliklerini kullanarak tutturucuları sorgulayacağız.

SpatialAnchorManager yalnızca yöntemini çağırarak oturumun durmasıyla DestroySession() ilgilenebilir. Bunu yöntemimize LongTap() ekleyelim

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

Şimdi tüm tutturucuyu kaldırmak için bir yöntem oluşturalım GameObjects

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

Ve oturumu yok ettikten sonra 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");
}

Yer İşareti Bul

Şimdi tutturucuları, içinde oluşturduğumuz doğru konuma ve döndürmeye yeniden bulmaya çalışacağız. Bunu yapmak için bir oturum başlatmamız ve verilen ölçütlere uygun tutturucuları arayabilecek bir Watcher oluşturmamız gerekir. Ölçüt olarak, daha önce oluşturduğumuz tutturucuların kimliklerini ona besleriz. Şimdi bir yöntem LocateAnchor() oluşturalım ve kullanarak SpatialAnchorManager oluşturalım Watcher. Yer işareti kimlikleri dışındaki bulma stratejileri için bkz. Yer işareti bulma stratejisi

/// <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!");
    }
}

bir izleyici başlatıldıktan sonra, verilen ölçütlere uyan bir yer işareti bulduğunda bir geri çağırma tetikler. İlk olarak izleyici bir yer işareti bulunduğunda çağrılacak şekilde yapılandıracağımız yer işareti bulunan yöntemimizi SpatialAnchorManager_AnchorLocated() oluşturalım. Bu yöntem bir görsel GameObject oluşturur ve yerel yer işareti bileşenini buna ekler. Yerel yer işareti bileşeni, öğesinin doğru konumunun ve döndürmesinin ayarlandığından GameObject emin olur.

Oluşturma işlemine benzer şekilde, tutturucu bir GameObject'e eklenir. Uzamsal tutturucuların çalışması için bu GameObject'in sahnenizde görünür olması gerekmez. Bu öğreticinin amacı doğrultusunda, her tutturucuyu bir kez bulduktan sonra mavi bir küp olarak görselleştireceğiz. Tutturucuyu yalnızca paylaşılan bir koordinat sistemi oluşturmak için kullanıyorsanız, oluşturulan GameObject'i görselleştirmeniz gerekmez.

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

İzleyici bir yer işareti bulduğunda yöntemimizin SpatialAnchorManager_AnchorLocated() çağrıldığından emin olmak için şimdi anchorLocated geri SpatialAnchorManager çağırmasına abone olalım.

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

Son olarak, yer işareti bulmayı da içerecek şekilde yöntemimizi LongTap() genişletelim. Uygulamaya Genel Bakış'ta IsSessionStarted açıklandığı gibi tüm yer işaretleri mi aradığımıza yoksa tüm tutturucuları mı yok ettiğimize karar vermek için boole değerini kullanacağız

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

Deneyin #2

Uygulamanız artık tutturucu oluşturmayı ve bunları bulmayı destekliyor. Uygulamanızı Unity'de derleyin ve dağıtmak ve hata ayıklamak için Visual Studio'yu kullanma'ya tıklayarak Visual Studio'dan dağıtın.

HoloLens'inizin İnternet'e bağlı olduğundan emin olun. Uygulama başladıktan ve Unity ile yapıldı iletisi kaybolduktan sonra çevrenize kısa bir dokunuşla dokunun. Oluşturulacak tutturucunun konumunu ve döndürmesini gösteren beyaz bir küp görünmelidir. Yer işareti oluşturma işlemi otomatik olarak çağrılır. Çevrenize yavaşça göz attıkça ortam verilerini yakalarsınız. Yeterli ortam verileri toplandıktan sonra uygulamamız belirtilen konumda bir yer işareti oluşturmaya çalışır. Yer işareti oluşturma işlemi tamamlandıktan sonra küp yeşile döner. Her şeyin beklendiği gibi çalışıp çalışmadiğini görmek için Visual Studio'da hata ayıklama günlüklerinizi denetleyin.

Sahnenizden tümünü GameObjects kaldırmak ve uzamsal bağlantı oturumunu durdurmak için uzun dokunun.

Sahneniz temizlendikten sonra yeniden uzun süre dokunarak bir oturum başlatabilir ve daha önce oluşturduğunuz tutturucuları arayabilirsiniz. Bulunduktan sonra, sabitlenmiş konumda ve döndürmede mavi küpler tarafından görselleştirilirler. Bu tutturucular (süresi dolmadığı sürece) doğru yer işareti kimliklerine sahip oldukları ve uzamsal bağlantı kaynağınıza erişimi olduğu sürece desteklenen herhangi bir cihaz tarafından bulunabilir.

Tutturucu sil

Şu anda uygulamamız yer işaretleri oluşturabilir ve bulabilir. öğesini silerken GameObjects, buluttaki tutturucuyu silmez. Mevcut bir yer işaretine dokunursanız bulutta da silme işlevselliğini ekleyelim.

şimdi bir alan GameObjectyöntemi DeleteAnchor ekleyelim. Ardından, buluttaki tutturucunun silinmesini istemek için öğesini nesnesinin CloudNativeAnchor bileşeniyle birlikte kullanacağızSpatialAnchorManager.

/// <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!");
}

bu yöntemi 'den ShortTapçağırmak için, bir dokunmanın mevcut görünür bir tutturucuya yakın olup olmadığını belirleyebilmemiz gerekir. Şimdi bunu halleden bir yardımcı yöntem oluşturalım

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

Artık yöntemimizi ShortTap çağrıyı DeleteAnchor içerecek şekilde genişletebiliriz

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

Deneyin #3

Uygulamanızı Unity'de derleyin ve dağıtmak ve hata ayıklamak için Visual Studio'yu kullanma'ya tıklayarak Visual Studio'dan dağıtın.

El dokunma hareketinizin konumunun parmaklarınızın ucu değil, bu uygulamadaki elinizin merkezi olduğunu unutmayın.

Bir tutturucuya dokunduğunuzda, oluşturulan (yeşil) veya bulunan (mavi) bu tutturucuyu hesaptan kaldırmak için uzamsal bağlantı hizmetine bir istek gönderilir. Oturumu durdurun (uzun dokunma) ve tüm tutturucuları aramak için oturumu yeniden başlatın (uzun dokunma). Silinen tutturucular artık bulunmayacak.

Her şeyi bir araya getirmek

Tüm farklı öğeler bir araya getirildikten sonra tam AzureSpatialAnchorsScript sınıf dosyasının nasıl görüneceği aşağıda verilmiştir. Bunu, kendi dosyanızla karşılaştırmak için başvuru olarak kullanabilir ve farklarınız varsa noktayı belirleyebilirsiniz.

Not

Betikte yer [RequireComponent(typeof(SpatialAnchorManager))] aldığımızı fark edeceksiniz. Bununla Unity, eklediğimiz AzureSpatialAnchorsScript GameObject'in de SpatialAnchorManager buna bağlı olduğundan emin olur.

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>

}

Sonraki adımlar

Bu öğreticide Unity kullanarak HoloLens için temel bir Spatial Anchors uygulaması uygulamayı öğrendiniz. Azure Spatial Anchors'ı yeni bir Android uygulamasında kullanma hakkında daha fazla bilgi edinmek için sonraki öğreticiye geçin.