ZMUIL2——遮罩系统

遮罩系统要做的工作

开启单遮模式时,每当调用UIModule​来控制UI窗口显示隐藏时,
UIModule​的遮罩系统都要计算显示的窗口中哪个窗口需要开启遮罩,并隐藏其他窗口的遮罩,以达到单遮的效果

UI配置文件可设定是否开启单遮模式

  • 单遮模式:无论打开多少界面,遮罩只有一层。
  • 叠遮模式:即每一个界面都有一层遮罩,打开的界面越多,遮罩就越黑。

UIModule的遮罩系统相关

配置文件

首先需要配置文件来控制遮罩系统的开关,使用继承ScriptableObject​的单例配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using UnityEngine;

[CreateAssetMenu(fileName = "UISetting", menuName = "UISetting", order = 0)]
public class UISetting : ScriptableObject
{
private static UISetting instance;
public static UISetting Instance
{
get
{
if (instance == null)
{
instance = Resources.Load<UISetting>("UISetting");
}
return instance;
}
}

public bool SINGMASK_SYSTEM; //是否启用单遮模式
}

右键创建UISetting配置文件,即可在选择是否开启单遮模式

image

UI对象结构

为了便于UIModule​管理各个窗口的遮罩,窗口对象的基本结构规定为:

image

其中,UIMask对象为一个覆盖全屏的黑色Image,黑色Image的透明度由CanvasGroup决定

简单来说,一个Window模板对象会包括UIMask对象作为背景遮罩UIContent作为所有UI控件的父对象

值得一提的是:

  • 使用这里的UIMask遮罩对象会使用Canvas Group进行控制遮罩的显示与隐藏
    不使用SetActive​设置的原因是,使用它会造成UI对象重绘,浪费性能
  • 这里创建UIContent作为UI控件父节点的原因是,便于缩放动画的制作,
    可以通过缩放UIContent来制作缩放动画,同时也避免UIMask也跟着缩放。

遮罩系统思路及其实现

遮罩系统主要在UI窗口显示隐藏时工作,而UIModule​负责窗口显隐的执行,因此遮罩系统主要在UIModule​内实现

首先需要在WindowBase​内实现对UIMask显隐的控制,并对UIModule​提供控制窗口UIMask显隐的接口

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
39
40
41
42
public class WindowBase : WindowBehaviour
{
//常用UI控件管理列表
//...

private CanvasGroup mUIMask; //窗口的遮罩
protected Transform mUIContent; //窗口的UI控件父节点

//初始化基类组件
private void InitializeBaseComponent()
{
mUIMask = transform.Find("UIMask").GetComponent<CanvasGroup>();
mUIContent = transform.Find("UIContent").transform;
}

#region 生命周期函数的声明
public override void OnAwake()
{
base.OnAwake();
InitializeBaseComponent();
}

//...
#endregion

public override void SetVisible(bool isVisible)
{
//...
}

// 控制遮罩是否生效,仅在单遮模式开启时有效
public void SetMaskVisible(bool isVisible)
{
//如果未开启单遮模式,则直接返回
if (!UISetting.Instance.SINGMASK_SYSTEM) return;
mUIMask.alpha = isVisible ? 1f : 0f;
}

#region 事件管理方法
//...
#endregion
}

接下来需要在UIModule​实现一套调节UI遮罩的算法,原理较复杂,使用伪代码阐述思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
目的:通过调用WindowBase类的SetMaskVisible方法开启唯一符合条件的窗口的遮罩,关闭所有不符合条件的窗口的遮罩,实现单遮效果
符合开启遮罩的窗口的条件:窗口的Canvas的sortingOrder最大时,位于兄弟窗口排序最靠后的窗口

private void 设置窗口遮罩()
{
if 未开启单遮模式
return
var 要开启遮罩的窗口 = null
for 遍历所有的可见的窗口
if 遍历到的窗口不为空且对应的UI对象也不为空
关闭当前循环到的窗口的遮罩
if 要开启遮罩的窗口 == null
要开启遮罩的窗口 = 当前循环到的窗口
else
if 要开启遮罩的窗口的渲染层级 < 当前循环到的窗口的渲染层级
要开启遮罩的窗口 = 当前循环到的窗口
else if 要开启遮罩的窗口的渲染层级 == 当前循环到的窗口的渲染层级 && 要开启遮罩的窗口比当前循环到的窗口在同层级排序中更靠下
要开启遮罩的窗口 = 当前循环到的窗口
if 要开启遮罩的窗口不为空
开启唯一符合条件的窗口的遮罩
}

SetWindowMaskVisible()​会在UIModule​的弹出窗口,隐藏窗口,销毁窗口里调用,达到每次显示隐藏面板时都会计算哪个面板需要开启遮罩,实现单遮

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
39
40
41
42
43
44
45
46
47
48
//UIModule内

#region 控制遮罩相关
//单遮模式下,设置窗口遮罩
private void SetWindowMaskVisible()
{
//如果未开启单遮模式,则直接返回
if (!UISetting.Instance.SINGMASK_SYSTEM) return;
WindowBase maxOrderWindowBase = null; //渲染层级最大的窗口
int maxOrder = 0; //渲染层级最大窗口的渲染层级
int maxIndex = 0; //最大排序下标,在相同父节点下的位置下标
//1. 关闭所有窗口的Mask,设置为不可见
//2. 从所有可见窗口中,找到一个层级最大的窗口,设置Mask为可见
for (int i = 0; i < mVisibleWindowList.Count; i++)
{
WindowBase window = mVisibleWindowList[i];
//当窗口管理类不为空且游戏对象不为空时
if (window != null && window.gameObject != null)
{
//先关闭遮罩
window.SetMaskVisible(false);
if (maxOrderWindowBase == null)
{
maxOrderWindowBase = window;
maxOrder = window.Canvas.sortingOrder;
maxIndex = window.transform.GetSiblingIndex();
}
else
{
//找到最大渲染层级的窗口,获取它
if (maxOrder < window.Canvas.sortingOrder)
{
maxOrderWindowBase = window;
maxOrder = window.Canvas.sortingOrder;
}
//如果两个窗口的渲染层级相同,就找到同节点下最靠下的物体,优先渲染这个最靠下的Mask
else if (maxOrder == window.Canvas.sortingOrder && maxIndex < window.transform.GetSiblingIndex())
{
maxOrderWindowBase = window;
maxIndex = window.transform.GetSiblingIndex();
}
}
}
}
//遍历完所有窗口后,得到层级最大且同节点最靠下的窗口,只开启这一个最大的窗口即可
maxOrderWindowBase?.SetMaskVisible(true);
}
#endregion