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);