您的当前位置:首页正文

VR Unity 射线点击操作

2024-11-28 来源:个人技术集锦

鉴于该文章点赞和收藏那么高,我推荐一款插件  VR Interaction Framework

该插件适配大部分的vr硬件,未来应该也超级兼容,而且UI事件这块兼容UGUI,单单这点就很亮眼。兼容UGUI,不用再下那么大功夫再搞按钮事件这块了。

* 我们只做VR内容时,如果要用到例如HTC手柄类的硬件的话,可能就需要一个射线点击的功能,如,点击某一个图标,按下手柄,触发点击按钮事件,那么这个类就是解决这类问题的一步
 * 射线碰撞事件的类,可以移植到大多数VR SDK上,因为每个平台都封装有了自己的注视管理,但这个引擎自身的拓展开发,方便拓展和维护,下面会有一个Debug的注释代码,可以注释掉来测试效果,如果需要有碰撞效果,必须给物体加上碰撞体,并且标签要对应,碰撞层也要对应,其实这么麻烦的原因也是为了后期的拓展开发,基础层的条件触发牢固后,上层才会安稳,舒服。

   碰撞逻辑我是放在LateUpdate里的,因为Input类放在Update里处理,解决完输入后逻辑后再处理碰撞逻辑。

  这是个抽象类,你必须新建一个类来继承他,实现获取射线的方法

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace CameraFixationManager
{
    public abstract class CameraFixationManager : MonoBehaviour
    {
        /// <summary>
        /// 射线是否撞到了有触发事件的碰撞体
        /// </summary>
        public static bool IsImpactCollider = false;
        /// <summary>
        /// 撞击点的位置
        /// </summary>
        public Vector3 HitPosition = Vector3.zero;
        /// <summary>
        /// 射线可以碰撞到的层
        /// </summary>
        public int RayColliderLayer = 5;

        #region 事件回调
        /// <summary>
        /// 射线进入回调
        /// </summary>
        public Action<GameObject> RayEnter;
        /// <summary>
        /// 射线离开的回调
        /// </summary>
        public Action<GameObject> RayLeave;
        /// <summary>
        /// 射线正在射中的回调
        /// </summary>
        public Action<GameObject> RayEntering;
        #endregion


        /// <summary>
        /// 响应的标签层,所有的交互事件都需要放到该层
        /// </summary>
        public string ResponseTag;
        /// <summary>
        /// 是否播放音乐特效
        /// </summary>
        public bool IsPlayAudio = true;
        /// <summary>
        /// 射线缓冲,意思就是射线当前碰撞套的物体
        /// </summary>
        private Transform _transform = null;
        /// <summary>
        /// 是否碰撞到
        /// </summary>
        protected bool IsCollider = true;
       
        
       protected   virtual void Awake()
        {
           
        }
        /// <summary>
        /// 放在延迟更新是因为,输入事件一般是在Update之前,所以,处理完输入逻辑后,在 Update里判断输入状态,再在延迟更新里判断碰撞状态,这样就不用手动
        /// edit/ProjectSettings/ScriptExecutionOrder的编排脚本的方法执行顺序  
        /// </summary>
        void LateUpdate()
        {
            if (!IsCollider) return;
            RaycastHit hit;
            //只能向屏幕中点发射线
            var ray = GetRay();//这里进一步抽象,因为有相机注视,还有手柄射线注视

           if (Physics.Raycast(ray, out hit,2000f,1<< RayColliderLayer))//这里把5层设置为可碰撞层,其他层都要忽略掉   
            {
                //  Debug.Log("碰撞到的物体名字是:" + hit.transform.name);
                HitPosition = hit.point;//碰撞点事世界位置,切记   切记

                if (!hit.transform.CompareTag(ResponseTag) )//如果不属于ResponseTag标签,则不进行消息触发
                {
                    IsImpactCollider = false;
                   

                    if (_transform != null)//物体离开回调
                    {
                        if (RayLeave != null)
                            RayLeave(_transform.gameObject);
                     //   Debug.Log("离开了" + _transform.name);
                    }
                    _transform = null;

                    return;
                }
                IsImpactCollider = true;
                //被碰撞的物体第一次进入触发回调,第二个逻辑是射线从一个物体移动到另外一个物体的判断
                if (_transform == null || (_transform != hit.transform && _transform != null))
                {
                    if (RayEnter != null)
                        RayEnter(hit.transform.gameObject);
                  //  if (IsPlayAudio)
                   // AudioManager.Instance.PlayAudio(AudioType.RayPass);
                 //   Debug.Log("进入了" + hit.transform.name);

                    if(_transform!=null)
                    {
                        if (RayLeave != null)
                            RayLeave(_transform.gameObject);
                     //   Debug.Log("离开了" + _transform.name);
                    }
                    _transform = hit.transform;
                }
                else if(_transform == hit.transform)//在进入物体的第二帧以上
                {
                    if (RayEntering != null)
                        RayEntering(hit.transform.gameObject);
                   // Debug.Log("持续碰撞物体:" + hit.transform.name);
                    _transform = hit.transform;
                }
            }
            else
            {
                IsImpactCollider = false;

                HitPosition = Vector3.zero;

                 if ( _transform!= null)//物体离开回调
                {
                    if (RayLeave != null)
                        RayLeave(_transform.gameObject);
                   // Debug.Log("离开了" + _transform.name);
                }
                _transform = null;
            }
        }

        protected abstract Ray GetRay();

        

        protected virtual void OnDestroy()
        {
            
        }
    }
}

