HoloLens开发学习记录--- 8.World Anchor 空间锚(场景固定到某位置)

        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)移动已经添加空间锚的物体

   之前说过物体被添加空间锚后无法移动,因此步骤如下:

  1.    销毁空间锚
  2.    移动物体
  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数组,在另外一台设备中加载这个数组并重新还原出锚点信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章