HTC Vive开发学习——VRTK(2)

上一节主要剖析了VRTK_ControllerEvents脚本,这一节我们继续通过Demo来学习VRTK。

(二)003_Controller_SimplePointer

上一节关于这个Demo的学习主要还是深入剖析VRTK_ControllerEvents脚本。这一节我们正式开始学习这个脚本,它的继承包括两个层次,父类:VRTK_WorldPointer,祖类:VRTK_DestinationMarker。它们都是抽象类,因此想实现自己的Pointer必须通过继承上述父类进行扩展。这个系列的脚本我看了一遍还是没有什么头绪,下面一点一点地来整理吧。

首先是各种public属性

Play Area Cursor:看脚本的时候就对这个名称的字段和方法很迷惑。它并不是当射线与一个碰撞体接触并引发碰撞检测时,射线头部出现的“鼠标”(从内部代码来看这个“鼠标”是通过一个Cube来模拟的,而且这个“鼠标”有它自己的相关属性Show Pointer Tip),而是这个Cursor位置将会出现一个Play Area(被冠以Play Area前缀)预览。一旦启用teleport功能,用户的Play Area将会传送到这个所谓Play Area Cursor的位置。这个共有属性和后面的Handle Play Area Cursor Collisions密切相关,如果进行Play Area碰撞检测,那么会进行是否发生传送操作的判断。

其次,我们来看实现

祖类VRTK_DestinationMarker中定义了三种事件,分别为射线终点进入、退出、设置。进入和退出很好理解,标记设置和teleport有关(后续我们讨论teleport是怎么实现的)。三种事件的触发方法在父类VRTK_WorldPointer中又进行了一步封装,最终封装为了在子类VRTK_SimplePointer中调用的PointerIn/Out/Set。子类的工作主要包括两方面:

(1)重写InitPointer虚方法。不知道在干嘛…貌似父类和祖类都没有实例化这条射线,而是将这个pointer的实现方式抽象出来,这个代码暂时有太多看不懂了…不过并不影响使用SimplePointer,因此留到后面慢慢学习。

(2)在Update中判断逻辑,添加射线并根据射线碰撞检测结果发布相应事件消息(上述三种)。这部分逻辑就很好理解了,之后为了实现我们自己的事件响应,需要参考VRTK_ControllerPointerEvents_ListenerExample脚本。这种基于事件的解耦方式看起来蛮好的,貌似如果想直接用的话只需要自己写事件响应方法就可以了。

这里顺便学一下射线碰撞检测的使用方法。首先有,射线类型Ray(这是一个struct!),它表示一条无线长的射线,由原点origin和方向direction两个构造参数描述(和几何意义上的射线相同)。其次有RaycastHit类型,它描述了射线和一个Collider碰撞发生时的一些信息。最终,我们通过Physics.Raycast()方法发出一条射线,它的参数总体来说包括上述两种类型。它返回一个bool型值,作为是否发生Collider碰撞的标志位,用于逻辑判断。脚本内部代码在Update中动态创建射线,并检测是否发生Collider碰撞,之后通过RaycastHit类型传递碰撞目标,更新对应的基类成员,并发布消息触发对应的事件。

总结一下,目前为止仅仅涉及了一点原理以及如何快速重用VRTK_SimplePointer脚本。需要做的工作有两个:第一,给需要交互的物体加上Collider组件;第二,编写自己的Listener脚本。

(三)004_CameraRig_BasicTeleport

这个脚本的功能是实现VR环境中的“移动”,即所谓的传送功能(Teleport)。它的实现基于我们前两节涉及到的两个核心脚本,通过SimplePointer发射一条射线并进行碰撞检测,通过触发DestinationMarkerSet事件进行传送。

这里想探究一下发生传送操作的逻辑。阅读相关代码后,我在这里将该功能的逻辑及事件触发流程详细地整理出来,以便更好地理解这几个核心脚本的使用方式。(为了文字上的简略,以下过程涉及到的脚本名称,均忽略VRTK_前缀。如:VRTK_WorldPointer简写成WorldPointer)

首先,BasicTeleport脚本Start()方法中为DestinationMarkerSet事件添加了DoTeleport的监听器,后者实现了传送操作。因此,当DestiantionMarkerSet事件触发时,传送才有可能发生。

其次,我们来理一下交互逻辑。在WorldPointer脚本中为AliasPointerOff事件添加了DisablePointerBeam侦听器;而子类SimplePointer脚本中重写了DisablePointerBeam方法,它内部先调用了WorldPointer.PointerSet()方法,触发了DestinationMarkerSet事件,从而尝试**传送操作。传送功能比较完整的调用栈如图:

HTC Vive开发学习——VRTK(2)


参考文献:

1. Github:https://github.com/giveaphuk/SteamVR_Unity_Toolkit

2. 《Htc Vive VR游戏开发实战》