【UGUI源码】事件系统EventSystem

一、下载UGUI源码

UGUI源码开源网站

二、打开工程

2.1 UnityEngine.UI

学习目标:弄明白为什么点击按钮会触发按钮事件,射线检测到接口方法执行的一系列流程弄清楚。

这篇文章,纯碎是写给我自己看的,随意写写,自己的一些看法(看源码就跟猜谜语一样哈哈)

public class EventSystem : UIBehaviour
public abstract class UIBehaviour : MonoBehaviour
{
空内容的虚方法:Awake、OnEnable、Start、OnDisable、OnDestroy;
有内容的虚方法:IsActive(){return isActiveAndEnabled}

空内容的特殊虚方法:
OnRectTransformDimensionsChange、
OnBeforeTransformParentChanged、
OnTransformParentChanged、
OnDidApplyAnimationProperties、
OnCanvasGroupChanged、
OnCanvasHierarchyChanged、

  //普通方法
  public bool IsDestroyed()
  {
    // Workaround for Unity native side of the object
    // having been destroyed but accessing via interfac
    // won't call the overloaded ==
    return this == null;
  }
}

重点是EventSystem的Update方法,如下所示:

  private void TickModules()
  {
      //遍历系统输入模块,进行更新模块
      for (var i = 0; i < m_SystemInputModules.Count; i++)
      {
            if (m_SystemInputModules[i] != null)
                    m_SystemInputModules[i].UpdateModule();
      }
  }

        //1.更新系统输入模块
        protected virtual void Update()
        {
            //当前的EventSystem是它吗?不是就return
            if (current != this)
                return;
            //更新系统输入模块
            TickModules();
            
            bool changedModule = false;
            //遍历系统输入模块
            for (var i = 0; i < m_SystemInputModules.Count; i++)
            {
                var module = m_SystemInputModules[i];
                //系统支持且处于激活状态
                if (module.IsModuleSupported() && module.ShouldActivateModule())
                {
                    //当前的系统输入模块 与 module不同 则更换输入模块
                    if (m_CurrentInputModule != module)
                    {
                        ChangeEventModule(module);
                        changedModule = true;
                    }
                    break;
                }
            }

            // no event module set... set the first valid one...
            if (m_CurrentInputModule == null)
            {  //如果当前输入模块是空的,需要进行寻找一个可以支持的输入模块
                for (var i = 0; i < m_SystemInputModules.Count; i++)
                {                        
                    var module = m_SystemInputModules[i];
                    if (module.IsModuleSupported())
                    {
                        //更换输入模块
                        ChangeEventModule(module);
                        changedModule = true;
                        break;
                    }
                }
            }
            //这一帧中没有更换模块且当前输入模块不为空
            if (!changedModule && m_CurrentInputModule != null)
                //使用当前输入模块进行操作(进行的是监控玩家输入并响应玩家输入)
                m_CurrentInputModule.Process();
        }

先不管TickModules方法到底干了什么,先解释下什么叫系统输入模块,其实就是一个专门监听玩家输入并响应的系统,例如:玩家按下了左击,此时可能会对鼠标所指向物体进行发送事件,响应一些物体所挂脚本的一些接口方法,下面具体进入到ChangeEventModule(module)方法进行详细说明。

 private void ChangeEventModule(BaseInputModule module)
 {
         
            if (m_CurrentInputModule == module)
                return;
            //对旧的输入模块进行禁用操作
            if (m_CurrentInputModule != null)  
                m_CurrentInputModule.DeactivateModule();
            //对新的输入模块进行激活
            if (module != null)
                module.ActivateModule();          
            m_CurrentInputModule = module;
 }

直接看激活ActivateModule()方法

(PS:module是BaseInputModule->PointerInputModule->StandaloneInputModule类群的对象)

上面这个PS其实很关键哈,直接就直接切入说这个方法会在StandaloneInputModule类实现了

//StandaloneInputModule类的ActivateModule()方法

