原文地址:http://blog.csdn.net/sherlockchang/article/details/51336901html
概述web
在VR开发中,咱们常常须要激活一个用户正在盯着的物体。咱们准备了一个示例,一个简单的可扩展的轻量级的系统,让用户和物体交互。这个系统由三个主要的脚本组成,VREyeRaycaster, VRInput, 和VRInteractiveItem - 下面有一些简短的说明和项目的源码。app
VREyeRaycaster编辑器

这个脚本须要放在主摄像机上,每一次调用脚本的Update()向前发射一条射线, 调用Physics.Raycast来检测是否是击中了任何碰撞盒。这个功能能够设置为排除特定的layer(层),根据场景,可能须要把全部互动的物体移动到另外一个层来作显示效果。ide
若是碰撞盒被射线集中,这个脚本会尝试在目标对象上查找VRInteractiveItem 控件:oop
- VRInteractiveItem interactible = hit.collider.GetComponent<VRInteractiveItem>();
经过这个咱们能够知道用户是否正在看着某个物体,或者中止查看某个物体,若是用户开始查看或者中止查看某个物体,咱们能够作一些响应,例如调用某个方法。布局
VRInput测试
VRinput是个简单的类,它用来兼容动做发生的来源,是使用GearVR上触发的 swipes, taps, double-taps事件,仍是使用DK2时,专为PC准备的输入事件。动画
能够在VRInput 中直接注册事件监听:this
- public event Action<SwipeDirection> OnSwipe;
- public event Action OnClick;
- public event Action OnDown;
- public event Action OnUp;
- public event Action OnDoubleClick;
- public event Action OnCancel;
关于注册事件监听的更多信息,请查看咱们的事件教程
VRInteractiveItem
任何想在VR中进行互动的物体均可以添加这个组件。须要物体上有碰撞盒。
这里有6个能够进行监听的事件:
- public event Action OnOver;
- public event Action OnOut;
- public event Action OnClick;
- public event Action OnDoubleClick;
- public event Action OnUp;
- public event Action OnDown;
添加一个boolean变量,用来查看如今是否是在Over( 悬停)的状态:
- public bool IsOver
- {
- get{ return m_IsOver; }
你能够建立本身的脚原本响应这些事件,这里有一个很是简单的例子,用到了这里的一些事件:
- using UnityEngine;
- using VRStandardAssets.Utils;
-
- namespace VRStandardAssets.Examples
- {
-
-
- public class ExampleInteractiveItem : MonoBehaviour
- {
- [SerializeField] private Material m_NormalMaterial;
- [SerializeField] private Material m_OverMaterial;
- [SerializeField] private Material m_ClickedMaterial;
- [SerializeField] private Material m_DoubleClickedMaterial;
- [SerializeField] private VRInteractiveItem m_InteractiveItem;
- [SerializeField] private Renderer m_Renderer;
-
-
- private void Awake ()
- {
- m_Renderer.material = m_NormalMaterial;
- }
-
-
- private void OnEnable()
- {
- m_InteractiveItem.OnOver += HandleOver;
- m_InteractiveItem.OnOut += HandleOut;
- m_InteractiveItem.OnClick += HandleClick;
- m_InteractiveItem.OnDoubleClick += HandleDoubleClick;
- }
-
-
- private void OnDisable()
- {
- m_InteractiveItem.OnOver -= HandleOver;
- m_InteractiveItem.OnOut -= HandleOut;
- m_InteractiveItem.OnClick -= HandleClick;
- m_InteractiveItem.OnDoubleClick -= HandleDoubleClick;
- }
-
-
-
- private void HandleOver()
- {
- Debug.Log("Show over state");
- m_Renderer.material = m_OverMaterial;
- }
-
-
-
- private void HandleOut()
- {
- Debug.Log("Show out state");
- m_Renderer.material = m_NormalMaterial;
- }
-
-
-
- private void HandleClick()
- {
- Debug.Log("Show click state");
- m_Renderer.material = m_ClickedMaterial;
- }
-
-
-
- private void HandleDoubleClick()
- {
- Debug.Log("Show double click");
- m_Renderer.material = m_DoubleClickedMaterial;
- }
- }
- }
要查看这个基础的示例的话,能够看一下VRSampleScenes/Scenes/Examples/ 中的InteractiveItem 场景
SelectionRadial(环形选择区) 和 SelectionSlider(滑块选择区)
(用来确认选项)
咱们用了一个圆形选择区,还有一个滑块选择区,玩家能够按住Fire1 来确认交互动做:
按住输入的按键, 选择条会填满, 而后发送OnSelectionComplete (选择完成)或者 OnBarFilled(读条失败)事件。这部分代码在SelectionRadial.cs和SelectionSlider.cs 文件中而且有详细的注释。对于VR来讲,站在用户体验角度考虑,用户知道他们在作什么并且可以随时把控整个各个方面。同时提供一个“held input (保持输入)”的确认模式,做为对当即响应事件的一些妥协。
VR示例场景中的一些互动示例
如今咱们来研究一下VR示例项目中的一些例子。咱们会讨论一下每一个场景的互动效果和它们的实现方法。
在菜单场景中的互动
每一个菜单画面都有几个组件。特别须要了解一下的是MenuButton, VRInteractiveItem, 和Mesh Collider。
MenuButton 组件监听了来自 VRInteractiveItem 组件的 OnOver 和 OnOut 事件, 当准星悬停在菜单画面上的时候, 环形选择块就会出现, 当视线离开menu item(菜单选项)时, 这个环形选择块会消失。当选择圈可见的时候,按下Fire1 键, 这个环形会逐渐填满:
这个类也监听了OnSelectionComplete 事件, 当环形圈填满的时候,HandleSelectionComplete 方法会被调用,它会让显示器淡出而后读取所选择的关卡。
- private void OnEnable ()
- {
- m_InteractiveItem.OnOver += HandleOver;
- m_InteractiveItem.OnOut += HandleOut;
- m_SelectionRadial.OnSelectionComplete += HandleSelectionComplete;
- }
-
-
- private void OnDisable ()
- {
- m_InteractiveItem.OnOver -= HandleOver;
- m_InteractiveItem.OnOut -= HandleOut;
- m_SelectionRadial.OnSelectionComplete -= HandleSelectionComplete;
- }
-
-
- private void HandleOver()
- {
-
- m_SelectionRadial.Show();
-
- m_GazeOver = true;
- }
-
-
- private void HandleOut()
- {
-
- m_SelectionRadial.Hide();
-
- m_GazeOver = false;
- }
-
-
- private void HandleSelectionComplete()
- {
-
- if(m_GazeOver)
- StartCoroutine (ActivateButton());
- }
-
-
- private IEnumerator ActivateButton()
- {
-
- if (m_CameraFade.IsFading)
- yield break;
-
-
- if (OnButtonSelected != null)
- OnButtonSelected(this);
-
-
- yield return StartCoroutine(m_CameraFade.BeginFadeOut(true));
-
-
- SceneManager.LoadScene(m_SceneToLoad, LoadSceneMode.Single);
- }
这里有一些关于Selection Radial的示例, 注意截图中央的粉色图形。
只有点状准星的:
空的选择确认圈, 由于如今正在看着menu 场景因此出现:
选择确认圈正在填充(用户正在看着菜单画面, 并且按住了 输入设备的 Fire1 键):
经过在研究示例项目中的进度条和环形试,着掌握这个模式。咱们建议你在本身的VR项目上考虑一下这个方法,这样能保证用户体验的一致性,也能帮助他们适应这个新的设备。
Maze Scene 中的交互
迷宫游戏提供了一个桌游模式的交互,在这里你须要用开关(挡板)来引导角色必考炮塔,到达终点。
为角色选择目的地的时候,会出现一个目的地标记,路上会出现一条路线,让角色沿着轨迹前进。在触控板上滑动,或者按下方向键,或者用手柄左摇杆,能够旋转画面。
MazeFloor 对象 有一个MeshCollider 组件和一个VRInteractiveItem 组件, 让它能在VR中进行交互:
MazeCourse 物体 包含有MazeFloor 和MazeWalls ,包含着用做迷宫布局的几何体:
MazeCourse 物体添加了MazeTargetSetting脚本, 有一个VRInteractiveItem 的引用,指向MazeFloor物体上的VRInteractiveItem 组件:
MazeTargetSetting 监听了来自VRInteractiveItem 的 OnDoubleClick 事件, 而后它会发送OnTargetSet 事件。这个事件会把准星的Transform(空间坐标信息)看成变量一块儿传出:
- public event Action<Transform> OnTargetSet;
-
-
- private void OnEnable()
- {
- m_InteractiveItem.OnDoubleClick += HandleDoubleClick;
- }
-
-
- private void OnDisable()
- {
- m_InteractiveItem.OnDoubleClick -= HandleDoubleClick;
- }
-
-
- private void HandleDoubleClick()
- {
-
- if (m_Active && OnTargetSet != null)
- OnTargetSet (m_Reticle.ReticleTransform);
- }
MazeCharacter 物体上的 Player 组件和 MazeDestinationMarkerGUI 物体上的 DestinationMarker 组件都监听了OnTargetSet这个事件,而后相应的作出了反应。
角色使用了Nav Mesh systems(导航系统)在迷宫中寻路。Player 组件实现了HandleSetTarget方法,在方法中,把导航系统的目标点设置为准星的坐标, 而后更新它对玩家轨迹的渲染:
- private void HandleSetTarget(Transform target)
- {
-
- if (m_IsGameOver)
- return;
-
- m_AiCharacter.SetTarget(target.position);
- m_AgentTrail.SetDestination();
- }
DestinationMarker (目标点生成器)会把标记移动到准星Transform的位置:
- private void HandleTargetSet(Transform target)
- {
-
- Show();
-
-
- transform.position = target.position;
-
-
- m_MarkerMoveAudio.Play();
-
-
- m_Animator.Play(m_HashMazeNavMarkerAnimState, -1, 0.0f);
- }
你能够看到准星,目的地制做器,玩家,和轨迹:
迷宫中的开关也是一个在VR中和物体互动的例子。使用了一个Collider(碰撞盒),像VRInteractiveItem 和SelectionSlider 这两个类同样。
像以上演示的和其余物体的互动, SelectionSlider脚本监听了 VRInteractiveItem 和 VRInput 发出的事件:
- private void OnEnable ()
- {
- m_VRInput.OnDown += HandleDown;
- m_VRInput.OnUp += HandleUp;
-
- m_InteractiveItem.OnOver += HandleOver;
- m_InteractiveItem.OnOut += HandleOut;
- }
Flayer 场景中的交互
飞行器场景是一个无限飞行的游戏,玩家须要控制飞船在场景中飞行,使用Fire1 进行射击, 射击小行星,穿过空中的门,都能增长分数。玩法相似飞行俱乐部和星际火狐。
在交互方面,Flyer是一个更简单的案例, FlyerLaserController 监听了 VRInput 的O nDown 事件来发射激光:
- private void OnEnable()
- {
- m_VRInput.OnDown += HandleDown;
- }
-
-
- private void HandleDown()
- {
-
- if (!m_GameController.IsGameRunning)
- return;
-
-
- SpawnLaser(m_LaserSpawnPosLeft);
- SpawnLaser(m_LaserSpawnPosRight);
- }
Shooter180 和 Shooter360 场景中的交互(Target Gallery/Target Arena)
VR示例实现了两个射击游戏,180度走廊打靶射击游戏,和360度的竞技场射击游戏。
每一个在射击游戏里建立的靶子都有 Collider, VRInteractiveItem 和 ShootingTarget 组件。
ShootingTarget 组件监听了VRInteractiveItem发出的OnDown事件,来判断标靶是否是被射击。这个方法适合即时射击,若是要作一个击中数的模式,咱们须要另外一个方案实现。
如今你可能对 VR交互组件 有了一个大概认识,知道了这些组件是如何在VR 示例项目中使用的。如今咱们来讨论一下注视和准星是如何在咱们的VR示例中生效的。
注视
在VR中侦测玩家正在查看的方向是很重要的,关系到是否容许用户和物体互动,激活动画,或者对对目标发射子弹等等。咱们引用的这个动做,在VR中叫作“gaze”(注视), 并且,接下来咱们会在咱们的VR文章中,使用这个术语。
因为大多数头戴式显示设备还不支持眼球追踪,咱们如今只能估算玩家的注视。由于镜片的扭曲,这意味着玩家基本上是朝前看的,因此有一个简单的解决方案。就像在概述中提到的,咱们只须要在摄像机中心朝前发射一条射线,而后查找这条射线全部碰撞的物体。固然,这意味着任何物体能被碰撞(或者经过注视进行互动)的物体都要有碰撞盒。
准星
准星能够帮助指示出玩家的视野中心。准星能够是一个简单的点,或者是个交叉十字线,这要看项目的需求。
在传统的3D游戏中,准星一般被设置为空间中固定的点, 一般是屏幕的中心。 在VR中定位一个准星要更复杂:用户在VR场景中观察环境,视线会汇集到离摄像机比较近的物体上。若是准星有一个固定的位置, 用户会看到两个:能够把手指放在眼前而后看远处的物体来模拟一下这个感受, 若是你聚焦到你的手指,背景的物体看起来会变成两个, 反之一样。这被叫作voluntary Diplopia(自发性复视)。
要避免用户观察环境时和注视不一样距离的物体时看到两个准星,须要把准星放到物体表面正在被观察的点,而后根据摄像机到这个点的距离对它进行缩放。
有一些简单的例子来演示准星如何处理不一样距离和尺寸, 来自 Examples/Reticlescene (示例/准星场景)
准星被放置在一个物体上,靠近摄像机:
准星被放置在一个远处的物体上:
准星放置在远处的位置:
因为调整了位置和缩放, 准星在用户看来是同样的大小,也忽略的它到摄像机的距离。
当注视的射线上没有物体的时候,咱们简单的把准星放到一个预设的距离上。在一个护腕环境,这个被简单的放在摄像机的最远端裁剪面上,或者在室内场景的话,这个位置会变得更近。
越过其余物体渲染准星
若是准星被放置在了和物体一样的位置, 这个准星可能和其余附近的物体有重叠:
要解决这个问题,咱们须要保证准星渲染在全部其余物体之上。VR示例中提供了一个基于 “UI/Unlit/Text”的shader,叫作UIOverlay.shader。给材质选择shader的时候,能够在“UI/Overlay”下面找到它。
这个shader能够用在UI元素和文字,会在其它物体的最上方渲染:
调整准星到场景中的其余物体
最后,若是须要旋转准星来贴合到视线击中的物体上。咱们能够用RaycastHit.normal。这里演示如何在准星中设置:
- public void SetPosition (RaycastHit hit)
- {
- m_ReticleTransform.position = hit.point;
- m_ReticleTransform.localScale = m_OriginalScale * hit.distance;
-
-
- if (m_UseNormal)
-
- m_ReticleTransform.rotation = Quaternion.FromToRotation (Vector3.forward, hit.normal);
- else
-
- m_ReticleTransform.localRotation = m_OriginalRotation;
- }
能够在迷宫场景中看到这个动做。
下面是准星如何贴合到墙壁上:

准星贴合到地面:
咱们已经引入了一个示例准星脚本。 和 VREyeRaycaster 一块儿把准星定位到场景中,有选项控制是否贴合到目标物体表面。
以上全部都能在VRSampleScenes/Scenes/Examples/ 文件夹下的 Reticle scene 查看。
VR中头部的旋转和坐标
HMD(头戴式显示设备)旋转和坐标的明显的用途是用来观察环境,可是这两个变量,用来和环境互动也是能够的。
须要使用 VR.InputTracking class 这个类来获得这两个值,而后区分须要进入哪一个 VRNode(VR节点)。咱们须要使用VRNode.Head来获得头部的旋转。头部,而不是每只眼睛。关于这个的更多信息,能够参考Getting Started with VR Development这篇文章。
把旋转做为一种输入类型的示例,基于头部旋转来精确的旋转一个菜单或者其余物体。能够在这里找到这里示例,VRSampleScenes/Examples/Rotation scene, 如下示例脚本:
- var eulerRotation = transform.rotation.eulerAngles;
-
- eulerRotation.x = 0;
- eulerRotation.z = 0;
- eulerRotation.y = InputTracking.GetLocalRotation(VRNode.Head).eulerAngles.y;
能够看到物体会根据玩家查看的方向进行旋转:
在咱们的示例Flyer game(飞行器游戏)中,你能够看到FlyerMovementController中,基于头部的旋转调整飞船坐标:
- Quaternion headRotation = InputTracking.GetLocalRotation (VRNode.Head);
- m_TargetMarker.position = m_Camera.position + (headRotation * Vector3.forward) * m_DistanceFromCamera;

VR 游戏中 与触控板和键盘的交互
Gear VR 在设备侧面有触控板。对于Unity来讲,表现为出镖, 能够经过下面的方式调用:
Input.mousePosition
Input.GetMouseButtonDown
Input.GetMouseButtonUp
使用Gear VR时,可能也许须要获取触控板的滑动数据。咱们引入了一个VRInput 脚原本处理滑动,触屏点击,和触屏双击。也接受键盘输入中的方向键和左Ctrl键(Unity默认输入术语中的Fire1),来触发swipes(滑动)和taps(点击)。
在Unity编辑器中,因为如今没有办法测试Unity到GearVR的内容,可能用DK2来测试GearVR 的内容。因为Gear VR 触控板事件是做为鼠标事件运做的,咱们能够简单的使用鼠标来模拟输入。因为使用HMD(头戴式显示设备)时,定位键盘要更简单一点, 为了方便,VRInput也会接受方向键做为滑动事件,而后左Ctrl(Fire1)做为触屏点击事件(taps)。
对于手柄的基础操做,左摇杆的动做会做为华东,而后按下其中一个按钮会做为点击。
在VRSampleScenes/Scenes/Examples/Touchpad中有一个例子监听了swipes(滑动)事件。
一下是ExampleTouchpad 脚本, 按照滑动的方向,对刚体添加扭力,让物体可以旋转起来。
- using UnityEngine;
- using VRStandardAssets.Utils;
-
- namespace VRStandardAssets.Examples
- {
-
-
- public class ExampleTouchpad : MonoBehaviour
- {
- [SerializeField] private float m_Torque = 10f;
- [SerializeField] private VRInput m_VRInput;
- [SerializeField] private Rigidbody m_Rigidbody;
-
-
- private void OnEnable()
- {
- m_VRInput.OnSwipe += HandleSwipe;
- }
-
-
- private void OnDisable()
- {
- m_VRInput.OnSwipe -= HandleSwipe;
- }
-
-
-
- private void HandleSwipe(VRInput.SwipeDirection swipeDirection)
- {
- switch (swipeDirection)
- {
- case VRInput.SwipeDirection.NONE:
- break;
- case VRInput.SwipeDirection.UP:
- m_Rigidbody.AddTorque(Vector3.right * m_Torque);
- break;
- case VRInput.SwipeDirection.DOWN:
- m_Rigidbody.AddTorque(-Vector3.right * m_Torque);
- break;
- case VRInput.SwipeDirection.LEFT:
- m_Rigidbody.AddTorque(Vector3.up * m_Torque);
- break;
- case VRInput.SwipeDirection.RIGHT:
- m_Rigidbody.AddTorque(-Vector3.up * m_Torque);
- break;
- }
- }
- }
- }
VR示例中的VRInput例子
像上文提到的,全部咱们的示例游戏,都用VRInput来处理触控板和鼠标事件。在Maze(迷宫)中的摄像机也响应了滑动事件。
Maze(迷宫)
在这个场景中,摄像机轨道监听了swipes(滑动)事件,可以改变视角:
- private void OnEnable ()
- {
- m_VrInput.OnSwipe += HandleSwipe;
- }
-
-
- private void HandleSwipe(VRInput.SwipeDirection swipeDirection)
- {
-
- if (!m_MazeGameController.Playing)
- return;
-
- if (m_CameraFade.IsFading)
- return;
-
-
- switch (swipeDirection)
- {
- case VRInput.SwipeDirection.LEFT:
- StartCoroutine(RotateCamera(m_RotationIncrement));
- break;
-
- case VRInput.SwipeDirection.RIGHT:
- StartCoroutine(RotateCamera(-m_RotationIncrement));
- break;
- }
- }
关于为何场景的摄像机轨道而不是直接旋转这个迷宫自己,请查看 Movement article 这篇文章。
如今你对VR 示例场景中,基础VR交互的工做原理有了一个更好的认识。有不少方法去完成这个事情,可是这个方法学起来又快又简单。在下一篇文章里,咱们会讨论VR中不一样类型的用户界面。同时记得,打算向一样研究VR的朋友们提问关于VR的问题的话,你能够跳转到Unity官方论坛VR板块。
在迷宫场景中看到这个动做。
下面是准星如何贴合到墙壁上: