UGUI 事件
场景必须存在EventSystem 并且挂载继承自PointerInputModule基(PointerInputModule继承自BaseInputModule),包括StandaloneInputModule和TouchInputModule。
事件触发从输入类BaseInput开始,此类是对Input类的包装,简化事件以适应UI事件。此类将在StandaloneInputModule和TouchInputModule的基类的基类BaseInputModule的input动态添加初始化。
public BaseInput input
{
get
{
if (m_InputOverride != null)
return m_InputOverride;
if (m_DefaultInput == null)
{
var inputs = GetComponents<BaseInput>();
foreach (var baseInput in inputs)
{
// We dont want to use any classes that derrive from BaseInput for default.
if (baseInput != null && baseInput.GetType() == typeof(BaseInput))
{
m_DefaultInput = baseInput;
break;
}
}
if (m_DefaultInput == null)
m_DefaultInput = gameObject.AddComponent<BaseInput>();
}
return m_DefaultInput;
}
}
如果挂载EventSystem组件的物体同时存在StandaloneInputModule和TouchInputModule,那么StandaloneInputModule和TouchInputModule分别持有各自的输入类BaseInput。
EventSystem 的Update调用StandaloneInputModule和TouchInputModule的UpdateModule()方法实时设置当前点个最后点和Process()。
EventSystem 的Update()方法
protected virtual void Update()
{
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())
{
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();
}
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;
}
StandaloneInputModule和TouchInputModule的UpdateModule()的Process()代码段将会分别调用 以下方法
StandaloneInputModule类调用的方法
public override void Process()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
return;
//
bool usedEvent = SendUpdateEventToSelectedObject(); //SendUpdateEventToSelectedObject() 发送选择事件,并设置当前选择的物体
if (eventSystem.sendNavigationEvents)
{
if (!usedEvent)
usedEvent |= SendMoveEventToSelectedObject(); //SendMoveEventToSelectedObject() 发送移动事件,并返回是否移动
if (!usedEvent)
SendSubmitEventToSelectedObject(); //SendSubmitEventToSelectedObject() 发送提交事件,
}
// touch needs to take precedence because of the mouse emulation layer
if (!ProcessTouchEvents() && input.mousePresent) //ProcessTouchEvents() 发送点触事件
ProcessMouseEvent(); //ProcessMouseEvent() 发送点击事件
}
TouchInputModule类调用的方法:
if (UseFakeInput())
FakeTouches();
else
ProcessTouchEvents();
最终将会调用StandaloneInputModule和TouchInputModule的基类PointerInputModule的 GetTouchPointerEventData()和GetMousePointerEventData()方法。然后这两个方法将会调用他们持有的EventSystem 的RaycastAll()方法。调用如下:
eventSystem.RaycastAll(pointerData, m_RaycastResultCache);
RaycastAll()方法将发射一条屏幕射线,获取第一个继承自Graphic基类的组件(如:Image)
RaycastAll()方法将会调用所有继承自BaseRaycaster基类的派生类(PhysicsRaycaster,GraphicRaycaster,Physics2DRaycaster)的Raycast()方法,获取射线所穿过的组件的第一个组件。如GraphicRaycaster,将会获取射线所穿过的所有组件的第一个继承自Graphic的组件 。
代码如下:
private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
{
// Debug.Log("ttt" + pointerPoision + ":::" + camera);
// Necessary for the event system
int totalCount = foundGraphics.Count;
for (int i = 0; i < totalCount; ++i)
{
Graphic graphic = foundGraphics[i];
// -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
if (graphic.depth == -1 || !graphic.raycastTarget || graphic.canvasRenderer.cull)
continue;
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
continue;
if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
continue;
if (graphic.Raycast(pointerPosition, eventCamera))
{
s_SortedGraphics.Add(graphic);
}
}
s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
// StringBuilder cast = new StringBuilder();
totalCount = s_SortedGraphics.Count;
for (int i = 0; i < totalCount; ++i)
results.Add(s_SortedGraphics[i]);
// Debug.Log (cast.ToString());
s_SortedGraphics.Clear();
}
}
BaseRaycaster的Raycast()方法又会调用Graphic的Raycast()方法来获取继承自Graphic基类的组件是否被射线穿过。
public virtual bool Raycast(Vector2 sp, Camera eventCamera)
{
if (!isActiveAndEnabled)
return false;
var t = transform;
var components = ListPool<Component>.Get();
bool ignoreParentGroups = false;
bool continueTraversal = true;
while (t != null)
{
t.GetComponents(components);
for (var i = 0; i < components.Count; i++)
{
var canvas = components[i] as Canvas;
if (canvas != null && canvas.overrideSorting)
continueTraversal = false;
var filter = components[i] as ICanvasRaycastFilter;
if (filter == null)
continue;
var raycastValid = true;
var group = components[i] as CanvasGroup;
if (group != null)
{
if (ignoreParentGroups == false && group.ignoreParentGroups)
{
ignoreParentGroups = true;
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
}
else if (!ignoreParentGroups)
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
}
else
{
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
}
if (!raycastValid)
{
ListPool<Component>.Release(components);
return false;
}
}
t = continueTraversal ? t.parent : null;
}
ListPool<Component>.Release(components);
return true;
}
其中
filter = components[i] as ICanvasRaycastFilter;
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
ICanvasRaycastFilter接口(实现此接口的派生类:Mask,RectMask2D,Image)的IsRaycastLocationValid()方法是判断组件是否在渲染范围内,代码如下,
Mask,RectMask2D代码相同:
public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (!isActiveAndEnabled)
return true;
return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera);
}
Image判断相对复杂一些,要判断图片alpha通道,如果alpha通道低于一个值,将不加入射线检测。
public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
if (alphaHitTestMinimumThreshold <= 0)
return true;
if (alphaHitTestMinimumThreshold > 1)
return false;
if (activeSprite == null)
return true;
Vector2 local;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local))
return false;
Rect rect = GetPixelAdjustedRect();
// Convert to have lower left corner as reference point.
local.x += rectTransform.pivot.x * rect.width;
local.y += rectTransform.pivot.y * rect.height;
local = MapCoordinate(local, rect);
// Normalize local coordinates.
Rect spriteRect = activeSprite.textureRect;
Vector2 normalized = new Vector2(local.x / spriteRect.width, local.y / spriteRect.height);
// Convert to texture space.
float x = Mathf.Lerp(spriteRect.x, spriteRect.xMax, normalized.x) / activeSprite.texture.width;
float y = Mathf.Lerp(spriteRect.y, spriteRect.yMax, normalized.y) / activeSprite.texture.height;
try
{
return activeSprite.texture.GetPixelBilinear(x, y).a >= alphaHitTestMinimumThreshold;
}
catch (UnityException e)
{
Debug.LogError("Using alphaHitTestMinimumThreshold greater than 0 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this);
return true;
}
}
所以上面的条件满足后还必须满足以下条件
所有的UGUI渲染组件(继承自graphic,如Button,Image)最终的父节点必须挂有Canvas组件和继承自射线管理类BaseRaycaster(GraphicRaycaster,PhysicsRaycaster和Physics2DRaycaster)的组件。
BaseRaycaster类将会通过方法OnEnable()调用RaycasterManager.AddRaycaster(this),将所有射线管理类BaseRaycaster注册到射线管理器RaycasterManager中统一管理。
RaycasterManager.GetRaycasters(); //获取所有的射线管理类
UGUI渲染组件有一个管理器GraphicRegistry。当继承自Graphic的ui渲染组件被实例化后将把这些渲染组件以此UI渲染组件所属的Canvas为key注册到管理器GraphicRegistry的词典中。词典定义如下:
private readonly Dictionary<Canvas, IndexedSet<Graphic>> m_Graphics = new Dictionary<Canvas, IndexedSet<Graphic>>();
从词典定义也会发现,每个键Canvas所对应的值Graphic列表正是每个Canvas所管理的渲染的各个UI渲染组件。
实事就是这样。
Graphic管理器 GraphicRegistry有一个方法,通过Canvas获取Canvas下所管理的所有UI渲染组件,方法如下,后面说明将会用到
public static IList<Graphic> GetGraphicsForCanvas(Canvas canvas)
GraphicRegistry.GetGraphicsForCanvas(canvas); //获取指定Canvas下的所有继承自Graphic基类的组件
eventSystem.RaycastAll(pointerData, m_RaycastResultCache); //获取指定屏幕事件的下射线穿过的所有继承自Graphic基类的所有组件。
var raycast = FindFirstRaycast(m_RaycastResultCache); //获取射线第一个穿过的有效组件
UGUI的渲染待续