public override void ActivateModule()
{            
            if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
                return;

            base.ActivateModule();
            //设置输入模块的2个成员:当前鼠标位置和上一次鼠标位置
            m_MousePosition = input.mousePosition;
            m_LastMousePosition = input.mousePosition;
            //从事件系统中获取当前的被选中物体(注意:这个被选中物体可能是射线检测Physics进行过后得知的,这里我还没仔细研读)            
            var toSelect = eventSystem.currentSelectedGameObject;
            if (toSelect == null)
                toSelect = eventSystem.firstSelectedGameObject;//默认值被选中的物体
            //PS:其实这个类是一个脚本,可以AddComponent方式挂在物体身上,仔细看他的属性就知道了。
            //调用事件系统的方法,设置被选中物体并且传递参数(事件参数!)
            eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData());
            
}

在上面,最后一行的SetSelectedGameObject(GameObject, BaseEventData)如下:

        //EventSystem类
        public void SetSelectedGameObject(GameObject selected, BaseEventData pointer)
        {
            if (m_SelectionGuard)
            {
                Debug.LogError("Attempting to select " + selected +  "while already selecting an object.");
                return;
            }

            m_SelectionGuard = true;
            if (selected == m_CurrentSelected)
            {
                m_SelectionGuard = false;
                return;
            }
          
            // Debug.Log("Selection: new (" + selected + ") old (" + m_CurrentSelected + ")");
            //调用Execute方法执行之前的被选中物体的Deselect方法,传递pointer事件参数
            ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler);
            m_CurrentSelected = selected;
            //调用Execute方法执行现在的被选中物体的Select方法,传递pointer事件参数
            ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler);
            m_SelectionGuard = false;
        }

关键部分:ExecuteEvents.Execute(GameObject, BaseEventData, EventFunction<T>)如下:

public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
        {
            //从处理器列表对象池获取一个空闲的处理器列表
            var internalHandlers = s_HandlerListPool.Get();
            //从target物体身上获取T类处理器(一个脚本),存入处理器列表internalHandlers
            GetEventList<T>(target, internalHandlers);
            //  if (s_InternalHandlers.Count > 0)
            //      Debug.Log("Executinng " + typeof (T) + " on " + target);
            //如果获取到了处理器列表
            for (var i = 0; i < internalHandlers.Count; i++)
            {
                T arg;
                try
                { 
                    //转成T类处理器存入arg
                    arg = (T)internalHandlers[i];
                }
                catch (Exception e)
                {                 
                    var temp = internalHandlers[i];
                    Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e));
                    continue;
                }

                try
                {
                    //执行EventFunction<T>委托方法,目的:执行arg处理器,处理器参数是eventData
                    functor(arg, eventData);
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
            }
            //处理器列表回收
            var handlerCount = internalHandlers.Count;
            s_HandlerListPool.Release(internalHandlers);
            return handlerCount > 0;//大于0表示有进行执行这个物体身上的相应方法!
        }

其实,看完上面这个方法大部分人都很懵逼的,因为我还有很多东西没讲,下面是第三个参数和第一个参数的讲解!第二个参数先视为一个事件参数好了,其实内容无关紧要这个BaseEventData的玩意。

以如下为例子说明:

 ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler);

