为SteamVR做射线触发UI

最近需要在SteamVR上做类似VRTK的射线和UI交互的功能。我自己开发的时候的思路是在右手手柄上加一个LineRender组件,然后从手柄的位置为起点,手柄正方向transform.forward*10000作为终点画一根线。然后给Canvas上的UI组件添加碰撞体。在Update中利用碰撞检测去调用UI的事件。我的这种方法对于按钮或者Toggle这种hi直接触发的实现方案比较简单,但是如果是操作滑动条啊之类的包含位置操作的,就会处理的逻辑相对复杂些。后来我的同事给出了更好的解决方法:重写Unity 的 BaseInputModel,让射线跟UI交互就跟我们的鼠标跟UI交互一样。话不多说,先上代码:

/****************************************************
    文件:VRInputModel.cs
	作者:Paul   邮箱: 794451358@qq.com
    日期:2020/12/18 11:22:22
	功能:为VR UI重写BaseInputModel
*****************************************************/


using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace PFarmeWork
{
    public class VRInputModel : BaseInputModule
    {
        /// <summary>
        /// 事件摄像机
        /// </summary>
        public Camera eventCamera;
        /// <summary>
        /// 是否执行UI操作标志位
        /// </summary>
        public bool isExecute = false;
        /// <summary>
        /// 指针事件数据
        /// </summary>
        public PointerEventData Data { get; private set; } = null;
        protected override void Start()
        {
            Data = new PointerEventData(eventSystem);
            //设定射线的起始点为事件相机的视窗中心
            Data.position = new Vector2(eventCamera.pixelWidth / 2, eventCamera.pixelHeight / 2);
        }
        public override void Process()
        {
            //发射射线检测UI
            eventSystem.RaycastAll(Data, m_RaycastResultCache);
            //从由近到远的射线碰撞结果m_RaycastResultCache中获取第一个(最近)的碰撞结果对应的射线结果
            Data.pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache);
            //先处理射线点进入或移出UI游戏物体(这个事件让继承IPointerEnterHandler和IPointerExitHandler中的事件触发)
            HandlePointerExitAndEnter(Data, Data.pointerCurrentRaycast.gameObject);
            //按下点击按钮的标志位
            if (isExecute)
            {
                ProcessPress();
            }
            else
            {
                ProcessRelease();
            }
            ExecuteEvents.Execute(Data.pointerDrag, Data, ExecuteEvents.dragHandler);

        }
        private void ProcessPress()
        {
            print("process");
            //把当前的射线信息赋值给光标按下射线
            Data.pointerPressRaycast = Data.pointerCurrentRaycast;
            //把光标按下射线对应的游戏物体赋值给指针数据中的pointPress
            Data.pointerPress = ExecuteEvents.GetEventHandler<IPointerClickHandler>(Data.pointerPressRaycast.gameObject);
            //执行光标按下事件,该事件会让继承了IPointerClickHandler的派生类中的事件触发
            ExecuteEvents.Execute(Data.pointerPress, Data, ExecuteEvents.pointerDownHandler);
            //把光标按下射线对应的游戏物体赋值给指针数据中的pointDrag
            Data.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(Data.pointerPressRaycast.gameObject);
            //执行光标开始拖动事件,该事件会让继承了IIDragHandler的派生类中的事件触发
            ExecuteEvents.Execute(Data.pointerDrag, Data, ExecuteEvents.beginDragHandler);
        }
        private void ProcessRelease()
        {
            GameObject pointRelease = ExecuteEvents.GetEventHandler<IPointerClickHandler>(Data.pointerCurrentRaycast.gameObject);

            if (Data.pointerPress == pointRelease)
                ExecuteEvents.Execute(Data.pointerPress, Data, ExecuteEvents.pointerClickHandler);

            ExecuteEvents.Execute(Data.pointerPress, Data, ExecuteEvents.pointerUpHandler);
            ExecuteEvents.Execute(Data.pointerDrag, Data, ExecuteEvents.endDragHandler);

            Data.pointerPress = null;
            Data.pointerDrag = null;

            Data.pointerCurrentRaycast.Clear();
        }
    }

}

在我们的手柄对应的输入检测函数中,将IsExecute设置为true。

/****************************************************
    文件:InteractionSys.cs
	作者:Paul   邮箱: 794451358@qq.com
    日期:2020/11/6 11:48:55
	功能:交互系统主要的交互功能函数
*****************************************************/


using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using PFarmeWork;
using Valve.VR.InteractionSystem;
using UnityEngine.Events;
using DG.Tweening;
using Valve.VR;
using UnityEditor;
using UniRx;
using System;
namespace DaschowStreet
{

    public class InteractionSys : MonoSingleton<InteractionSys>
    {

         
        public SteamVR_Action_Boolean uiClick;
        public SteamVR_Action_Boolean m_UIPoint;
        public SteamVR_Action_Boolean m_UI;
        private GameObject m_Player;
        public Transform rightHand;
        private VRInputModel m_InputModel;
        private LineRenderer m_Line;
      
        private void Awake()
        {

            if (GetComponent<CustomInput>())
            {
              
                m_UIPoint = GetComponent<CustomInput>().UIPoint;
                m_Player = GameObject.FindGameObjectWithTag("Player");
                m_UI = GetComponent<CustomInput>().UI;
                m_UIPoint.onState += M_UIPoint_onState;
                m_UIPoint.onStateUp += M_UIPoint_onStateUp;
                m_Line = GetComponent<LineRenderer>();
                m_InputModel = rightHand.GetComponent<VRInputModel>();
            }

            else
            {
                Destroy(gameObject);
            }


        }

        private void M_UIPoint_onStateUp(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
        {
            
            m_InputModel.isExecute = false;
           
            var line = GetComponent<LineRenderer>();
            line.SetPosition(0, Vector3.zero);
            line.SetPosition(1, Vector3.zero);
            
        }

        private void M_UIPoint_onState(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
        {
            if (uiClick.stateDown)
            {
                m_InputModel.isExecute = true;
            }
            else
            {
                m_InputModel.isExecute = false;
            }
           
            var line = GetComponent<LineRenderer>();
            line.SetPosition(0, rightHand.position);
            line.SetPosition(1, rightHand.forward*10000);

        
        }

       

       
       
    }

}

然后,最重要的一步:在手柄上添加一个Camera作为事件相机,所有的UI事件都是基于这个相机触发的(但是这个相机的enable可以失能)。把这个事件相机拖到第一个脚本的事件相机eventCamera 和Canvas中的EventCamera中

关于第一个脚本,如果理解不了的,可以先看这个大佬的博客:https://blog.csdn.net/ecidevilin/article/details/52503595。理解了BaseInputModel和EventSystem后再去看代码会容易理解些。

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

int_Paul

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值