本文首发于“洪流学堂”公众号。
洪流学堂,让你快人几步
本教程基于Unity2017.2及Visual Studio 2017
本教程编写时间:2017年12月4日
本文内容提要
学习简单的凝视(Gaze)、手势(Guesture)、英文语音控制(Voice)、立体音效(Spatial sound)、空间映射(Spatial mapping)
将这些功能整合开发一个小应用
视频教程
本教程入门篇的视频课程也已经上线
看视频教程,细节无遗漏哦~
https://edu.csdn.net/course/detail/6639
资源下载
本文使用了官方教程的资源
原地址
如果下载有困难,百度云地址
创建工程
- 将下载的资源解压
- 打开Unity3d,按下图打开工程
- 设置摄像机
- 位置设置为0,0,0
- Clear Flags设置为Solid Color
- Background设置为0,0,0,0
- 建立场景内容
- 创建一个空物体,命名为OrigamiCollection
- 将Project面板里的Holograms文件夹中的Sphere1、Sphere2、Stage拖到OrigamiCollection下面
- 将命名为OrigamiCollection的位置设置为0,-0.5,2
- 将Hierarchy中的Directionnal Light删掉,将Holograms文件夹中的Lights拖到Hierarchy面板中的空白处
Game视图看到的内容如下图所示:
5.保存场景 - 按下Ctrl+S保存场景,将场景保存到Scenes/Main.unity
- 发布测试
- File > Build Settings,选择Universal Windows Platform,点击Switch Platform按钮
- Target device设置为Hololens,选中Unity C# Projects
- 点击左下角Player Settings按钮,切换到Windows图标的标签页,在Capabilities中选中Microphone和SpatialPerception(本工程已经为选中状态,但是自己开发时一定要注意手动勾选,否侧会有权限问题)
- 在Build Settings面板,点击Build,在Origami文件夹下新建
App
文件夹并选择该文件夹 - Build完成后,打开App文件夹下的Origami.sln,将Debug改为Release,ARM改为x86,并选中Hololens Emulator
- 点击调试 > 开始执行(不调试)或者Ctrl+F5(注意:模拟器启动慢可能会引起部署超时,这时候不要关闭模拟器,直接再次Ctrl+F5即可)
- 出来画面了,如下!!!(画面如果是纯黑的,是因为Hololens模拟器的镜头位置记录了历史的位置,尝试左键按下移动镜头寻找)
凝视(Gaze)
- 将Holograms目录下的Cursor拖到Hierarchy的空白处
- 在Scripts目录(如果没有请新建)下,创建C#脚本并命名为WorldCursor
- 双击打开脚本并将以下代码粘贴覆盖
using UnityEngine;
public class WorldCursor : MonoBehaviour
{
private MeshRenderer meshRenderer;
void Start()
{
// 获取物体上的MeshRenderer组件
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))
{
// 如果射线碰到了hologram,显示curour模型
meshRenderer.enabled = true;
// 将cursor移到射线碰撞的位置
this.transform.position = hitInfo.point;
// 将cursor与hologram的表面平行
this.transform.rotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
}
else
{
// 如果涉嫌没有检测到hologram,隐藏cursor
meshRenderer.enabled = false;
}
}
}
- 将WorldCursor脚本拖到Cursor物体上
- 到这可以测试一下了!效果如下:
手势(Guesture)
- 在Scripts文件夹中创建C#脚本,名字为GazeGestureManager
- 将GazeGestureManager脚本拖到OrigamiCollection物体上
- 打开GazeGestureManager,替换为以下代码
using UnityEngine;
using UnityEngine.XR.WSA.Input;
public class GazeGestureManager : MonoBehaviour
{
public static GazeGestureManager Instance { get; private set; }
// 保存当前凝视的物体
public GameObject FocusedObject { get; private set; }
GestureRecognizer recognizer;
void Start()
{
Instance = this;
// 用来检测Select手势
recognizer = new GestureRecognizer();
recognizer.Tapped += (args) =>
{
// 向凝视的物体和父物体发送OnSelect消息
if (FocusedObject != null)
{
FocusedObject.SendMessageUpwards("OnSelect", SendMessageOptions.DontRequireReceiver);
}
};
recognizer.StartCapturingGestures();
}
void Update()
{
GameObject oldFocusObject = 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
FocusedObject = null;
}
// 如果凝视的物体发生了改变,重新开始检测手势
if (FocusedObject != oldFocusObject)
{
recognizer.CancelGestures();
recognizer.StartCapturingGestures();
}
}
}
- 在Scripts文件夹下创建SphereCommands脚本
- 脚本中替换以下代码
using UnityEngine;
public class SphereCommands : MonoBehaviour
{
// 检测到Select手势时,GazeGestureManager会发送此消息
void OnSelect()
{
// 如果球体没有刚体组件,添加一个,这样可以启用物理属性
if (!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
}
- 将SphereCommands脚本拖到Sphere1和Sphere2上面
- 可以build测试啦!在模拟器中按空格或鼠标右键模拟Select手势
英文语音控制(Voice)
- 在Scripts文件夹中创建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", () =>
{
// 在每个会掉落的物体上调用OnReset方法
this.BroadcastMessage("OnReset");
});
keywords.Add("Drop", () =>
{
var focusObject = GazeGestureManager.Instance.FocusedObject;
if (focusObject != null)
{
// 在凝视的物体上调用OnDrop方法
focusObject.SendMessage("OnDrop", SendMessageOptions.DontRequireReceiver);
}
});
// 根据关键字创建KeywordRecognizer
keywordRecognizer = new KeywordRecognizer(keywords.Keys.ToArray());
// 给KeywordRecognizer的OnPhraseRecognized注册一个回调函数
keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
// 开始识别
keywordRecognizer.Start();
}
private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
{
Debug.Log(args.text);
System.Action keywordAction;
if (keywords.TryGetValue(args.text, out keywordAction))
{
keywordAction.Invoke();
}
}
}
- 将SpeechManager 脚本拖到OrigamiCollection物体上
- 将SphereCommands 更新替换为下面的代码
using UnityEngine;
public class SphereCommands : MonoBehaviour
{
Vector3 originalPosition;
void Start()
{
// 存储球体的初始位置,用于后面重置位置
originalPosition = this.transform.localPosition;
}
// 检测到Select手势时,GazeGestureManager会发送此消息
void OnSelect()
{
// 如果球体没有刚体组件,添加一个,这样可以启用物理属性
if (!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
// 当用户说“Reset world”命令时,SpeechManager会调用此方法
void OnReset()
{
// 如果球体有刚体组件,移除该组件来禁用物理属性
var rigidbody = this.GetComponent<Rigidbody>();
if (rigidbody != null)
{
rigidbody.isKinematic = true;
Destroy(rigidbody);
}
// 将球体的位置设置为初始的local位置
this.transform.localPosition = originalPosition;
}
// 当用户说“Drop sphere”命令时,SpeechManager会调用此方法
void OnDrop()
{
// 同Select的逻辑
OnSelect();
}
}
- 可以build测试啦!(注意选中Capabilities中的Microphone)
- 凝视一个球体,说“Drop”来使小球掉落
- 说“Reset”重置小球的位置
立体音效(Spatial sound)
- Edit > Project Settings > Audio
- Spatializer Plugin 选择 MS HRTF Spatializer
- 将Holograms文件夹下的Ambience音频拖到OrigamiCollection物体上
- 选中OrigamiCollection并找到Audio Source组件,修改一下内容:
- 选中Spatialize 属性
- 选中Play On Awake.
- 将Spatial Blend的滑条拖到最右边(3D)
- 选中Loop(循环)属性
- 展开3D Sound Settings, 在Doppler Level中输入0.1
- Volume Rolloff设置为Logarithmic Rolloff
- Max Distance设置为20
- 在Scripts 文件夹中创建SphereSounds脚本,并替换为以下代码
using UnityEngine;
public class SphereSounds : MonoBehaviour
{
AudioSource impactAudioSource = null;
AudioSource rollingAudioSource = null;
bool rolling = false;
void Start()
{
// 添加AudioSource组件并设置参数
impactAudioSource = gameObject.AddComponent<AudioSource>();
impactAudioSource.playOnAwake = false;
impactAudioSource.spatialize = true;
impactAudioSource.spatialBlend = 1.0f;
impactAudioSource.dopplerLevel = 0.0f;
impactAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
impactAudioSource.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;
// 从Resources文件夹中加载声音
impactAudioSource.clip = Resources.Load<AudioClip>("Impact");
rollingAudioSource.clip = Resources.Load<AudioClip>("Rolling");
}
// 物体与其他物体碰撞时,会收到此消息
void OnCollisionEnter(Collision collision)
{
// 如果碰撞够强烈,播放碰撞音效
if (collision.relativeVelocity.magnitude >= 0.1f)
{
impactAudioSource.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)
{
// 如果物体掉落并停止碰撞,停止滚动的音效
if (rolling)
{
rolling = false;
impactAudioSource.Stop();
rollingAudioSource.Stop();
}
}
}
- 将SphereSounds脚本拖到Sphere1和Sphere2物体上
- build测试吧!(戴上耳机才能听到环绕立体效果哦)
空间映射(Spatial mapping)
- 将Project面板Holograms文件夹下的Spatial Mapping拖到Hierarchy的空白处
- 点击Hierarchy面板中的Spatial Mapping物体, 在Inspector面板中勾选Draw Visual Meshes
- build一下看看效果(注意:Capabilities中的Spatial Perception需要勾选)
模拟器中加载房间效果如下图所示:
效果如下如图
房间的三维信息会以线框渲染,试着让小球滚落掉地上! **接下来让你可以将OrigamiCollection移到一个新的位置:** 1. 在Scripts文件夹下创建TapToPlaceParent脚本,并将下面的代码替换
using UnityEngine;
public class TapToPlaceParent : MonoBehaviour
{
bool placing = false;
// GazeGestureManager检测到用户的Select手势时调用
void OnSelect()
{
// 检测到Select手势时,切换用户是否在放置模式
placing = !placing;
// 如果在放置模式,显示空间映射网格
if (placing)
{
SpatialMapping.Instance.DrawVisualMeshes = true;
}
// 如果没在放置模式,隐藏空间映射网格
else
{
SpatialMapping.Instance.DrawVisualMeshes = false;
}
}
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;
}
}
}
}
- 将TapToPlaceParent脚本拖到OrigamiCollection物体下的Stage物体
- build来测试下吧!
- 可以切换不同的房间来试下效果
交流群:492325637
洪流学堂,让你快人几步
今天的文章hololens开发教程_hololens属于什么技术[通俗易懂]分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/85440.html