文章目录
Chapter 1 —— “Holo” world
设置相机
- Main Camera位置设置(0,0,0);
- Clear Flags > Solid Color,Background > RGB(0,0,0,0);
设置场景
- 创建空GameObject命名为OrigamiCollection位置设置成(0,-0.5,2.0);
- 将Holgrams文件夹内的Stage,Sphere1,Sphere2预制体拖放到OrigamiCollection子级中;
- 删除Directional Lights,将Holgrams文件夹内的Lights拖放到层次结构视图中;
- 在PayerSettings中Capabilities > Microphone和SpatialPerception功能;
Chapter 1 —— Gaze 凝视
Objectives 目标
- 使用锁定的光标可视用户视线;
Instructions 说明
- 将Holgrams文件夹内的Cursor预制体拖放层次结构视图根目录中;
- 给Cursor对象添加C#脚本,命名为WorldCursor
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WorldCursor : MonoBehaviour
{
private MeshRenderer meshRenderer;
// Use this for initialization
void Start()
{
meshRenderer = this.GetComponentInChildren<MeshRenderer>();
}
// Update is called once per frame
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;
}
}
}
Chapter 1 —— Gestures 手势
Objectives 目标
- 利用手势控制全息图;
Instructions 说明
- 选中OrigamiCollection对象添加C#脚本,命名为GazeGestureManager;
using System.Collections;
using System.Collections.Generic;
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;
// Use this for initialization
void Start()
{
Instance = this;
//手势识别
recognizer = new GestureRecognizer();
//定阅手势按下之后的事件
recognizer.Tapped += (args) =>
{
if (FocusedObject != null)
{
//向聚焦的游戏对象发送OnSelect函数消息;
FocusedObject.SendMessageUpwards("OnSelect", SendMessageOptions.DontRequireReceiver);
}
};
recognizer.StartCapturingGestures();
}
// Update is called once per frame
void Update()
{
GameObject oldFocusObject = FocusedObject;
var headPostion = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;
if (Physics.Raycast(headPostion, gazeDirection, out hitInfo))
{
//当射线击中的游戏对象作为聚焦对象;
FocusedObject = hitInfo.collider.gameObject;
}
else
{
FocusedObject = null;
}
//如果聚焦对象改变了那么重新开始检测新的手势;
if (FocusedObject != oldFocusObject)
{
recognizer.CancelGestures();
recognizer.StartCapturingGestures();
}
}
}
- 另外,新建脚本SphereCommands,展开OrigamiCollection对象,选中Sphere1和Sphere2将SphereCommands脚本拖放到选中对象上;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SphereCommands : MonoBehaviour
{
//当用户执行选择手势时,将会由GazeGestureManager调用
void OnSelect()
{
//如果该对象没有刚体,则添加一个并启用物理特性;
if(!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
}
Chapter 1 —— Voice 声音
Objectives 目标
使用“Reset world”将前面掉落的球体返回他的原始位置,然后使用“Drop sphere”使球体落下;
- 添加后台侦听的语音命令;
- 创建一个对语音命令有反应的全息图;
Instructions 说明
- 创建名为SpeechManager的脚本并挂载到OrigamiCollection对象上;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Windows.Speech;
public class SpeechManager : MonoBehaviour
{
/// <summary>
/// 单一关键词识别,关键词识别对象
/// </summary>
KeywordRecognizer keywordRecognizer = null;
/// <summary>
/// 存放关键词的字典
/// </summary>
Dictionary<string, System.Action> keywords = new Dictionary<string, System.Action>();
// Use this for initialization
void Start()
{
//向字典中添加关键词,key为关键词,value为一个匿名action
keywords.Add("Reset world", () =>
{
this.BroadcastMessage("OnReset");
});
keywords.Add("Drop Sphere", () =>
{
//获取注视的物体
var focusObject = GazeGestureManager.Instance.FocusedObject;
//说了Drop Sphere关键词,并且存在注视物体,则发送消息到OnDrop函数;
if (focusObject!=null)
{
focusObject.SendMessage("OnDrop", SendMessageOptions.DontRequireReceiver);
}
});
//初始化识别对象;
keywordRecognizer = new KeywordRecognizer(keywords.Keys.ToArray());
//添加关键词代理事件;
keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
//开始执行监听,非常重要;
keywordRecognizer.Start();
}
private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
{
System.Action keywordAction;
//如果识别到的关键字在字典中,则调用该action;
if(keywords.TryGetValue(args.text,out keywordAction))
{
keywordAction.Invoke();
}
}
}
- 打开挂载到Sphere1和Sphere2的脚本SphereCommands;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SphereCommands : MonoBehaviour
{
Vector3 originalPosion;
private void Start()
{
originalPosion = this.transform.localPosition;
}
//当用户执行选择手势时,将会由GazeGestureManager调用
void OnSelect()
{
//如果该对象没有刚体,则添加一个并启用物理特性;
if(!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
void OnReset()
{
var rigidbody = this.GetComponent<Rigidbody>();
if(rigidbody!=null)
{
//是否为刚体,如果启用该参数,对象不会被物理控制,只能通过直接设置位置,旋转和缩放来控制它;
rigidbody.isKinematic = true;
Destroy(rigidbody);
}
this.transform.localPosition = originalPosion;
}
void OnDrop()
{
OnSelect();
}
}
Chapter 1 —— Spatial sound 空间声音
给应用程序添加音乐,并且在某些操作上触发音效,在3D空间中我们将使用Spatial Sound在特定的位置播放声音;
Objectives
- Hear holograms in your world;
Instructions
- Editor > Project Settings >Audio;
- 找到Spatializer Plugin设置选择MS HRTF Spatializer;
- 从Holograms文件夹中将Ambience拖放到层次结构视图中的OrigamiCollection对象上,会自动挂上AudioSource并且AudioClip属性为Ambience;
- 选中OrigamiCollection并找到AudioSource,需要修改一些属性;
- 选中Spatialize属性;
- 选中Play On Awake;
- Spatial Blend将滑块拖到右侧,拖动到3D;
- 选中Loop属性;
- 展开3D Sound Settings,将Doppler Level输入为0.1;
- 将Volume Rolloff设置为Logarithmic Rolloff;
- 将Max Distance设置为20;
- 设置完成后,创建一个名为SphereSounds的脚本,并把脚本分别挂到Sphere1和Sphere2上;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SphereSounds : MonoBehaviour
{
AudioSource impactAudioSource = null;
AudioSource rollingAudioSource = null;
bool rolling = false;
// Use this for initialization
void Start()
{
//为音频设置属性;
impactAudioSource = this.gameObject.AddComponent<AudioSource>();
impactAudioSource.playOnAwake = false;
impactAudioSource.spatialize = true;
impactAudioSource.spatialBlend = 1.0f;
impactAudioSource.loop = true;
impactAudioSource.dopplerLevel = 0.0f;
impactAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
impactAudioSource.maxDistance = 20f;
rollingAudioSource = this.gameObject.AddComponent<AudioSource>();
rollingAudioSource.playOnAwake = false;
rollingAudioSource.spatialize = true;
rollingAudioSource.spatialBlend = 1.0f;
rollingAudioSource.loop = true;
rollingAudioSource.dopplerLevel = 0.0f;
rollingAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
rollingAudioSource.maxDistance = 20f;
为音频添加音频片段;
impactAudioSource.clip = Resources.Load<AudioClip>("Impact");
rollingAudioSource.clip = Resources.Load<AudioClip>("Rolling");
}
private void OnCollisionEnter(Collision collision)
{
if (collision.relativeVelocity.magnitude >= 0.1f)
{
impactAudioSource.Play();
}
}
private void OnCollisionExit(Collision collision)
{
Rigidbody rigidbody = gameObject.GetComponent<Rigidbody>();
//rigidbody.velocity刚体的线性速度,可以理解为给物体一个初速度;
//rigidbody.velocity.magnitude
if (!rolling && rigidbody.velocity.magnitude >= 0.01f)
{
rolling = true;
rollingAudioSource.Play();
}
else if (rolling && rigidbody.velocity.magnitude < 0.01f)
{
rolling = false;
rollingAudioSource.Stop();
}
}
private void OnCollisionStay(Collision collision)
{
if (rolling == false)
{
rolling = false;
impactAudioSource.Stop();
rollingAudioSource.Stop();
}
}
}
Chapter 1 —— Spatial mapping 空间映射
使用Spatial mapping实现将游戏板旋转到现实世界的真实物体上;
Objectives 目标
- 将真实世界带入虚拟世界;
- 将全息放置你任意想放置的地方;
Instructions 说明
- 将Holograms文件中的Spatial Mapping预制体拖放到层次结构视图根目录中;
- 修改Spatial Mapping的属性;
- 选中Draw Visual Meshes;
- Draw Material属性可以改变绘制的材质;比如当前设置的为Wireframe,那么当绘制当前世界的时候将使用Wireframe材质;
接下来,需要展示的是如何将游戏对象OrigamiCollection移到另一个新的位置上;
- 创建一个名为TapToPlaceParent的脚本,并把脚本分别挂到OrigamiCollection的子级Stage上;
注:在GazeGestureManager.cs脚本中有定阅手势按下之后的事件向OnSelect函数发送消息;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TapToPlaceParent : MonoBehaviour
{
bool placing = false;
private void OnSelect()
{
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;
//这条射线只SpatialMapping.PhysicsRaycastMask层上会返回焦点
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;
}
}
}
}
Chapter 1 —— Holographic fun
Objectives 目标
将Stage放置在地板上,然后使用手势将球体落下,撞击到Target的时候,会发生爆炸,Target会隐藏,并会出现一个洞,可以看见另一个世界;
Instructions 说明
- 将Holograms文件中的Underwrold预制体拖放到层次结构视图中作为OrigaminCollection的子级;
- 创建名为HitTarget的脚本,挂载到OrigamiCollection - Stage - Target中;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HitTarget : MonoBehaviour
{
public GameObject underworld;
public GameObject objectToHide;
private void OnCollisionEnter(Collision collision)
{
objectToHide.SetActive(false);
underworld.SetActive(true);
SpatialMapping.Instance.MappingEnabled = false;
}
}
- 并将Stage拖入到HotTarget的ObjectToHide属性中,将Underworld拖入到HotTarget的Underworld属性中;