鉴于该文章点赞和收藏那么高,我推荐一款插件 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是一个小球体模型,作用是在碰撞点显示为一个球体,这个就需要你自己把他手动拖进去了,到了这一步,大概就把射线碰撞这块给处理好了。下一步我们处理手柄输入的问题