好了,如果你新建一个类来继承他了,我这里实现方法是

 protected override Ray GetRay()
    {
        Ray ray = new Ray(RayStartingPoint.transform.position, RayStartingPoint.transform.forward);
        return ray;
    }

RayStartingPoint为射线的发射点   这样就可以试着运行看看了,写到了这里,你会发现看不到射线,那我们这里再给你继承CameraFixationManager 的类实现一个射线显示的方法,这个射线是有宽度的,这个方法是Unity提供的接口

 public void OnRenderObject()
    {
        GL.PushMatrix();

        mat.SetPass(0);

        GL.Begin(GL.QUADS);

        GL.Color(Color.cyan);

       

        //该面片是以逆时针画线,第一个点是在物体右边开始
        //右边开始的第一个点
        Vector3 position1 = RayStartingPoint.transform.position + RayStartingPoint. transform.right * Width;
        //右边远方的第二个点
        Vector3 position2 = RayStartingPoint.transform.position;
        //远方没有偏移的第三个点
        Vector3 position3 = RayStartingPoint. transform.position;
        //物体的原点为第四个点
        Vector3 position4 = RayStartingPoint.transform.position;

        //设置射线
        Ray rayer = new Ray(RayStartingPoint.transform.position, RayStartingPoint.transform.forward);
        RaycastHit rayhit;
        //当射线与物体碰撞时,获取rayhit(rayhit.point射线与物体的接触点,rayhit.distance向量方向,rayhit.collider.gameobject获取物体的所有信息)
        if (Physics.Raycast(rayer, out rayhit))
        {
            //右边远方的第二个点
            position2 = rayhit.point + RayStartingPoint.transform.right * Width;
            //远方没有偏移的第三个点
            position3 = rayhit.point;
            //把球体放在射线与物体的接触点
            _sphere.transform.position = new Vector3(rayhit.point.x, rayhit.point.y, rayhit.point.z);
        }
        else
        {
            position2 = position1 + RayStartingPoint.transform.forward * Lenght;
            position3 = RayStartingPoint. transform.position + RayStartingPoint.transform.forward * Lenght;
            _sphere.transform.position = new Vector3(100000f, 0f, 0f);
        }

       
        //1
        GL.Vertex(position1);
        //2
        GL.Vertex(position2);
        //3
        GL.Vertex(position3);
        //4
        GL.Vertex(position4);

        GL.End();

        GL.PopMatrix();

    }

_sphere是一个小球体模型,作用是在碰撞点显示为一个球体,这个就需要你自己把他手动拖进去了,到了这一步,大概就把射线碰撞这块给处理好了。下一步我们处理手柄输入的问题

显示全文