美文网首页征服Unity3dUnity技术分享unity优化
再谈Unity输入事件脚本实现方式的选择及 LayerMask的

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

作者: Angeladaddy | 来源:发表于2017-12-04 09:25 被阅读79次

    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
    
            /// <summary>
            /// 场景物体点击事件
            /// </summary>
            public event Action<GameObject> OnSceneObjectClicked;
            public event Action<GameObject> 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<Renderer>().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<Renderer>().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的

        本文链接:https://www.haomeiwen.com/subject/ldnabxtx.html