SteamVR脚本功能分析(Yanlz+SteamVR+OpenVR+Teleport+Valve+VR+Ray+RaycastHit+立钻哥哥+==)

时间:2025-01-28 15:05:25

public class SteamVR_LaserPointer : MonoBehaviour{

    public bool active = true;

    public Color color;    //激光的颜色

 

    //激光束的粗细(创建了一个立方体,按下面的scale, x, y是0.002,z是100,就能看到是一条很长的细线了)

    public float thickness = 0.002f;

 

    public GameObject holder;    //一个空的GameObject, 用于做激光束的parent

 

    //激光束本身,是用一个立方体来长来模拟的(立钻哥哥:为啥不用圆柱体?显然立方体要比圆柱体渲染简单得多,在很细的情况下,用立方体是明智的选择)

    public GameObject pointer;

 

    bool isActive = false;    //用来判断是否第一次调用

 

    //这个是暴露在Inspector中的属性,用于控制是否给激光束(长方体)添加刚体;光线本身是没有重量的,没有必要添加刚体吧? 所以这里缺省是false;

    public bool addRigidBody = false;

 

    public Transform reference;

 

    //用于触发激光命中和离开事件

    public Event PointerEventHandler PointerIn;

    public Event PointerEventHandler PointerOut;

 

    //上次激光命中的物体的transform对象,用于判断是否命中同一个物体

    Transform previousContact = null;

 

    void Start(){

        //在脚本被加载的时候,做一些初始化

 

        //首先创建一个holder(即激光束的父物体)

        holder = new GameObject();

 

        //holder的transform的parent设为当前脚本所在的物体(通常这个脚本会加到控制器手柄上面)

        holder.transform.parent = this.transform;

 

        //位置设在0点(本地坐标系,相对于父亲)

        holder.transform.localPosition = Vector3.zero;

        holder.transform.localRotation = Quaternion.identity;

 

        //创建激光束,用长方体模拟

        pointer = GameObject.CreatePrimitive(PrimitiveType.Cube);

 

        //将父亲设为上面的holder

        pointer.transform.parent = holder.transform;

 

        //设置localScale为(0.002, 0.002, 100),看起来就是一条很长的线

        pointer.transform.localScale = new Vector3(thickness, thickness, 100f);

 

        //立钻哥哥:位置设在父亲的(0, 0, 50)位置,因为对于立方体(长方体),其中心在立方体中心,因为上面被放大到了100倍,那移动位置到(0, 0, 50)可以让激光束的起点为父亲;

        pointer.transform.localPosition = new Vector3(0f, 0f, 50f);

        pointer.transform.localRotation = Quaternion.identity;

 

        //立钻哥哥:如果指定了addRigidBody为true,则为激光束添加一个刚体,对应的Collider则只设为触发器(不会执行碰撞,但会进入代码);否则,会把Collider销毁掉,也就是不需要Collider;

        BoxCollider collider = pointer.GetComponent<BoxCollider>();

        if(addRigidBody){

            if(collider){

                collider.isTrigger = true;

            }

            Rigidbody rigidbody = pointer.AddComponent<Rigidbody>();

            rigidbody.isKinematic = true;

 

        }else{

            if(collider){

                Object.Destroy(collider);

            }

        }

 

        //新建纯色材质并添加到MeshRender中;Color值通过Inspector设置;

        Material newMaterial = new Material(Shader.Find(Unlit/Color));

        newMaterial.SetColor(_Color, color);

        pointer.GetComponent<MeshRender>().material = newMaterial;

    }

 

    public virtual void OnPointerIn(PointerEventArgs e){

        //立钻哥哥:回调激光命中 委托

        if(null != PointerIn){

            PointerIn(this, e);

        }

    }

 

    public virtual void OnPointerOut(PointerEventArgs e){

        //立钻哥哥:回调激光不在命中 委托

        if(null != PointerOut){

            PointerOut(this, e);

        }

    }

 

    void Update(){

        if(!isActive){

            //立钻哥哥:第一次调用时将holder设为active(当前物体transform的第一个child就是holder)

            isActive = true;

            this.transform.GetChild(0).gameObject.SetActive(true);

        }

 

        //命中物体(或者说激光束)的最远距离记为100米

        float dist = 100f;

 

        //当前物体(手柄上)上还要挂一个SteamVR_TrackedController脚本

        SteamVR_TrackedController controller = GetComponent<SteamVR_TrackedController>();

 

        //构造一条射线

        Ray raycast = new Ray(transform.position, transform.forward);

        RaycastHit hit;

 

        //计算射线命中的场景中的物体

        bool bHit = Physics.Raycast(raycast, out hit);

 

        if(previousContact && previousContact != hit.transform){

            //立钻哥哥:如果之前已经有一个命中的物体,而当前命中的物体发生了变化,那么说明前一个命中的物体就要收到一个不再命中的通知

            PointerEventArgs args = new PointerEventArgs();

            if(null != controller){

                args.controllerIndex = controller.controllerIndex;

            }

            args.distance = 0f;

            args.flags = 0;

            args.target = previousContact;

            OnPointerOut(args);

            previousContact = null;

        }    //立钻哥哥:if(previousContact && previousContact != ){}

 

        if(bHit && previousContact != hit.transform){

            //通知命中新的物体

            PointerEventArgs argsIn = new PointerEventArgs();

            if(null != controller){

                argsIn.controllerIndex = controller.controllerIndex;

            }

 

            //为射线原点到命中点的距离

            argsIn.distance = hit.distance;

            argsIn.flags = 0;

 

            //target记录的是命中物体的transform

            argsIn.target = hit.transform;

            OnPointerIn(argsIn);

 

            //记录上一次命中的物体的transform

            previousContact = hit.transform;

 

        }    //立钻哥哥:if(bHit && previousContact != ){}

 

        if(!bHit){

            previousContact = null;

        }

 

        if(bHit && hit.distance < 100f){

            //如果命中物体距离小于100,则记录下来,否则最远就是100米

            dist = hit.distance;

        }

 

        if(controller != null && controller.triggerPressed){

            //立钻哥哥:当按下扳机键时,将光束的粗细增大5倍,同时长度会设为dist,这样看起来光束就会到命中点截止,不会穿透物体

            pointer.transform.localScale = new Vector3(thickness * 5f, thickness * 5f, dist);

 

        }else{

            //按下扳机或者当前控制器没有添加SteamVR_TrackedController时,显示原始粗细的光束

            pointer.transform.localScale = new Vector3(thickness, thickness, dist);

        }

 

        //立钻哥哥:光束的位置总是设在光束长度的一半的位置,使得光束看起来总是从手柄发出来的

        pointer.transform.localPosition = new Vector3(0f, 0f, dist/2f);

 

    }    //立钻哥哥:void Update(){}

 

}    //立钻哥哥:public class SteamVR_LaserPointer:MonoBehaviour{}

 

public struct PointerEventArgs{

    public uint controllerIndex;    //控制器(手柄)索引

    public uint flags;   

    public float distance;    //激光原点到命中点(交点)的距离

    public Transform target;    //命中物体的transform对象

}

 

public delegate void PointerEventHandler(object sender, PointerEventArgs e);