CanvasGroup 组件中的属性是如何被应用的?

前言

CanvasGroup 组件用来调整一组 UI 元素的部分属性,这个组件的出现就是为了方便开发者实现一些批量更新操作,而不用单独修改多个 UI 元素的属性。

Alpha

调整当前 group 下面所有 UI 元素的透明度,UI 元素自身的透明度值不会改变,但是最终的透明度是当前设置的 Alpha 和 UI 元素透明度乘积。

Interactable

设定当前 group 下的 UI 元是否响应输入事件(可以交互)。

对于这个配置,我们以 Button 为例来分析这个属性是如何生效的。对于 Button 组件来说,当它响应自定义绑定的函数 OnClick,是在接收到 EventSystem 发送的 Press 事件,代码如下:

1
2
3
4
5
6
7
8
private void Press()
{
if (!IsActive() || !IsInteractable())
return;
UISystemProfilerApi.AddMarker("Button.onClick", this);
m_OnClick.Invoke();
}

从代码中可以看出,当 Button 组件不是激活状态或者是不响应交互状态,那么就不会执行回调函数,这里我们主要看 IsInteractable() 方法。

Button 类继承自 Selectable 类,它没有覆写IsInteractable() 方法,所以看Selectable 类中该方法的实现:

1
2
3
4
public virtual bool IsInteractable()
{
return m_GroupsAllowInteraction && m_Interactable;
}

该方法实现很简单,当 CanvasGroup 组件的 Interactable 为 true 且自身的 Interactable 设置也为 true,那么当前的 Button 就能响应交互事件。

那么 CanvasGroup 组件的 Interactable 配置是如何赋值到 Button 组件的 m_GroupsAllowInteraction 成员变量中的了?Selectable 类继承自 UIBehaviour 类,因此当有 CanvasGroup 组件配置更新时,OnCanvasGroupChanged() 方法会被回调(仅能够影响这个组件的 Canvas Group 更新才会收到回调,比如父节点或自身节点上的 Canvas Group 更新),正是在这个方法里面,CanvasGroup 组件的 Interactable 配置被更新到了 Button 组件的 m_GroupsAllowInteraction 变量中。代码如下:

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
protected override void OnCanvasGroupChanged()
{
var groupAllowInteraction = true;
Transform t = transform;
while (t != null)
{
t.GetComponents(m_CanvasGroupCache);
bool shouldBreak = false;
for (var i = 0; i < m_CanvasGroupCache.Count; i++)
{
if (!m_CanvasGroupCache[i].interactable)
{
groupAllowInteraction = false;
shouldBreak = true;
}
// do 'ignoreParentGroups' code...
}
if (shouldBreak)
break;
// other code...
}
// 更新 CanvasGroup 的 Interactable 到 m_GroupsAllowInteraction
if (groupAllowInteraction != m_GroupsAllowInteraction)
{
m_GroupsAllowInteraction = groupAllowInteraction;
OnSetProperty();
}
}

Block Raycasts

UI 元素是否接收射线检测,在 Event System 中检测事件拦截对象时使用。

这里还是以 Button 组件为例分析。在 Event System 射线检测拦截事件对象这一步时,对于 UI 元素的检测会使用到 Graphic 的 Raycast(Vector2 sp, Camera eventCamera) 方法,其中就根据这个值判断了射线检测是否成功,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var filter = components[i] as ICanvasRaycastFilter;
if (filter == null)
continue;
var raycastValid = true;
var group = components[i] as CanvasGroup;
if (group != null)
{
// other code...
raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
}
// other code...
if (!raycastValid)
{
ListPool<Component>.Release(components);
return false;
}

由于 Button 类及其祖先类都没有实现 ICanvasRaycastFilter 接口,而 CanvasGroup 类实现了;当在 Button 对象上添加 CanvasGroup 组件,raycastValid 结果就由 CanvasGroup 类的 IsRaycastLocationValid() 方法决定,CanvasGroup 类中的 IsRaycastLocationValid() 方法返回值就是设置的 Block Raycasts,所以当 Block Raycasts 设置为 true,则会通过射线检测。当然在上面只是 Graphic 类中射线检测代码的不部分,要通过完整的检测还包括其它条件判断以及整个节点链的测试通过等,具体可阅读 Unity Raycasters 剖析 一文。

Ignore Parent Groups

忽略祖先节点对象中 CanvasGroup 组件的设置。

如果当前 CanvasGroup 所在对象的父节点或是祖先节点也绑定了 CanvasGroup 组件,那么它们的 CanvasGroup 组件设置也会影响当前对象的行为。

这里我们接着以 Button 射线检测为例,看看 Graphic 类中的射线检测另一部分代码:

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
var t = transform;
var components = ListPool<Component>.Get();
bool ignoreParentGroups = false;
while (t != null)
{
t.GetComponents(components);
for (var i = 0; i < components.Count; i++)
{
// other code...
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);
}
if (!raycastValid) return false;
}
t = continueTraversal ? t.parent : null;
}
// other code...
return true;

当 Button 对象绑定了 CanvasGroup 组件并且设置其 Ignore Parent Groups 为 true,那么上面代码中变量 raycastValid 的值就只由当前 CanvasGroup 组件设置的 Block Raycasts 值决定;否则如果设置的 Ignore Parent Groups 为 false,那么变量 raycastValid 的值会一层层向上冒泡去求得。

所以 Ignore Parent Groups 这个配置会忽略祖先节点的 CanvasGroup 组件配置。