PicoXR中的手柄射线
前言
本文是继PicoXR中的输入事件博客的进一步补充,针对手柄的射线相关API进行解析,并以VR水果忍者为例子封装手柄射线的事件。
手柄射线的获取
- XRRayInteractor 关键脚本,由Unity XR提供的射线交互器脚本
- 有关此脚本详细的API请参考官方UnityXR的手册Class XRRayInteractor | XR Interaction Toolkit | 1.0.0-pre.8 (unity3d.com)
- 下面介绍一些较为常用的获取射线和获取击中点的方式
XRRayInteractor leftInteractor; //以左手的射线交互器为例
//省略获取脚本的方法,根据名称标签等根据项目而定
public void RayTest()
{
//接收out输出
RaycastHit rayInfo;
//获取左手当前的射线结果RaycastHit
leftInteractor.GetCurrentRaycastHit(out rayInfo);
//后续就可以通过rayInfo获取射线击中的碰撞体等等操作
if(rayInfo.collider != null){}
//还有一个方法,直接尝试获取各个射线击中的信息,不如上述方法方便
Vector3 position = new Vector3(); //击中点的坐标
Vector3 normal = new Vector3(); //击中点的法线向量
int positionInLine = 0;
bool isValidTarget = false; //是否是有效的目标 击中目标是否是可交互的
//尝试获取击中点的若干信息,若击中返回true 若未击中发回false
leftInteractor.TryGetHitInfo(ref position,ref normal,ref positionInLine,ref isValidTarget);
//注:新版Unity XR 的API中ref的形式已经由out代替
}
射线使用举例
在VR水果忍者中,非常重要的一点就是判断水果的切割方向,然后根据切割方向播放相应的特效和水果裂开的模型。可以考虑使用射线检测,碰到水果时产生切割。仅仅靠射线肯定无法知道切割的方向,所以我们还需知道手柄在切割时的挥动方向。
下面以Pico neo3的Unity XR SDK为例,介绍其PXR_Input的相关方法,有关更具体的脚本用法,可以参考Pico的官方文档。Documentation - Pico开发者平台 (pico-interactive.com)
//预测控制器在0.1秒后的位置
Vector3 targetPos = PXR_Input.GetControllerPredictPosition
(Controller.LeftController, 0.1f);
//知道此位置后 和当前位置做差就可以得到切割方向的方向向量了
//获取手柄挥动方向向量
Vector3 predictDir = targetPos - leftInteractor.transform.position;
根据上面的挥动向量配合射线检测,就能实现切割的输入事件了,但是如果直接使用此逻辑去判断,势必会造成输入和逻辑的紧耦合,所以这里也应该将射线检测封装成事件,供外部注册,采用观察者模式进行。
由于射线检测传递的数据一般不只一项,这里采用C#提供的EventHandler委托配合事件参数类来封装左右手柄控制器的射线切割事件,详细源码如下:
namespace VRFruit.InputSystem
{
//事件数据类
public class RayEventArgs : EventArgs
{
/// <summary>
/// 射线信息
/// </summary>
public RaycastHit rayInfo;
/// <summary>
/// 预测挥动方向向量
/// </summary>
public Vector3 predictDir;
public RayEventArgs(RaycastHit rayInfo,Vector3 predictDir)
{
this.rayInfo = rayInfo;
this.predictDir = predictDir;
}
}
}
namespace VRFruit.InputSystem
{
/// <summary>
/// 提供输入事件 事件源
/// </summary>
public class InputEventCenter : MonoBehaviour
{
#region 公开事件
/// <summary>
/// 左手柄射线击中到Interactive物体事件
/// </summary>
public event EventHandler<RayEventArgs> onCutInteractionObjectL;
/// <summary>
/// 右手柄射线击中到Interactive物体事件
/// </summary>
public event EventHandler<RayEventArgs> onCutInteractionObjectR;
#endregion
/* 属性 */
XRRayInteractor leftInteractor;
XRRayInteractor rightInteractor;
private void Awake()
{
leftInteractor = transform.
FindChildByName(InputVariables.LEFTHAND_CONTROLLER_NAME).GetComponent<XRRayInteractor>();
rightInteractor = transform.
FindChildByName(InputVariables.RIGHTHAND_CONTROLLER_NAME).GetComponent<XRRayInteractor>();
}
private void Update()
{
CutInteractionObjectL();
CutInteractionObjectR();
}
private void CutInteractionObjectL()
{
RaycastHit rayInfo;
leftInteractor.GetCurrentRaycastHit(out rayInfo);
if (rayInfo.collider != null && rayInfo.collider.tag == InputVariables.INTERACTION_TAG)
{
//预测手柄固定时间后的位置
Vector3 targetPos = PXR_Input.GetControllerPredictPosition
(Controller.LeftController, InputVariables.PREDICT_TIME);
//获取手柄挥动方向向量
Vector3 predictDir = targetPos - leftInteractor.transform.position;
//封装事件参数
RayEventArgs args = new RayEventArgs(rayInfo, predictDir);
if(onCutInteractionObjectL!=null)
onCutInteractionObjectL(null,args);
}
}
private void CutInteractionObjectR()
{
RaycastHit rayInfo;
rightInteractor.GetCurrentRaycastHit(out rayInfo);
if (rayInfo.collider != null && rayInfo.collider.tag == InputVariables.INTERACTION_TAG)
{
//预测手柄固定时间后的位置
Vector3 targetPos = PXR_Input.GetControllerPredictPosition
(Controller.RightController, InputVariables.PREDICT_TIME);
//获取手柄挥动方向向量
Vector3 predictDir = targetPos - rightInteractor.transform.position;
//封装事件参数
RayEventArgs args = new RayEventArgs(rayInfo, predictDir);
if(onCutInteractionObjectR!=null)
onCutInteractionObjectR(null, args);
}
}
}
}