通过本文章能知道:
绘制出自己的射线
如何利用射线,并通过事件、委托,来实现射线与其他物体交互
注意:
删减了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。