一、下载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接口的脚本的接口方法,说到这里我想这个文章的意义就此结束了吧。