先学习一下HoloToolkit的原理,但是直接看里面的脚本,相互直接有关系,稍微有点复杂,先从官方教程开始学习吧。使用最原始的脚本来进行交互。
官方提供了系列教程:
https://developer.microsoft.com/en-us/windows/mixed-reality/academy
交互部分:
与Hololens的交互有:Gaze(凝视),Gesture(手势),Voice(语音)。
1.Gaze(凝视)
WorldCursor.cs
using UnityEngine;
public class WorldCursor : MonoBehaviour {
private MeshRenderer meshRenderer;
void Start () {
meshRenderer = this.gameObject.GetComponentInChildren<MeshRenderer>();
}
void Update () {
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;
if(Physics.Raycast(headPosition,gazeDirection,out hitInfo))
{
meshRenderer.enabled = true;
this.transform.position = hitInfo.point;
this.transform.rotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
}
else
{
meshRenderer.enabled = false;
}
}
}
创建一个游戏物体(简单的圆)作为光标,添加脚本,移动摄像头。
当摄像头前有物体时光标显示在物体上,没有时则隐藏。
注意:作为光标的物体不能有Collider,不然会发生光标从碰撞处移动到摄像头,不断循环,因为光标显示后就会碰上自己了。
相当于头部(摄像头)就是鼠标本身,通过移动头部来控制光标的位置。
2.Gesture(手势)
GazeGestureManager.cs
using UnityEngine;
using UnityEngine.VR.WSA.Input;
public class GazeGestureManager : MonoBehaviour {
public static GazeGestureManager Instance { get; set; }
public GameObject FocusedObject { get; private set; }
GestureRecognizer recognizer;
void Awake () {
Instance = this;
recognizer = new GestureRecognizer();
recognizer.TappedEvent += Recognizer_TappedEvent;
recognizer.StartCapturingGestures();
}
private void Recognizer_TappedEvent(InteractionSourceKind source, int tapCount, Ray headRay)
{
SendSelect();
}
[ContextMenu("SendSelect")]
void SendSelect()
{
if (FocusedObject != null)
{
FocusedObject.SendMessageUpwards("OnSelect", SendMessageOptions.DontRequireReceiver);
}
}
void Update () {
GameObject oldFolusObject = FocusedObject;
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;
if (Physics.Raycast(headPosition, gazeDirection, out hitInfo))
{
FocusedObject = hitInfo.collider.gameObject;
}
else
{
FocusedObject = null;
}
if (FocusedObject != oldFolusObject)
{
recognizer.CancelGestures();
recognizer.StartCapturingGestures();
}
}
}
通过GestureRecognizer识别点击事件(Tap),发送OnSelect消息给当前对焦的游戏物体。
添加到场景中的任何物体上都可以。
using UnityEngine;
public class Commands : MonoBehaviour {
void OnSelect()
{
if (!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
}
接收GestureManager发送过来的OnSelect消息,在OnSelect中给游戏物体添加刚体,让物体下落。
[ContextMenu("SendSelect")]是为了在Unity中测试
3.Voice(声音)
SpeechManager
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Windows.Speech;
public class SpeechManager : MonoBehaviour {
KeywordRecognizer keywordRecognizer = null;
Dictionary<string, System.Action> keywords = new Dictionary<string, System.Action>();
// Use this for initialization
void Start()
{
keywords.Add("Reset world", SendReset);
keywords.Add("Drop Sphere", SendDrop);
keywordRecognizer = new KeywordRecognizer(keywords.Keys.ToArray());
keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
keywordRecognizer.Start();
}
[ContextMenu("SendReset")]
void SendReset()
{
this.BroadcastMessage("OnReset");
}
[ContextMenu("SendDrop")]
void SendDrop()
{
var focosObject = GazeGestureManager.Instance.FocusedObject;
if (focosObject != null)
{
focosObject.SendMessage("OnDrop", SendMessageOptions.DontRequireReceiver);
}
}
private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
{
System.Action keywordActoin;
if(keywords.TryGetValue(args.text,out keywordActoin))
{
keywordActoin.Invoke();
}
}
// Update is called once per frame
void Update () {
}
}
定义关键字,通过KeywordRecognizer识别语音,根据识别到的关键字发送相应的消息。
添加到场景中的任何物体上都可以。
修改Commands,添加相应的消息响应函数。
using UnityEngine;
public class Commands : MonoBehaviour {
void OnSelect()
{
if (!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
Vector3 originalPosition;
void Start()
{
originalPosition = this.transform.localPosition;
}
void OnReset()
{
var rigidbody = this.GetComponent<Rigidbody>();
if (rigidbody != null)
{
rigidbody.isKinematic = true;
Destroy(rigidbody);
}
this.transform.localPosition = originalPosition;
}
void Ondrop()
{
OnSelect();
}
}
因为用的是BroadcastMessage发送消息,要把游戏物体放到SpeechManager物体的子物体中,Reset才有反应。
后面还有,稍微看一下就好
4.空间声音
按提示修改场景
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpatialSounds : MonoBehaviour {
AudioSource impactAudoSource = null;
AudioSource rollingAudioSource = null;
bool rolling = false;
// Use this for initialization
void Start () {
impactAudoSource = gameObject.AddComponent<AudioSource>();
impactAudoSource.playOnAwake = false;
impactAudoSource.spatialize = true;
impactAudoSource.spatialBlend = 1.0f;
impactAudoSource.dopplerLevel = 0.0f;
impactAudoSource.rolloffMode = AudioRolloffMode.Logarithmic;
impactAudoSource.maxDistance = 20f;
rollingAudioSource = gameObject.AddComponent<AudioSource>();
rollingAudioSource.playOnAwake = false;
rollingAudioSource.spatialize = true;
rollingAudioSource.spatialBlend = 1.0f;
rollingAudioSource.dopplerLevel = 0.0f;
rollingAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
rollingAudioSource.maxDistance = 20f;
rollingAudioSource.loop = true;
impactAudoSource.clip = Resources.Load<AudioClip>("Impact");
rollingAudioSource.clip = Resources.Load<AudioClip>("Rolling");
}
void OnCollisionEnter(Collision collision)
{
print("OnCollisionEnter:"+ collision);
if (collision.relativeVelocity.magnitude >= 0.1f)
{
impactAudoSource.Play();
}
}
void OnCollisionStay(Collision collision)
{
Rigidbody rigid = gameObject.GetComponent<Rigidbody>();
if(!rolling && rigid.velocity.magnitude >= 0.01f)
{
rolling = true;
rollingAudioSource.Play();
}
else if(rolling && rigid.velocity.magnitude < 0.01f)
{
rolling = false;
rollingAudioSource.Stop();
}
}
void OnCollisionExit(Collision collision)
{
print("OnCollisionExit:" + collision);
if (rolling)
{
rolling = false;
impactAudoSource.Stop();
rollingAudioSource.Stop();
}
}
}
运行后的效果是点击,两个小球掉下来,滚动,响应的发出滚动的声音。停止滚动则停止声音。
5.空间地图
按提示修改场景,添加 Spatial Mapping预设,修改场景中的脚本设置。
添加脚本 TapToPlaceParent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TapToPlaceParent : MonoBehaviour {
bool placing = false;
// Use this for initialization
void OnSelect(){
placing = !placing;
if (placing)
{
SpatialMapping.Instance.DrawVisualMeshes = true;
}
else
{
SpatialMapping.Instance.DrawVisualMeshes = false;
}
}
// Update is called once per frame
void Update () {
if (placing)
{
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;
if(Physics.Raycast(headPosition,gazeDirection,out hitInfo,30.0f,SpatialMapping.PhysicsRaycastMask))
{
this.transform.parent.position = hitInfo.point;
Quaternion toQuat = Camera.main.transform.localRotation;
toQuat.x = 0;
toQuat.z = 0;
this.transform.parent.rotation = toQuat;
}
}
}
}
没有实际测试过,总之应该是点击物体,地图显示。物体在地图上移动,再点击一次物体,固定物体。隐藏地图。
地图还不是很精细,应该有地方调整的。以后再说。
到这,基本就过一遍了。后面的教程是对这几方面的更加详细的展开说明。
按需要再学习吧。
先用当前的做一下测试。