Hololens 實現全息體驗的一個特性就是場景保持。當用戶離開場景或關閉應用時,場景中的全息圖會被保存在所放置的位置,當用戶回到場景或重新打開應用時,能夠準確的還原之前場景內的全息內容。
World Anchor(空間錨)提供了一種能夠將物體保留在特定位置和旋轉狀態上的方法,以此來保證全息對象的穩定性(即靜止參考框架),也通過它來實現場景保持。
腳本WorldAnchorStore.cs 是實現空間錨特性的關鍵 API,爲了能夠真正保持一個全息對象,通常爲根 GameObject 添加空間錨,同時對其子 GameObject 也附上具有相對位置偏移的空間錨組件。
一、實例程序
(一)、 用 unity2018.4.9 vs2017 創建一個新的 Unity 項目 VoiceDemo,初始化項目:
1.導入 MRTK 包 (版本 HoloToolkit-Unity-2017.4.2.0)
2.應用項目設置爲 MR 項目 (一鍵設置成爲可以部署的環境)
3.使用 HoloLensCamera 替代默認相機
4.添加 CursorWithFeedback (識別並反饋手勢的光標控件)
5.添加 InputManager (作爲輸入源管理器,管理 gaze,gesture,speech等)
6.設置 InputManager 的 SimpleSinglePointerSelector 腳本的 Cursor 屬性爲添加的 CursorWithFeedback (添加手勢源到inputmanger)
7.添加一個 Cube 改動z:4
最終 Hierarchy 結構如下:
(二)、編寫腳本 CubeCommand.cs 並將其添加到 Cube 上。
當添加腳本遇到錯誤時,更改腳本名稱試試。
項目實驗效果: 打開程序,立方體位於前方4m處。 點擊立方體時,立方體會隨着視野移動,再次點擊則被放置。 重新打開程序時,立方體的位置已經改變。
using UnityEngine;
using HoloToolkit.Unity.InputModule;
using UnityEngine.XR.WSA;
using UnityEngine.XR.WSA.Persistence;
using System.Linq;
public class CubeCommand1 : MonoBehaviour, IInputClickHandler //因爲要《點擊跟隨視野》和《點擊放置》,引用該接口 IInputClickHandler
{
// 定義對象: 被保存的錨點
public string ObjectAnchorStoreName;
//定義對象: 存儲錨點的倉庫 屬於UnityEngine.XR.WSA.Persistence
WorldAnchorStore anchorStore;
// 是否可被移動
bool HasMove = false;
void Start()
{
WorldAnchorStore.GetAsync(AnchorStoreReady); //WorldAnchorStore的靜態方法,獲取WorldAnchorStore實例。
}
private void AnchorStoreReady(WorldAnchorStore store)
{
anchorStore = store;
if (anchorStore.GetAllIds().Contains(ObjectAnchorStoreName)) //GetAllIds獲取當前持久化的WorldAnchors的所有標識符,返回的時string。判斷是否有當前的錨點
{
anchorStore.Load(ObjectAnchorStoreName, gameObject); //若錨點存在,則加載到遊戲對象上。
}
}
void Update()
{
// 如果立方體可移動,更新其位置
if (HasMove)
{
gameObject.transform.position = Camera.main.transform.position + Camera.main.transform.forward * 2;
}
}
public void OnInputClicked(InputClickedEventData eventData) // IInputClickHandler接口調用的方法 點擊就調用
{
if (anchorStore == null)
{
return;
}
if (HasMove)
{
// 當物體處於移動狀態,且再次被點擊後
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
if (anchor.isLocated)
{
anchorStore.Save(ObjectAnchorStoreName, anchor);
}
else
{
anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
}
}
else
{
// 當物體處於不可移動,且再次被點擊後
WorldAnchor anchor = gameObject.GetComponent<WorldAnchor>();
if (anchor != null)
{
DestroyImmediate(anchor);
}
if (anchorStore.GetAllIds().Contains(ObjectAnchorStoreName))
{
anchorStore.Delete(ObjectAnchorStoreName);
}
}
HasMove = !HasMove;
}
void Anchor_OnTrackingChanged(WorldAnchor self, bool located)
{
if (located)
{
anchorStore.Save(ObjectAnchorStoreName, self);
// 取消事件監聽
self.OnTrackingChanged -= Anchor_OnTrackingChanged;
}
}
}
三、相關API解析
添加命名空間:
using UnityEngine.XR.WSA;
using UnityEngine.XR.WSA.Persistence;
(1)爲物體添加空間錨
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
(2)銷燬物體上的空間錨
當物體被添加空間錨後,該物體不能夠再移動。
假設要單純的銷燬空間錨,不需要移動物體,則使用Destroy():
Destroy(gameObject.GetComponent<WorldAnchor>());
假設銷燬空間錨,之後需要移動物體,使用 DestroyImmediate() 來銷燬空間錨:
DestroyImmediate(gameObject.GetComponent<WorldAnchor>());
(3)移動已經添加空間錨的物體
之前說過物體被添加空間錨後無法移動,因此步驟如下:
- 銷燬空間錨
- 移動物體
- 重新添加空間錨
DestroyImmediate(gameObject.GetComponent<WorldAnchor>());
gameObject.transform.position = new Vector3(0, 0, 2);
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
(4)讀取已保存的所有空間錨
通過調用 WorldAnchorStore.GetAsync() 來加載所有保存的空間錨。
void Start () {
WorldAnchorStore.GetAsync(AnchorStoreReady);
}
private void AnchorStoreReady(WorldAnchorStore store)
{
// 讀取所有已保存的空間錨
WorldAnchorStore anchorStore = store;
string[] ids = anchorStore.GetAllIds();
}
(5)保存空間錨
/**
* 返回是否保存成功
* @Param anchorName: 保存的錨點名
* @Param anchor: 物體上的錨點組件
*/
bool saved = anchorStore.Save(anchorName, anchor);
(6)加載已保存的空間錨到物體上
/**
* 當加載成功時返回錨點對象
* @Param anchorName: 保存的錨點名
* @Param gameObject: 被添加空間錨的目標對象
*/
WorldAnchor anchor = anchorStore.Load(anchorName, gameObject);
(7)刪除已保存的空間錨
/**
* 返回是否刪除成功
* @Param anchorName: 刪除的錨點名
*/
bool deleted = anchorStore.Delete(anchorName);
(8)OnTrackingChanged 事件
當我們爲物體添加空間錨的情況下,有些情況空間錨會被立即定位到,即:
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
// anchor.isLocated == true
但是有些情況下不會被立即定位到,我們可以爲空間錨綁定 OnTrackingChanged 事件,當它定位成功後,再繼續後面的邏輯。
anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
例如,我們需要爲物體添加空間錨,等到被定位後將其保存起來,那麼代碼大概如下:
void OnSelect() {
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
if(anchor.isLocated) {
anchorStore.Save("測試錨點名", anchor);
} else {
anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
}
}
void Anchor_OnTrackingChanged(WorldAnchor self, bool located) {
if(located) {
anchorStore.Save("測試錨點名", self);
// 取消事件監聽
self.OnTrackingChanged -= Anchor_OnTrackingChanged;
}
}
四、錨點共享
錨點可以在多個設備間共享,來使得不同設備可以使用相同的空間位置,可以通過 WorldAnchorTransferBatch
將錨點信息導出爲byte數組,在另外一臺設備中加載這個數組並重新還原出錨點信息。