再谈Unity输入事件脚本实现方式的选择及 LayerMask的使用

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

你可能感兴趣的:(再谈Unity输入事件脚本实现方式的选择及 LayerMask的使用)