Unity的UI系统中,EventSystem
负责管理调度事件,控制各输入模块、射线投射以及事件动作的执行。UI的事件系统处理用户的交互动作,通过BaseInput来获取用户输入的信息和状态,通过InputModule处理输入,产生和发送事件,通过RayCaster判断和选择需要响应交互事件的对象,最终由ExecuteEvents执行响应的动作,调用EventSystemHandler,以完成交互。
重要的成员
public static EventSystem current { get; set; }
EventSystem
的全局单例。
List<BaseInputModule> m_SystemInputModules
维护了一个列表,处于激活状态的输入模块。
BaseInputModule
是输入模块的基类,衍生类有TouchInputModule
和StandaloneInputModule
。BaseInputModule m_CurrentInputModule
当前正在响应的输入模块,私有方法
ChangeEventModule
会更新和改变此成员GameObject m_FirstSelected
首个选中的对象,在
StandaloneInputModule
中会用到GameObject m_CurrentSelected
当前选中的对象,可由各个输入模块调用
EventSystem
的SetSelectedGameObject
方法来更新和改变此成员。在执行事件的动作时以此成员为对象,即ExecuteEvents.Execute(m_CurrentSelected, ...)
bool m_Paused
表示事件系统是否处在暂停状态
bool m_SelectionGuard
选择的保护状态,当选择了一个新的对象时,会先将该值置为
true
,在完成新旧对象选择状态及事件的执行后,再将该值置为false
,详见SetSelectedGameObject
BaseEventData m_DummyData
一份伪造的
BaseEventData
假数据。
重要的方法
void UpdateModules()
当输入模块的激活状态改变时(
OnEnable
或OnDisable
)会调用此函数来更新EventSystem
中管理的输入模块的列表。void SetSelectedGameObject(GameObject selected, BaseEventData pointer)
设置当前选中的对象。还有一个重载方法
void SetSelectedGameObject(GameObject selected)
。此方法会被一些衍生自Selectable
的类直接调用,指定当前响应事件的对象。void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
会从RaycasterManager获取所有RayCaster(都继承于
BaseRaycaster
),并调用每个RayCaster的Raycast
方法,将所有的射线投射结果存入raycastResults
中,在函数返回之前,将所有的射线投射结果排序,排序依据见RaycastComparer
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
{
if (lhs.module != rhs.module)
{
if (lhs.module.eventCamera != null && rhs.module.eventCamera != null && lhs.module.eventCamera.depth != rhs.module.eventCamera.depth)
{
// need to reverse the standard compareTo
if (lhs.module.eventCamera.depth < rhs.module.eventCamera.depth)
return 1;
if (lhs.module.eventCamera.depth == rhs.module.eventCamera.depth)
return 0;
return -1;
}
if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);
if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
}
if (lhs.sortingLayer != rhs.sortingLayer)
{
// Uses the layer value to properly compare the relative order of the layers.
var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
return rid.CompareTo(lid);
}
if (lhs.sortingOrder != rhs.sortingOrder)
return rhs.sortingOrder.CompareTo(lhs.sortingOrder);
if (lhs.depth != rhs.depth)
return rhs.depth.CompareTo(lhs.depth);
if (lhs.distance != rhs.distance)
return lhs.distance.CompareTo(rhs.distance);
return lhs.index.CompareTo(rhs.index);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
排序依据首先是输入模块相机深度、sortOrder、renderOrder,然后是射线投射结果(`RaycastResult`)的`sortingLayer`、`sortingOrder`、`depth`、`distance`最后是`index`。具体这些参数后边再展开讨论。
- `bool IsPointerOverGameObject()`
有重载形式`bool IsPointerOverGameObject(int pointerId)`,缺省传入参数为鼠标左键的ID,此函数调用并返回当前输入模块的`IsPointerOverGameObject(int pointerId)`方法:
```C#
public bool IsPointerOverGameObject(int pointerId)
{
if (m_CurrentInputModule == null)
return false;
return m_CurrentInputModule.IsPointerOverGameObject(pointerId);
}
这个方法通常可用于判断当前是否点击在UI上。
void TickModules()
每帧都会调用,遍历当前的所有输入模块,调用其
UpdateModule()
方法:1
2
3
4
5
6
7
8private void TickModules()
{
for (var i = 0; i < m_SystemInputModules.Count; i++)
{
if (m_SystemInputModules[i] != null)
m_SystemInputModules[i].UpdateModule();
}
}void Update()
最主要的逻辑,在每帧内都会处理一下的事情:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39protected virtual void Update()
{
if (current != this || m_Paused)
return;
TickModules();
bool changedModule = false;
for (var i = 0; i < m_SystemInputModules.Count; i++)
{
var module = m_SystemInputModules[i];
if (module.IsModuleSupported() && module.ShouldActivateModule())
{
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)
会把module
设置为m_CurrentInputModule
。如果输入模块没有发生变化则会调用该模块的Process()
方法。
其它密切配合的类
负责处理输入并产生事件的 BaseInputModule
及其衍生类,以及负责射线投射的BaseRaycaster
及其衍生类,会在后边的文章讨论。
本系列其它文章详见Unity3D UGUI 源码学习