Hololens入门之空间锚与场景保持
World Anchor(空间锚)提供了一种能够将物体保留在特定位置和旋转状态上的方法。这保证了全息对象的稳定性,同时提供了后续在真实世界中保持全息对象位置的能力。简单地说,你可以为全息物体来添加空间锚点,这样就能在后续步骤中将全息物体准确恢复到它原来的位置。
场景保持是HoloLens全息体验的一个关键特性,当用户离开原场景中时,原场景中全息对象会保持在特定位置,当用户回到原场景时,能够准确还原原场景的全息内容。WorldAnchorStore类是实现此特性的关键API,这保证了用户能够将任何全息对象贴到任何他们想要放置的位置。
WorldAnchorStore能够允许你保持场景中空间锚的位置,为了能够真正保持全息对象,你需要单独使用特定的空间锚来追踪每一个对象。通常创建一个根GameObject并附上空间锚,同时对它的子GameObject也附上具有相对位置偏移的空间锚组件。
本文实现以下功能
移动两个Cube的位置,关闭应用,,然后通过场景保持在下一次启动应用时,能够恢复应用关闭前Cube所处的位置
本文示例在 Hololens入门之凝视 的基础上进行修改开发
1、在Manager中添加GestureManager.cs脚本组件(直接使用HoloToolkit中的GestureManager.cs)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using UnityEngine;
using UnityEngine.VR.WSA.Input;
namespace HoloToolkit.Unity
{
///
/// GestureManager creates a gesture recognizer and signs up for a tap gesture.
/// When a tap gesture is detected, GestureManager uses GazeManager to find the game object.
/// GestureManager then sends a message to that game object.
///
[RequireComponent(typeof(GazeManager))]
public partial class GestureManager : Singleton
{
///
/// Key to press in the editor to select the currently gazed hologram
///
public KeyCode EditorSelectKey = KeyCode.Space;
///
/// To select even when a hologram is not being gazed at,
/// set the override focused object.
/// If its null, then the gazed at object will be selected.
///
public GameObject OverrideFocusedObject
{
get; set;
}
///
/// Gets the currently focused object, or null if none.
///
public GameObject FocusedObject
{
get { return focusedObject; }
}
private GestureRecognizer gestureRecognizer;
private GameObject focusedObject;
void Start()
{
// Create a new GestureRecognizer. Sign up for tapped events.
gestureRecognizer = new GestureRecognizer();
gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap);
gestureRecognizer.TappedEvent += GestureRecognizer_TappedEvent;
// Start looking for gestures.
gestureRecognizer.StartCapturingGestures();
}
private void OnTap()
{
if (focusedObject != null)
{
focusedObject.SendMessage("OnSelect");
}
}
private void GestureRecognizer_TappedEvent(InteractionSourceKind source, int tapCount, Ray headRay)
{
OnTap();
}
void LateUpdate()
{
GameObject oldFocusedObject = focusedObject;
if (GazeManager.Instance.Hit &&
OverrideFocusedObject == null &&
GazeManager.Instance.HitInfo.collider != null)
{
// If gaze hits a hologram, set the focused object to that game object.
// Also if the caller has not decided to override the focused object.
focusedObject = GazeManager.Instance.HitInfo.collider.gameObject;
}
else
{
// If our gaze doesn't hit a hologram, set the focused object to null or override focused object.
focusedObject = OverrideFocusedObject;
}
if (focusedObject != oldFocusedObject)
{
// If the currently focused object doesn't match the old focused object, cancel the current gesture.
// Start looking for new gestures. This is to prevent applying gestures from one hologram to another.
gestureRecognizer.CancelGestures();
gestureRecognizer.StartCapturingGestures();
}
#if UNITY_EDITOR
if (Input.GetMouseButtonDown(1) || Input.GetKeyDown(EditorSelectKey))
{
OnTap();
}
#endif
}
void OnDestroy()
{
gestureRecognizer.StopCapturingGestures();
gestureRecognizer.TappedEvent -= GestureRecognizer_TappedEvent;
}
}
}
2、新增两个Cube GameObject,添加脚本组件CubeScript.cs
CubeScript.cs如下
using UnityEngine;
using System.Collections;
using UnityEngine.VR.WSA.Persistence;
using System;
using UnityEngine.VR.WSA;
public class CubeScript : MonoBehaviour {
public string ObjectAnchorStoreName;
WorldAnchorStore anchorStore;
bool Placing = false;
void Start () {
//获取WorldAnchorStore 对象
WorldAnchorStore.GetAsync(AnchorStoreReady);
}
private void AnchorStoreReady(WorldAnchorStore store)
{
anchorStore = store;
string[] ids = anchorStore.GetAllIds();
//遍历之前保存的空间锚,载入指定id场景对象信息
for (int index = 0; index < ids.Length; index++)
{
if (ids[index] == ObjectAnchorStoreName)
{
WorldAnchor wa = anchorStore.Load(ids[index], gameObject);
break;
}
}
}
// Update is called once per frame
void Update () {
if (Placing)
{
//当Cube处于可移动状态,根据凝视射线的位置,更新Cube的位置
gameObject.transform.position = Camera.main.transform.position + Camera.main.transform.forward * 2;
}
}
void OnSelect()
{
if (anchorStore == null)
{
return;
}
if (Placing)
{
//当再次点击全息对象时,保存空间锚信息
WorldAnchor attachingAnchor = gameObject.AddComponent();
if (attachingAnchor.isLocated)
{
bool saved = anchorStore.Save(ObjectAnchorStoreName, attachingAnchor);
}
else
{
//有时空间锚能够立刻被定位到。这时候,给对象添加空间锚后,空间锚组件的isLocated属性
//值将会被设为true,这时OnTrackingChanged事件将不会被触发。因此,在添加空间锚组件
//后,推荐立刻使用初始的isLocated状态去调用OnTrackingChanged事件
attachingAnchor.OnTrackingChanged += AttachingAnchor_OnTrackingChanged;
}
}
else
{
//当全息对象已附加空间锚组件后,它不能被移动。如果你需要移动全息对象的话,那么你必须这样做:
//1.立刻销毁空间锚组件
//2.移动全息对象
//3.添加一个新的空间锚到全息对象上
WorldAnchor anchor = gameObject.GetComponent();
if (anchor != null)
{
DestroyImmediate(anchor);
}
string[] ids = anchorStore.GetAllIds();
for (int index = 0; index < ids.Length; index++)
{
if (ids[index] == ObjectAnchorStoreName)
{
bool deleted = anchorStore.Delete(ids[index]);
break;
}
}
}
Placing = !Placing;
}
private void AttachingAnchor_OnTrackingChanged(WorldAnchor self, bool located)
{
if (located)
{
bool saved = anchorStore.Save(ObjectAnchorStoreName, self);
self.OnTrackingChanged -= AttachingAnchor_OnTrackingChanged;
}
}
}
4、运行测试
1) 启动应用,可以看到Cube的初始位置
2) 选中Cube,移动头部,然后再次选中Cube将Cube放置到新的位置
3)关闭应用
4)再次打开应用,能够看到两个Cube所处的位置为关闭应用前的位置,非初始位置