美文网首页Unity技术VR/AR分享
SteamVR2.0 射线与物体交互,部分代码注释

SteamVR2.0 射线与物体交互,部分代码注释

作者: 86a262e62b0b | 来源:发表于2019-07-30 11:00 被阅读0次

    通过本文章能知道:

    1. 绘制出自己的射线

    2. 如何利用射线,并通过事件、委托,来实现射线与其他物体交互

    3. 注意:
      删减了SteamVR2.0自带LaserPointer脚本,提取了重点代码。

    • 发送者代码:
    using UnityEngine;
    using Valve.VR;
    
    public class My_LaserPointer : MonoBehaviour
    {
        public SteamVR_Behaviour_Pose pose; //可以获取是哪个手柄
        public SteamVR_Action_Boolean interactWithUI = SteamVR_Input.GetBooleanAction("InteractUI");    //行为类
    
        public LayerMask layerMask;
        //public QueryTriggerInteraction queryTriggerInteraction;
    
        public float dist = 100f; //射线检测最远距离
        public Color color;    //激光的颜色
        public Color clickColor = Color.green;//按下,激光的颜色
        public float thickness = 0.0025f; //激光束的粗细(创建了一个立方体,按下面的scale,x、y是0.002,z是100,就能看 到是一条很长的细线了)
    
        public GameObject holder;//一个空的GameObject,用于作激光束的父亲
        public GameObject pointer;  //激光束本身,是用一个立方体拉长来模拟的(为啥不用圆柱体?显然立方体要比圆柱体渲染简单得多,在很细的情况下,用立方体是明智的选择)
    
        private bool isActive = false; //用来判断是否为第一次调用
        private Transform previousContact = null;    //上次激光命中的物体的transform对象,用于判断是否命中同一个物体
        private float currentDist;//射线检测到物体时的距离
    
        public struct MyEventArgs       //通知
        {
            public SteamVR_Input_Sources fromInputSource;   //从那个手柄触发
            public uint flags;
            public float distance;      //距离
            public Transform target;    //哪个对象收到该通知
        }
    
        public delegate void MyEventHandler(object sender, MyEventArgs e);
    
        /**
        * 用来给外部提供委托。
        * 委托与事件实现了设计模式中的观察者模式,即一个为订阅者,一个为发送者。当消息发送时,凡是注册/订阅的用户都能收到
        */
        public event MyEventHandler PointerIn;
        public event MyEventHandler PointerOut;
        public event MyEventHandler PointerClick;
    
        private void Start()
        {
            if (pose == null)
                pose = this.GetComponent<SteamVR_Behaviour_Pose>();
            if (pose == null)
                Debug.LogError("No SteamVR_Behaviour_Pose component found on this object");
    
            if (interactWithUI == null)
                Debug.LogError("No ui interaction action has been set on this component.");
             
            CreateRay();
        }
    
        private void CreateRay()
        {
            holder = new GameObject();                  //激光束的父亲      
            holder.transform.parent = this.transform;
            holder.transform.localPosition = Vector3.zero;
            holder.transform.localRotation = Quaternion.identity;
    
            pointer = GameObject.CreatePrimitive(PrimitiveType.Cube);               //创建激光束,用长方体模拟
            pointer.transform.parent = holder.transform;
            pointer.transform.localScale = new Vector3(thickness, thickness, 100f); //设置locale为(0.002,0.002,100),看起来就是一条很长的线 
            pointer.transform.localPosition = new Vector3(0f, 0f, 50f);//位置设在父亲的(0,0,50)位置,因为对于立方体(长方体),其中心在立方体中心,因为上面被放大到了100倍,那移动位置到(0,0,50)可以让激光束的起点为父亲
            pointer.transform.localRotation = Quaternion.identity;
    
            //新建纯色材质并添加到MeshRender中。Color值通过inspector设置
            Material newMaterial = new Material(Shader.Find("Unlit/Color"));
            newMaterial.SetColor("_Color", color);
            pointer.GetComponent<MeshRenderer>().material = newMaterial;
        }
    
        public virtual void OnPointerIn(MyEventArgs e)
        {
            //回调激光命中委托
            if (PointerIn != null)
                PointerIn(this, e);
        }
    
        public virtual void OnPointerClick(MyEventArgs e)
        {
            if (PointerClick != null)
                PointerClick(this, e);
        }
    
        public virtual void OnPointerOut(MyEventArgs e)
        {
            //回调激光离开委托
            if (PointerOut != null)
                PointerOut(this, e);
        }
    
    
        private void Update()
        {
            if (!isActive) //如果第一次调用
            {
                isActive = true;
                this.transform.GetChild(0).gameObject.SetActive(true); //当前物体transform的第一个child就是holder
            }
    
            //此处请自行优化
            Ray raycast = new Ray(transform.position, transform.forward); //  构造一条射线
            RaycastHit hit;
            bool bHit = Physics.Raycast(raycast, out hit, dist,layerMask);
    
            //  如果之前已经有一个命中的物体,而当前命中的物体不是之前那个,那么说明前一个命中的物体就要收到射线离开的通知
            if (previousContact && previousContact != hit.transform)
            {
                MyEventArgs args = new MyEventArgs();   //通知
                args.fromInputSource = pose.inputSource;
                args.distance = 0f;
                args.flags = 0;
                args.target = previousContact;  //该由哪个对象收到?
    
                OnPointerOut(args);
                previousContact = null; //清除上一次命中的物体
            }
    
            //如果光线与Collider相碰 并且不是之前的物体
            if (bHit && previousContact != hit.transform)
            {
                MyEventArgs argsIn = new MyEventArgs();
                argsIn.fromInputSource = pose.inputSource;
                argsIn.distance = hit.distance; 
                argsIn.flags = 0;
                argsIn.target = hit.transform;
    
                OnPointerIn(argsIn);
                
                previousContact = hit.transform;// 记录这一次命中的物体
            }
    
            //如果没有命中
            if (!bHit)
            {
                previousContact = null; //清空
            }
    
            ////如果命中物体距离小于最大距离,则记录下来,否则最远就是100米
            //if (bHit && hit.distance <= dist)
            //{
            //    dist = hit.distance;
            //}
    
            currentDist = hit.distance;
    
            if (bHit && interactWithUI.GetStateUp(pose.inputSource))
            {
                MyEventArgs argsClick = new MyEventArgs();
                argsClick.fromInputSource = pose.inputSource;
                argsClick.distance = hit.distance;
                argsClick.flags = 0;
                argsClick.target = hit.transform;
    
                OnPointerClick(argsClick);
            }
    
            if (interactWithUI != null && interactWithUI.GetState(pose.inputSource))
            {
                //当...,将光束的粗细增大5倍,同时长度会设为currentDist,这样看起来光束就会到命中点截止,不会穿透物体
                pointer.transform.localScale = new Vector3(thickness * 5f, thickness * 5f, currentDist);
                pointer.GetComponent<MeshRenderer>().material.color = clickColor;
            }
            else
            {
                //当...时,显示原始粗细的光束
                pointer.transform.localScale = new Vector3(thickness, thickness, currentDist);
                pointer.GetComponent<MeshRenderer>().material.color = color;
            }
    
            //光束的位置总是设在光束长度的一半的位置,使得光束看起来总是从手柄发出来的
            pointer.transform.localPosition = new Vector3(0f, 0f, currentDist / 2f);
        }
    }
    
    • 订阅者,挂在你需要交互的物体上
    using UnityEngine;
    using static My_LaserPointer;
    
    //接收者
    public class MyReceiver : MonoBehaviour
    {
        public GameObject LaserOwner;     //SteamVR_LaserPointer类所在的位置
        private My_LaserPointer LaserSender;
    
        void Start()
        {
            LaserSender = LaserOwner.GetComponent<My_LaserPointer>();
    
            AddDelegate();
        }
    
        private void MyMoveIn(object send, MyEventArgs e)
        {
            if (e.target.Equals(transform)) //是不是自己
            {
                Debug.Log("MyMoveIn 1");
            }
        }
    
        private void MyMoveOut(object send, MyEventArgs e)
        {
            if (e.target.Equals(transform)) //是不是自己
            {
                Debug.Log("MyMoveOut 1");
            }
        }
        private void MyClick(object send, MyEventArgs e)
        {
            if (e.target.Equals(transform)) //是不是自己
            {
                Debug.Log("MyClick 1");
            }
        }
    
        private void AddDelegate()
        {
            if (LaserSender != null)
            {
                LaserSender.PointerIn += MyMoveIn;
                LaserSender.PointerOut += MyMoveOut;
                LaserSender.PointerClick += MyClick;
            }
            else
            {
                Debug.Log("LaserSender为空");
            }
        }
    
        private void DeleteDelegate()
        {
            if (LaserSender != null)
            {
                LaserSender.PointerIn -= MyMoveIn;
                LaserSender.PointerOut -= MyMoveOut;
                LaserSender.PointerClick -= MyClick;
            }
        }
    
        private void OnDestroy()
        {
            DeleteDelegate();
        }
    }
    
    • 另外,如果想和界面交互,请添加触发器,调整layerMask。

    相关文章

      网友评论

        本文标题:SteamVR2.0 射线与物体交互,部分代码注释

        本文链接:https://www.haomeiwen.com/subject/ueegrctx.html