一样要注册azure账号ID,key什么的一个都不能少,因此跟官网的unity2020实现的要求是一个尿性。之所以也记载一下,是因为这是早期的版本,暴漏出底层的一些代码,容易理解和升华,官网新的例子包装太厉害了,看不到底层的原理。通过研究代码,我发现,Azure Space Anchor起码在这里最终还是用的World Anchor。本例子还是用的一代Hololensd的点击建立小球,生成anchor,状态不同,颜色自己变。如果你的azure账号ID,key什么的已经过期,那么最终就是个红球。然后具体实现如下。用的unity2019.4.29.f1c2,unity hub统一管理
存在我电脑中的位置:
需要导入的package
主物体sphere的位置,这里是用复制技术实现的物体显示 ,点哪里出在哪里
主要物体挂在的主代码
AzureSpatialAnchorsScript.cs代码如下:
using Microsoft.Azure.SpatialAnchors;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.WSA;
using UnityEngine.XR.WSA.Input;
public class AzureSpatialAnchorsScript : MonoBehaviour
{
/// <summary>
/// The sphere prefab.
/// </summary>
public GameObject spherePrefab;
/// <summary>
/// Set this string to the Spatial Anchors account id provided in the Spatial Anchors resource.
/// </summary>
protected string SpatialAnchorsAccountId = "4db07f78-bf45-40b8-9808-d5904672649e";
/// <summary>
/// Set this string to the Spatial Anchors account key provided in the Spatial Anchors resource.
/// </summary>
protected string SpatialAnchorsAccountKey = "ChThGYP25XeNJdIakyBXULCyP/JwlNjh1ilXmtCQc4A=";
/// <summary>
/// Set this string to the Spatial Anchors account domain provided in the Spatial Anchors resource.
/// </summary>
protected string SpatialAnchorsAccountDomain = "southeastasia.mixedreality.azure.com";
/// <summary>
/// Our queue of actions that will be executed on the main thread.
/// </summary>
private readonly Queue<Action> dispatchQueue = new Queue<Action>();
/// <summary>
/// Use the recognizer to detect air taps.
/// </summary>
private GestureRecognizer recognizer;
protected CloudSpatialAnchorSession cloudSpatialAnchorSession;
/// <summary>
/// The CloudSpatialAnchor that we either 1) placed and are saving or 2) just located.
/// </summary>
protected CloudSpatialAnchor currentCloudAnchor;
/// <summary>
/// True if we are 1) creating + saving an anchor or 2) looking for an anchor.
/// </summary>
protected bool tapExecuted = false;
/// <summary>
/// The ID of the CloudSpatialAnchor that was saved. Use it to find the CloudSpatialAnchor
/// </summary>
protected string cloudSpatialAnchorId = "";
/// <summary>
/// The sphere rendered to show the position of the CloudSpatialAnchor.
/// </summary>
protected GameObject sphere;
protected Material sphereMaterial;
/// <summary>
/// Indicate if we are ready to save an anchor. We can save an anchor when value is greater than 1.
/// </summary>
protected float recommendedForCreate = 0;
// Start is called before the first frame update
void Start()
{
recognizer = new GestureRecognizer();
recognizer.StartCapturingGestures();
recognizer.SetRecognizableGestures(GestureSettings.Tap);
recognizer.Tapped += HandleTap;
InitializeSession();
}
// Update is called once per frame
void Update()
{
lock (dispatchQueue)
{
if (dispatchQueue.Count > 0)
{
dispatchQueue.Dequeue()();
}
}
}
/// <summary>
/// Queues the specified <see cref="Action"/> on update.
/// </summary>
/// <param name="updateAction">The update action.</param>
protected void QueueOnUpdate(Action updateAction)
{
lock (dispatchQueue)
{
dispatchQueue.Enqueue(updateAction);
}
}
/// <summary>
/// Cleans up objects.
/// </summary>
public void CleanupObjects()
{
if (sphere != null)
{
Destroy(sphere);
sphere = null;
}
if (sphereMaterial != null)
{
Destroy(sphereMaterial);
sphereMaterial = null;
}
currentCloudAnchor = null;
}
/// <summary>
/// Cleans up objects and stops the CloudSpatialAnchorSessions.
/// </summary>
public void ResetSession(Action completionRoutine = null)
{
Debug.Log("ASA Info: Resetting the session.");
if (cloudSpatialAnchorSession.GetActiveWatchers().Count > 0)
{
Debug.LogError("ASA Error: We are resetting the session with active watchers, which is unexpected.");
}
CleanupObjects();
this.cloudSpatialAnchorSession.Reset();
lock (this.dispatchQueue)
{
this.dispatchQueue.Enqueue(() =>
{
if (cloudSpatialAnchorSession != null)
{
cloudSpatialAnchorSession.Stop();
cloudSpatialAnchorSession.Dispose();
Debug.Log("ASA Info: Session was reset.");
completionRoutine?.Invoke();
}
else
{
Debug.LogError("ASA Error: cloudSpatialAnchorSession was null, which is unexpected.");
}
});
}
}
/// <summary>
/// Initializes a new CloudSpatialAnchorSession.
/// </summary>
void InitializeSession()
{
Debug.Log("ASA Info: Initializing a CloudSpatialAnchorSession.");
if (string.IsNullOrEmpty(SpatialAnchorsAccountId))
{
Debug.LogError("No account id set.");
return;
}
if (string.IsNullOrEmpty(SpatialAnchorsAccountKey))
{
Debug.LogError("No account key set.");
return;
}
cloudSpatialAnchorSession = new CloudSpatialAnchorSession();
cloudSpatialAnchorSession.Configuration.AccountId = SpatialAnchorsAccountId.Trim();
cloudSpatialAnchorSession.Configuration.AccountKey = SpatialAnchorsAccountKey.Trim();
cloudSpatialAnchorSession.Configuration.AccountDomain = SpatialAnchorsAccountDomain.Trim();
cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;
cloudSpatialAnchorSession.Error += CloudSpatialAnchorSession_Error;
cloudSpatialAnchorSession.OnLogDebug += CloudSpatialAnchorSession_OnLogDebug;
cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;
cloudSpatialAnchorSession.LocateAnchorsCompleted += CloudSpatialAnchorSession_LocateAnchorsCompleted;
cloudSpatialAnchorSession.Start();
Debug.Log("ASA Info: Session was initialized.");
}
private void CloudSpatialAnchorSession_Error(object sender, SessionErrorEventArgs args)
{
Debug.LogError("ASA Error: " + args.ErrorMessage);
}
private void CloudSpatialAnchorSession_OnLogDebug(object sender, OnLogDebugEventArgs args)
{
Debug.Log("ASA Log: " + args.Message);
System.Diagnostics.Debug.WriteLine("ASA Log: " + args.Message);
}
private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args)
{
Debug.Log("ASA Log: recommendedForCreate: " + args.Status.RecommendedForCreateProgress);
recommendedForCreate = args.Status.RecommendedForCreateProgress;
}
private void CloudSpatialAnchorSession_AnchorLocated(object sender, AnchorLocatedEventArgs args)
{
switch (args.Status)
{
case LocateAnchorStatus.Located:
Debug.Log("ASA Info: Anchor located! Identifier: " + args.Identifier);
QueueOnUpdate(() =>
{
// Create a green sphere.
sphere = GameObject.Instantiate(spherePrefab, Vector3.zero, Quaternion.identity) as GameObject;
sphere.AddComponent<WorldAnchor>();
sphereMaterial = sphere.GetComponent<MeshRenderer>().material;
sphereMaterial.color = Color.green;
// Get the WorldAnchor from the CloudSpatialAnchor and use it to position the sphere.
sphere.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().SetNativeSpatialAnchorPtr(args.Anchor.LocalAnchor);
// Clean up state so that we can start over and create a new anchor.
cloudSpatialAnchorId = "";
tapExecuted = false;
});
break;
case LocateAnchorStatus.AlreadyTracked:
Debug.Log("ASA Info: Anchor already tracked. Identifier: " + args.Identifier);
break;
case LocateAnchorStatus.NotLocated:
Debug.Log("ASA Info: Anchor not located. Identifier: " + args.Identifier);
break;
case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
Debug.LogError("ASA Error: Anchor not located does not exist. Identifier: " + args.Identifier);
break;
}
}
private void CloudSpatialAnchorSession_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
{
Debug.Log("ASA Info: Locate anchors completed. Watcher identifier: " + args.Watcher.Identifier);
}
/// <summary>
/// Called by GestureRecognizer when a tap is detected.
/// </summary>
/// <param name="tapEvent">The tap.</param>
public void HandleTap(TappedEventArgs tapEvent)
{
if (tapExecuted)
{
return;
}
tapExecuted = true;
// We have saved an anchor, so we will now look for it.
if (!String.IsNullOrEmpty(cloudSpatialAnchorId))
{
Debug.Log("ASA Info: We will look for a placed anchor.");
ResetSession(() =>
{
InitializeSession();
// Create a Watcher to look for the anchor we created.
AnchorLocateCriteria criteria = new AnchorLocateCriteria();
criteria.Identifiers = new string[] { cloudSpatialAnchorId };
cloudSpatialAnchorSession.CreateWatcher(criteria);
Debug.Log("ASA Info: Watcher created. Number of active watchers: " + cloudSpatialAnchorSession.GetActiveWatchers().Count);
});
return;
}
Debug.Log("ASA Info: We will create a new anchor.");
// Clean up any anchors that have been placed.
CleanupObjects();
// Construct a Ray using forward direction of the HoloLens.
Ray GazeRay = new Ray(tapEvent.headPose.position, tapEvent.headPose.forward);
// Raycast to get the hit point in the real world.
RaycastHit hitInfo;
Physics.Raycast(GazeRay, out hitInfo, float.MaxValue);
this.CreateAndSaveSphere(hitInfo.point);
}
/// <summary>
/// Creates a sphere at the hit point, and then saves a CloudSpatialAnchor there.
/// </summary>
/// <param name="hitPoint">The hit point.</param>
protected virtual void CreateAndSaveSphere(Vector3 hitPoint)
{
// Create a white sphere.
sphere = GameObject.Instantiate(spherePrefab, hitPoint, Quaternion.identity) as GameObject;
sphere.AddComponent<WorldAnchor>();
sphereMaterial = sphere.GetComponent<MeshRenderer>().material;
sphereMaterial.color = Color.white;
Debug.Log("ASA Info: Created a local anchor.");
// Create the CloudSpatialAnchor.
currentCloudAnchor = new CloudSpatialAnchor();
// Set the LocalAnchor property of the CloudSpatialAnchor to the WorldAnchor component of our white sphere.
WorldAnchor worldAnchor = sphere.GetComponent<WorldAnchor>();
if (worldAnchor == null)
{
throw new Exception("ASA Error: Couldn't get the local anchor pointer.");
}
// Save the CloudSpatialAnchor to the cloud.
currentCloudAnchor.LocalAnchor = worldAnchor.GetNativeSpatialAnchorPtr();
Task.Run(async () =>
{
// Wait for enough data about the environment.
while (recommendedForCreate < 1.0F)
{
await Task.Delay(330);
}
bool success = false;
try
{
QueueOnUpdate(() =>
{
// We are about to save the CloudSpatialAnchor to the Azure Spatial Anchors, turn it yellow.
sphereMaterial.color = Color.yellow;
});
await cloudSpatialAnchorSession.CreateAnchorAsync(currentCloudAnchor);
success = currentCloudAnchor != null;
if (success)
{
// Allow the user to tap again to clear state and look for the anchor.
tapExecuted = false;
// Record the identifier to locate.
cloudSpatialAnchorId = currentCloudAnchor.Identifier;
QueueOnUpdate(() =>
{
// Turn the sphere blue.
sphereMaterial.color = Color.blue;
});
Debug.Log("ASA Info: Saved anchor to Azure Spatial Anchors! Identifier: " + cloudSpatialAnchorId);
}
else
{
sphereMaterial.color = Color.red;
Debug.LogError("ASA Error: Failed to save, but no exception was thrown.");
}
}
catch (Exception ex)
{
QueueOnUpdate(() =>
{
sphereMaterial.color = Color.red;
});
Debug.LogError("ASA Error: " + ex.Message);
}
});
}
}
debug用的Text UI
DebugWindow.cs代码如下:
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace MRTK.Tutorials.AzureSpatialAnchors
{
public class DebugWindow : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI debugText = default;
private ScrollRect scrollRect;
private void Start()
{
// Cache references
scrollRect = GetComponentInChildren<ScrollRect>();
// Subscribe to log message events
Application.logMessageReceived += HandleLog;
// Set the starting text
debugText.text = "Debug messages will appear here.\n\n";
}
private void OnDestroy()
{
Application.logMessageReceived -= HandleLog;
}
private void HandleLog(string message, string stackTrace, LogType type)
{
debugText.text += message + " \n";
Canvas.ForceUpdateCanvases();
scrollRect.verticalNormalizedPosition = 0;
}
}
}
设置如下: