第3章VR控制器 VR控制器是真实世界与虚拟世界交互的媒介,VR应用开发者需要了解VR控制器本身的状态以及VR使用者在VR控制器所进行的操作。本章讨论如何获取控制器的信息以及用户在控制器上的操作。 在Unity编辑器的Hierarchy视图中右击,在弹出的上下文菜单中选择XR→UI Canvas选项,在场景中建立UI画布,如图31所示,该UI画布将用于显示来自VR控制器的信息。 图31选择XR→UI Canvas选项 调整所建立的UI Canvas的属性,使其能够出现在VR场景中头显的前方,如图32所示。 图32调整UI Canvas的属性 在Hierarchy视图中右击,在弹出的上下文菜单中选择UI→Text选项,如图33所示,在UI画布上建立文本标签。 图33选择UI→Text选项 该游戏对象Text的名称为textGetTrigger,并给其添加可视化脚本组件,指定宏名称为UIGetControllerInfo,该宏将获取控制器上的扳机键是否被按下,如图34所示。 图34指定宏名称为UIGetControllerInfo 视频讲解 3.1获取控制器特定按键是否按下 Unity 的XR 平台具有多种输入功能,可以在设计用户交互时加以利用。应用程序可以使用某些特定数据,这些数据引用位置、旋转、触摸、按钮、游戏杆和手指传感器。但是,在不同平台之间访问这些输入功能可能有很大差别。 Unity 提供了InputFeatureUsage 的结构,该结构定义了一组标准的物理设备控件(例如grip按钮和扳机键),以访问任何平台上的用户输入。这些控件可通过名称识别输入类型。每个 InputFeatureUsage 对应一个常见的输入操作或类型,例如,Unity 将名为 trigger 的 InputFeatureUsage 定义为食指控制的单轴扳机键输入。无论使用哪种 XR 平台,都可以使用 InputFeatureUsage 通过名称来获取 trigger 状态,因此无须为常规 Unity 输入系统设置一个轴(或某些 XR 平台上的按钮)。 表31列出了标准控制器InputFeatureUsage的名称与常见 XR 系统的控制器按键之间的映射关系。 表31InputFeatureUsage的名称与常见XR系统的控制器按键之间的映射关系 InputFeatureUsage的名称功能类型控制器对应按键 primary2DAxis2D 轴触控板/游戏杆 trigger单轴扳机 grip单轴握把 secondary2DAxis2D 轴 secondary2DAxisClick按钮 primaryButton按钮主要 primaryTouch按钮 secondaryButton按钮备用 secondaryTouch按钮 gripButton按钮握把按下 triggerButton按钮扳机按下 menuButton按钮 primary2DAxisClick按钮触控板/游戏杆按下 primary2DAxisTouch按钮触控板/游戏杆触控 InputDevice 代表任何物理设备,例如控制器或头盔,它可以包含有关设备跟踪、按钮、游戏杆和其他输入控件的信息。使用 XR.InputDevices 类可访问当前连接到 XR 系统的输入设备。在 XR 系统断开与输入设备的连接之前,输入设备将跨帧保持有效。使用 InputDevice.IsValid 属性来确定 InputDevice 是否仍代表激活的控制器。可以通过特征、角色以及XR 节点的方式访问输入设备。 设备特征描述了设备的功能或用途。InputDeviceCharacteristics是一系列标志,可以添加到代码中,用于搜索符合特定规格的设备。可以按表32所示的特征筛选设备。 表32设备特征表 设备特征 HeadMounted设备连接到用户的头部。它具有设备跟踪和眼球中心跟踪功能。此标志常用于标识头戴式显示器 (HMD) Camera设备具有摄像机跟踪功能 HeldInHand用户将设备握在手中 HandTracking设备代表物理跟踪的手。它具有设备跟踪功能,并且可能包含手和骨骼数据 EyeTracking设备可以执行眼球跟踪并具有EyesData 功能 TrackedDevice可以在 3D 空间中跟踪设备,具有设备跟踪功能 Controller设备具有按钮和轴的输入数据,并且可以用作控制器 TrackingReference设备代表静态跟踪参考对象,具有设备跟踪功能,但该跟踪数据不应该更改 Left将此特征与HeldInHand 或 HandTracking 特征组合使用,可以将设备标识为与左手关联 Right将此特征与HeldInHand 或 HandTracking 特征组合使用,可以将设备标识为与右手关联 Simulated6DOF设备报告 6DOF 数据,但仅具有 3DOF 传感器。Unity 负责模拟位置数据 底层 XR SDK 会报告这些特征,可以使用InputDevice.Characteristics查找这些特征。设备通常具有多个特征,可以使用位标志来筛选和访问这些特征。 设备角色描述输入设备的一般功能,可使用InputDeviceRole枚举来指定设备角色,如表33所示。 表33设备角色 角色描述 GameController游戏主机风格的游戏控制器 Generic代表核心 XR 设备的设备,例如头戴式显示器或移动设备 HardwareTracker跟踪设备 LeftHanded与用户左手关联的设备 RightHanded与用户右手关联的设备 TrackingReference跟踪其他设备的设备,如 Oculus 跟踪摄像机 底层 XR SDK 会报告这些角色,但是不同的提供商可能会以不同的方式组织他们的设备角色。此外,用户可以换手,因此角色分配结果可能与用户握住输入设备的手不匹配。XR 节点表示 XR 系统中的物理参考点(例如,用户的头部位置、左右手之类的跟踪参考)。XRNode枚举定义的节点如表34所示。 表34XRNode 枚举定义的节点 XR节点描述 CenterEye用户两个瞳孔之间的中点 GameController游戏主机风格的游戏控制器。用户的应用程序可以有多个游戏控制器设备 HardwareTracker硬件跟踪设备,通常连接到用户或物理项。可以存在多个硬件跟踪器节点 Head由 XR 系统计算出的用户头部的中心点 LeftEye用户的左眼 续表 XR节点描述 LeftHand用户的左手 RightEye用户的右眼 RightHand用户的右手 TrackingReference跟踪参考点,例如 Oculus 摄像机。可以存在多个跟踪参考节点 可以从特定的InputDevice读取输入功能,例如扳机键的状态。例如,要读取右扳机键的状态,应按照下列步骤操作。 (1) 使用InputDeviceRole.RightHanded或XRNode.RightHand获取惯用右手设备的实例。 (2) 有了正确的设备后,使用InputDevice.TryGetFeatureValue方法访问当前状态。TryGetFeatureValue()尝试访问功能的当前值,并根据情况返回不同的值。 ① 如果成功获取指定的功能值,则返回 true。 ② 如果当前设备不支持指定的功能,或者该设备无效(即控制器不再处于激活状态),则返回 false。 要获取特定的按钮、触摸输入或游戏杆轴值,应使用CommonUsages类。CommonUsages类包括XR 输入映射表中的每个InputFeatureUsage,以及诸如位置和旋转之类的跟踪功能。CommonUsages定义用于从 XR.InputDevice.TryGetFeatureValue 中获取输入功能的静态变量。使用这些静态变量可按使用情况获取 XR.InputDevice 的常用功能值,如表35所示。 表35XR.InputDevice 的常用功能值 功能值作用 batteryLevel表示设备的当前电池续航时间的值 centerEyeAcceleration设备以眼睛为中心的加速度 centerEyeAngularAcceleration设备以眼睛为中心的角加速度,采用欧拉角的格式 centerEyeAngularVelocity设备以眼睛为中心的角速度,采用欧拉角的格式 centerEyePosition设备以眼睛为中心的位置 centerEyeRotation设备以眼睛为中心的旋转 centerEyeVelocity设备以眼睛为中心的速度 colorCameraAcceleration设备彩色摄像机的加速度 colorCameraAngularAcceleration设备彩色摄像机的角加速度,采用欧拉角的格式 colorCameraAngularVelocity设备彩色摄像机的角速度,采用欧拉角的格式 colorCameraPosition设备彩色摄像机的位置 colorCameraRotation设备彩色摄像机的旋转 colorCameraVelocity设备彩色摄像机的速度 deviceAcceleration设备的加速度 deviceAngularAcceleration设备的角加速度,采用欧拉角的格式 deviceAngularVelocity设备的角速度,采用欧拉角的格式 devicePosition设备的位置 deviceRotation设备的旋转 续表 功能值作用 deviceVelocity设备的速度 eyesData包含从设备中收集的眼睛跟踪数据的眼睛结构 grip表示控制器上的用户手柄 gripButton表示设备是否被握住的二进制测量值 handData表示设备的手柄数据的值 isTracked告知开发人员当前是否在跟踪设备 leftEyeAcceleration设备左眼的加速度 leftEyeAngularAcceleration设备左眼的角加速度,采用欧拉角的格式 leftEyeAngularVelocity设备左眼的角速度,采用欧拉角的格式 leftEyePosition设备左眼的位置 leftEyeRotation设备左眼的旋转 leftEyeVelocity设备左眼的速度 menuButton表示菜单按钮,用于暂停、返回或退出游戏 primary2DAxis设备上的主触控板或游戏杆 primary2DAxisClick表示被单击或按下的主 2D 轴 primary2DAxisTouch表示被触摸的主 2D 轴 primaryButton在设备上被按下的主要面按钮或唯一按钮(如果只有一个按钮可用) primaryTouch设备上被触摸的主要触控板或者摇杆 rightEyeAcceleration设备右眼的加速度 rightEyeAngularAcceleration设备右眼的角加速度,采用欧拉角的格式 rightEyeAngularVelocity设备右眼的角速度,采用欧拉角的格式 rightEyePosition设备右眼的位置 rightEyeRotation设备右眼的旋转 rightEyeVelocity设备右眼的速度 secondary2DAxis设备上的辅助触控板或游戏杆 secondary2DAxisClick表示被单击或按下的辅助 2D 轴 secondary2DAxisTouch表示被触摸的辅助 2D 轴 secondaryButton设备上被按下的辅助面按钮 secondaryTouch设备上被触摸的辅助面按钮 trackingState表示此设备跟踪的值 trigger触发式控制,用食指按下 triggerButton表示食指是否正在激活扳机键的二进制测量值 userPresence使用此属性来测试用户当前是否佩戴XR设备和/或与之互动。该属性的确切行为因设备类型而异: 有些设备有一个专门检测用户接近度的传感器,但当该属性为UserPresenceState.Present时,可以合理地推断出用户与设备在一起 要在textGetTrigger 文本上显示右手控制器的扳机键是否按下,可视化脚本如图35所示。从右手控制器获取扳机键是否按下,如果按下则显示“Right Trigger Pressed!”,否则显示“Right Trigger Unpressed!”。 图35可视化脚本 视频讲解 3.2获取控制器特定按键按下程度 复制textGetTrigger 文本游戏对象,并改名为textGetTriggerValue,如图36所示。 在场景视图中将游戏对象textGetTriggerValue移动至游戏对象textGetTrigger上方,如图37所示。 图36改名为textGetTriggerValue 图37将textGetTriggerValue移动至 textGetTrigger上方 为textGetTriggerValue添加可视化脚本组件,指定宏名称为UIGetControllerTriggerValue,如图38所示。 图38为textGetTriggerValue添加可视化脚本组件 UIGetControllerTriggerValue宏的可视化脚本如图39所示。Unity会根据扳机键按下的幅度返回[0,1]的浮点数,0代表没有按下,1代表完全按下。如果稍稍按下扳机键,在textGetTriggerValue上便会显示出相应的浮点数值。 图39UIGetControllerTriggerValue宏的可视化脚本 视频讲解 3.3获取控制器触控板的输入 复制textGetTriggerValue 文本游戏对象,并改名为textGetTouchPad,如图310所示。 为textGetTouchPad添加可视化脚本组件,指定宏名为UIGetControllerTouchPad,如图311所示。 图310改名为textGetTouchPad 图311指定宏名为UIGetControllerTouchPad UIGetControllerTouchPad宏的可视化脚本如图312所示。Unity会根据手指在触控板按下的位置返回二维矢量,x,y∈[-1,1]。当x=-1时,手指位于触控板的最左边缘或者摇杆已经位于最左边沿; 当x=1时,手指位于触控板的最右边缘或者摇杆已经位于最右边沿; 当y=-1时,手指位于触控板的最上边缘或者摇杆已经位于最上边沿; 当y=1时,手指位于触控板的最下边缘或者摇杆已经位于最下边沿。 图312UIGetControllerTouchPad宏的可视化脚本 视频讲解 3.4获取控制器的位置信息 复制textGetTouchPad 文本游戏对象,并改名为textGetPosition,如图313所示。 图313改名为textGetPosition 为textGetPosition添加可视化脚本组件,指定宏名为UIGetControllerPos,如图314所示。 图314指定宏名为UIGetControllerPos UIGetControllerPos宏的可视化脚本如图315所示。Unity会获取右手控制器相对XR Rig原点的位置,并显示该位置的变化。 图315UIGetControllerPos宏的可视化脚本 视频讲解 3.5定制虚拟手 目前VR环境中仅用两根红线表示控制器,可以使用定制的虚拟手来代表对应的控制器,这样对用户来说更加直观一些。在Hierarchy视图中右击,在弹出的上下文菜单中选择XR→Devicebased→Direct Interactor选项,如图316所示,建立名为VRLeft的游戏对象。 图316选择XR→Devicebased→Direct Interactor选项 设定VRLeft游戏对象的XR Controller组件的Controller Node为Left Hand,如图317所示。 图317XR Controller组件 设定VRLeft游戏对象的XR Direct Interactor组件的Interaction Layer Mask为Nothing,如图318所示。 图318XR Direct Interactor组件 在Hierarchy视图中选择VRLeft游戏对象,按Ctrl+D组合键复制并重命名为VRRight,如图319所示。 设定VRRight游戏对象的XR Controller组件的Controller Node为Right Hand,如图320所示。 图319重命名为VRRight 图320XR Controller组件 在Unity编辑器的菜单栏中选择Assets→Import Package→Custom Package选项,如图321所示。 图321选择Assets→Import Package→Custom Package选项 在Import package对话框中选择hand.unitypackage图标,如图322所示。hand.unitypackage文件中含有虚拟手的所有相关资源。 图322选择hand.unitypackage图标 导入hand.unitypackage文件后,在Project视图中单击Oculus Hands文件夹下AnimationControler目录下的LefthandAC动画控制器,如图323所示。 图323LefthandAC动画控制器 LefthandAC动画控制器含有两个浮点参数,分别为Grip和Trigger,并含有一个Blend Tree状态,如图324所示。 图324LefthandAC动画控制器的参数 在Blend Tree中,Blend Type为2D Freeform Cartesian,Montion域含有4个动画片段,如图325所示。动画片段Take 001 代表手掌摊开状态,动画片段L_hand_pinch_anim代表拇指和食指处于捏合状态,而动画片段l_hand_fist代表手掌处于握拳状态,右手动画控制器RighthandAC也是类似的设定。 图325Blend Tree 将Assets中的Oculus Hands目录下的Models中的l_hand_skeletal_lowres放置到VRLeft下成为VRLeft的子游戏对象,将Assets中的Oculus Hands目录下的Models中的r_hand_skeletal_lowres放置到VRRight下成为VRRight的子游戏对象,放置后的结果如图326所示。 为l_hand_skeletal_lowres游戏对象的Animator组件的Controller指定LefthandAC为其动画控制器,如图327所示。 图326放置后的结果 图327Animator组件(一) 为r_hand_skeletal_lowres游戏对象的Animator组件的Controller指定RighthandAC为其动画控制器,如图328所示。 图328Animator组件(二) 选择Assets目录下的Oculus Hands目录下的Materials目录下的Hands_solid材质,如图329所示。 把Hands_solid材质的Shader更改为Wave/Essence/Hand/Model,如图330所示。 图329Hands_solid材质 图330Hands_solid材质的Shader 为r_hand_skeletal_lowres游戏对象增加宏名为AniRightHand的可视化脚本组件,如图331所示。 图331宏名为AniRightHand的可视化组件 AniRightHand宏的可视化脚本如图332所示。在循环事件中检查右手控制器的握把键是否按下,如果按下设定动画控制器的Grip参数为1,否则为0。检查右手控制器的扳机键的按下幅度值,如果按下,则设定动画控制器的Trigger参数为该幅度值,否则为0。 图332AniRightHand宏的可视化脚本 为l_hand_skeletal_lowres游戏对象增加宏名为AniLeftHand的可视化脚本组件,如图333所示。 图333宏名为AniLeftHand的可视化组件 AniLeftHand宏的可视化脚本如图334所示。在循环事件中检查左手控制器的握把键是否按下,如果按下设定动画控制器的Grip参数为1,否则为0。检查左手控制器的扳机键的按下幅度值,如果按下,则设定动画控制器的Trigger参数为该幅度值,否则为0。 图334AniLeftHand宏的可视化脚本