第一个参数是当前被选中物体(GameObject类型),在Execute方法里面,他的作用就是

       //从处理器列表对象池获取一个空闲的处理器列表
            var internalHandlers = s_HandlerListPool.Get();
            //从target物体身上获取T类处理器(一个脚本),存入处理器列表internalHandlers
            GetEventList<T>(target, internalHandlers);
        /// <summary>
        /// Get the specified object's event event.
        /// </summary>
        private static void GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
        {
            // Debug.LogWarning("GetEventList<" + typeof(T).Name + ">");
            if (results == null)
                throw new ArgumentException("Results array is null", "results");

            if (go == null || !go.activeInHierarchy)
                return;
            //相当于new一个组件列表(这里也是一个列表对象池,优化的很彻底!!!)
            var components = ListPool<Component>.Get();
            //从go获取所有组件存入components
            go.GetComponents(components);
            for (var i = 0; i < components.Count; i++)
            {
                //检查组件i是否为T类型,不是的话就continue
                if (!ShouldSendToComponent<T>(components[i]))
                    continue;
                
                // Debug.Log(string.Format("{2} found! On {0}.{1}", go, s_GetComponentsScratch[i].GetType(), typeof(T)));
                //将该组件放入处理器,实际上组件会继承IEventSystemHandler接口,所以组件本身就是一个处理器!
                results.Add(components[i] as IEventSystemHandler);
            }
            //回收组件(注意,这里回收不会影响到results已经保存好了的处理器!)
            ListPool<Component>.Release(components);
            // Debug.LogWarning("end GetEventList<" + typeof(T).Name + ">");
        }

所以你应该清楚了上面的前半部分代码了,就是获取Execute第一个参数(物体)的有继承于T类型的脚本!T类型可以说是一个接口泛型;最终获取到一个IEventSystemHandler列表(internalHandlers),我可是一直在说着上面那个例子哦!

那么Execute的第三个参数:ExecuteEvents.selectHandler到底是什么鬼玩意呢?如下:

 public static EventFunction<ISelectHandler> selectHandler
 {
      get { return s_SelectHandler; }
 }

 private static readonly EventFunction<ISelectHandler> s_SelectHandler = Execute;
 //绕了那么一个大弯,结果是一个委托~ 哈~ 第一个参数很明显就是我所说的“处理器”,第二个参数是事件参数
 private static void Execute(ISelectHandler handler, BaseEventData eventData)
 {
      //这里执行的是ISelectHandler接口的方法OnSelect(BaseEventData)
      handler.OnSelect(eventData);
 }

至此,已经解释完毕Execute,它就是执行(第一个参数)物体身上的所有(第三个参数)<T>泛型接口的方法,参数是BaseEventData(第二个参数)。

在上述案例说明中:<T>一直是第三个参数委托方法指定的泛型ISelectHandler

提问:下面这行代码会做什么事情呢?

//调用Execute方法执行之前的被选中物体的Deselect方法,传递pointer事件参数
ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler);
public static EventFunction<IDeselectHandler> deselectHandler
{
    get { return s_DeselectHandler; }
}
private static readonly EventFunction<IDeselectHandler> s_DeselectHandler = Execute;

private static void Execute(IDeselectHandler handler, BaseEventData eventData)
{
    handler.OnDeselect(eventData);
}

答案:执行m_CurrentSelected物体身上的继承了IDeselectHandler接口的脚本的OnDeselect(BaseEventData)接口方法!参数是pointer(BaseEventData类型)!

至此,从EventSystem.Update(寻找要更换的、能使用的系统输入模块)到EventSystem.ChangeEventModule(更换输入模块),再到StandaloneInputModule.ActivateModule(激活输入模块),再到EventSystem.SetSelectedGameObject(选中物体,why?为什么突然就选中物体了?),再到ExecuteEvents.Execute(执行选中物体的被选中方法)的流程结束!

上面有个why?为什么跑去突然选中物体了?明明上一个逻辑是激活输入模块,因为这是默认的一个操作,即激活一个输入模块就会触发这个逻辑,之前也说了输入模块StandaloneInputModule是一个脚本!因为我没开Unity所以不截图了,它会有一个属性是与SelectedGameObject有关的,即使输入模块是刚刚才激活的,也会有一个默认的被选中物体;之后就是(选中物体),触发被选中物体的被选中逻辑OnSelect()

之后,解释最关键的代码:在EventSystem.Update函数最后一行:

m_CurrentInputModule.Process();

简单来说,也是利用了Execute方法触发了鼠标点击,按下,抬起,拖拉,进入,离开等事件,同样也是触发物体身上的某个继承于XXXX接口的脚本的接口方法,说到这里我想这个文章的意义就此结束了吧。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值