unity射线判断,有的时候是一个不得不用的功能,这时候就要区别射线穿过的物体,网上有的说用tag,确实可以,但是当场景物体一多,那就每个物体就要加tag,给维护造成不必要的麻烦,所以最方便的还是使用layer。
假设场景中有一堆3d物体,你想判断他们的射线穿透情况,同时又想避免UI,那么最好的办法如下:
void Update()
{
//使用位操作得到UI层的layer
int layerMask = 1 << LayerMask.NameToLayer("UI");
//对layer取反,意思是得到除了UI层以外的层,这一步很主要
layerMask = ~layerMask;
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit,Mathf.Infinity,layerMask))
{
Debug.Log("hit on object");
}
else
{
Debug.Log("hit on ui");
}
}
}
这个脚本挂到摄像机或者任何一个物体都可,我一般会添加一个InputListener
的单例来挂这个脚本,然后在这个单例中发送事件,场景中其余脚本只要监听这个事件,判断点击的物体是不是自己就行了。
为什么用layer?因为unity中添加UI 的时候,是自动归到UI层的,所以用layer判断,场景不需要做任何改动。
贴出完整的实现方法:
- InputListener类负责检测所有的用户输入,并发送事件
public class InputListener : MonoBehaviour
{
#region 单例实现
private static InputListener _instance;
public static InputListener Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType(typeof(InputListener )) as InputListener ;
if (_instance == null)
{
GameObject obj = new GameObject();
obj.hideFlags = HideFlags.HideAndDontSave;//隐藏实例化的new game object
_instance = obj.AddComponent(typeof(InputListener )) as InputListener;
}
}
return _instance;
}
}
#endregion
///
/// 场景物体点击事件
///
public event Action OnSceneObjectClicked;
public event Action OnSceneObjectDoubleClicked;
void Update()
{
int layerMask = 1 << LayerMask.NameToLayer("UI");
layerMask = ~layerMask;
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit,Mathf.Infinity,layerMask))
{
Debug.Log("hit on object");
if (hit.collider != null)
{
if (OnSceneObjectClicked != null)
{
OnSceneObjectClicked(hit.collider.gameObject);
}
//双击检测
t2 = Time.realtimeSinceStartup;
if (t2 - t1 < 0.2f)
{
if (OnSceneObjectDoubleClicked != null)
{
OnSceneObjectDoubleClicked(hit.collider.gameObject);
}
}
t1 = t2;
}
}
else
{
Debug.Log("hit on ui");
}
}
}
在任何一个物体上,或者其父物体上,监听事件
public class DemoObjectController : MonoBehaviour
{
void Start()
{
PCInputListener.Instance.OnSceneObjectClicked += Instance_OnSceneObjectClicked;
}
private void Instance_OnSceneObjectClicked(GameObject obj)
{
//入股此脚本挂在父物体上,则下面的gameObject换成子物体的gameObject就行
if (obj == gameObject)
{
// do what you want...
}
}
}
用这种方法的好处是比较灵活,在需要判断用户输入的物体上监听事件就可以了。实际上这就是观察者模式的实现。
为何不用OnMouseXXXX方法和OnPointerXXXX方法?
这两个方法适用于场景中物体较少的时候,因为每个物体都要写这个方法。但是当你的场景物体本来就不多(少于10个),还是可以用的。
在我的工程中,我一般用这两个方法做一些简单的事,比如高亮鼠标悬停物体:
OnMouseXXXX版本:
public class InteractiveObjIdentfier : MonoBehaviour
{
Material m;
Color initColor;
private void Start()
{
m = GetComponent().materials[0];
initColor = m.color;
}
void OnMouseEnter()
{
m.color = GV.HighlightColor;
Debug.Log("mouse enter object");
}
void OnMouseExit()
{
m.color = initColor;
}
}
OnPointerXXXX版本,前提条件是场景中有EventSystem,以及Physical Reaycaster,见我以前的这篇文章:
using UnityEngine.EventSystem;
public class InteractiveObjIdentfier : MonoBehaviour,IPointerEnterHandler,IPointerExitHandler
{
Material m;
Color initColor;
private void Start()
{
m = GetComponent().materials[0];
initColor = m.color;
}
public void OnPointerEnter(PointerEventData eventData)
{
m.color = GV.HighlightColor;
Debug.Log("pointer enter object");
}
public void OnPointerExit(PointerEventData eventData)
{
m.color = initColor;
}
}