UFL7-2——UI管理器

制作UI管理器的目的

方便管理所有UI面板,控制UI面板的显示隐藏,提供公共API供外部使用,比如:

  1. 显示面板
  2. 隐藏面板
  3. 获取面板
  4. 添加自定义事件

等等

UI管理器层级规划

主要思路:

  1. UI面板在任何场景都会显示,因此Canvas​和EventSystem​对象应该过场景不移除,并且保证唯一性和动态创建

    1. UI管理器 为 不继承MonoBehaviour的单例模式

      1
      2
      3
      4
      public class UIManager : BaseManager<UIManager>
      {
      private UIManager() { }
      }
    2. 在构造函数中动态创建设置好的 Canvas​ 和 EventSystem​ 预设体(如果使用了UI摄像机,也需要单独处理摄像机预设体)

      先在场景上创建 Canvas​ 和 EventSystem​ 以及 UI摄像机 预设体

      image

      然后,在UI管理器的构造函数内动态创建这三个预设体(方便在切换场景的情况下使用)

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

      public class UIManager : BaseManager<UIManager>
      {
      private Camera uiCamara;
      private Canvas uiCanvas;
      private EventSystem uiEventSystem;

      private UIManager()
      {
      //动态创建唯一的Canvas和EventSystem(摄像机)
      uiCamara = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/UICamera")).GetComponent<Camera>();
      //UI摄像机过场景不可移除,它专门用来渲染UI面板
      GameObject.DontDestroyOnLoad(uiCamara);
      //动态创建Canvas,并设置使用的UI摄像机,设置过场景不可移除
      uiCanvas = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/Canvas")).GetComponent<Canvas>();
      uiCanvas.worldCamera = uiCamara;
      GameObject.DontDestroyOnLoad(uiCanvas);
      uiEventSystem = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/EventSystem")).GetComponent<EventSystem>();
      GameObject.DontDestroyOnLoad(uiEventSystem);
      }
      }
  2. UI面板的显示可以存在层级(前后)关系,我们可以预先创建好层级对象,提供获取层级对象的方法

    1. Canvas​ 下创建好管理层级的子对象,之后面板作为对应层级对象的子对象达到分层作用

      image

    2. 提供获取层级对象的方法

      在构造函数内从Canvas​上获取四个层级对象,声明一个枚举代表四个层级,通过传入的枚举来返回对应的层级对象

      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
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      using UnityEngine;
      using UnityEngine.EventSystems;

      /// <summary>
      /// 层级枚举
      /// </summary>
      public enum E_UILayer
      {
      Bottom,
      Middle,
      Top,
      System,
      }

      public class UIManager : BaseManager<UIManager>
      {
      private Camera uiCamara;
      private Canvas uiCanvas;
      private EventSystem uiEventSystem;
      //层级父对象
      private Transform bottomLayer;
      private Transform middleLayer;
      private Transform topLayer;
      private Transform systemLayer;

      private UIManager()
      {
      //动态创建唯一的Canvas和EventSystem(摄像机)
      uiCamara = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/UICamera")).GetComponent<Camera>();
      //UI摄像机过场景不可移除,它专门用来渲染UI面板
      GameObject.DontDestroyOnLoad(uiCamara);
      //动态创建Canvas,并设置使用的UI摄像机,设置过场景不可移除
      uiCanvas = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/Canvas")).GetComponent<Canvas>();
      uiCanvas.worldCamera = uiCamara;
      GameObject.DontDestroyOnLoad(uiCanvas);
      //找到层级父对象
      bottomLayer = uiCanvas.transform.Find("Bottom");
      middleLayer = uiCanvas.transform.Find("Middle");
      topLayer = uiCanvas.transform.Find("Top");
      systemLayer = uiCanvas.transform.Find("System");
      //动态创建EventSystem
      uiEventSystem = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/EventSystem")).GetComponent<EventSystem>();
      GameObject.DontDestroyOnLoad(uiEventSystem);
      }

      /// <summary>
      /// 获取对应层级的父对象
      /// </summary>
      /// <param name="layer">层级枚举值</param>
      /// <returns>层级父对象</returns>
      public Transform GetLayerObj(E_UILayer layer)
      {
      switch (layer)
      {
      case E_UILayer.Bottom:
      return bottomLayer;
      case E_UILayer.Middle:
      return middleLayer;
      case E_UILayer.Top:
      return topLayer;
      case E_UILayer.System:
      return systemLayer;
      default:
      return null;
      }
      }
      }

具体实现

主要实现内容:

  1. 存储面板的容器

    1
    2
    3
    4
    /// <summary>
    /// 用于存储所有的面板对象
    /// </summary>
    private Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>();
  2. 显示面板

    由于面板的加载可能会不可避免的是异步的

    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
    /// <summary>
    /// 显示面板
    /// </summary>
    /// <typeparam name="T">面板的类型</typeparam>
    /// <param name="layer">面板要显示在哪个层级下</param>
    /// <param name="callBack">面板加载出来后的接收面板的回调函数</param>
    /// <param name="isSync">是否同步加载 默认为false</param>
    public void ShowPanel<T>(E_UILayer layer = E_UILayer.Middle, UnityAction<T> callBack = null, bool isSync = false) where T : BasePanel
    {
    //获取面板名,预设体名必须与面板类名一致
    string panelName = typeof(T).Name;
    //存在面板
    if (panelDic.ContainsKey(panelName))
    {
    panelDic[panelName].ShowMe();
    //直接执行回调,直接传递出去即可
    callBack?.Invoke(panelDic[panelName] as T);
    return;
    }
    //不存在面板
    ABResManager.Instance.LoadResAsync<GameObject>("ui", panelName, (res) =>
    {
    //层级的处理
    Transform layerObj = GetLayerObj(layer);
    //避免没有按照指定规则传递参数,避免为空
    if (layerObj == null)
    layerObj = middleLayer;
    //将面板预设体创建到对应父对象下,并且保持原本的缩放大小
    GameObject panelObj = GameObject.Instantiate(res, layerObj, false);
    T panel = panelObj.GetComponent<T>(); //获取对应的UI控件返回出去
    panel.ShowMe(); //显示面板时执行的默认方法
    callBack?.Invoke(panel); //传递到外部使用
    panelDic.Add(panelName, panel); //将显示出来的面板添加到字典内
    }, isSync);
    }
  3. 隐藏面板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /// <summary>
    /// 隐藏面板
    /// </summary>
    /// <typeparam name="T">面板名</typeparam>
    public void HidePanel<T>() where T : BasePanel
    {
    string panelName = typeof(T).Name;
    if (panelDic.ContainsKey(panelName))
    {
    //销毁后从容器中移除
    panelDic[panelName].HideMe();
    GameObject.Destroy(panelDic[panelName].gameObject);
    panelDic.Remove(panelName);
    }
    }
  4. 获取面板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /// <summary>
    /// 获取面板
    /// </summary>
    /// <typeparam name="T">面板名</typeparam>
    public T GetPanel<T>() where T : BasePanel
    {
    string panelName = typeof(T).Name;
    if (panelDic.ContainsKey(panelName))
    return panelDic[panelName] as T;
    return null;
    }

使用示例

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
using UnityEngine.UI;

public class BeginPanel : BasePanel
{
private void Start()
{
string name = GetControl<Button>("btnBegin").name;
print(name);
}

protected override void ClickButton(string btnName)
{
switch (btnName)
{
case "btnBegin":
print("开始按钮被点击");
break;
case "btnSetting":
print("设置按钮被点击");
break;
case "btnQuit":
print("退出按钮被点击");
break;
}
}

public override void ShowMe()
{
print("显示面板时 BeginPanel会执行的默认逻辑");
}

public override void HideMe()
{
print("隐藏面板时 BeginPanel会执行的默认逻辑");
}

public void TestFun()
{
print("想要执行BeginPanel的逻辑");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
UIManager.Instance.ShowPanel<BeginPanel>(E_UILayer.System, (panel) =>
{
panel.TestFun();
});
}
if (Input.GetKeyDown(KeyCode.H))
{
UIManager.Instance.HidePanel<BeginPanel>();
}
}

按下S键显示面板:image

显示面板时输出:image

隐藏面板时输出:image

抛出问题

  1. 如果同一帧连续显示同一个面板(可能导致重复加载面板的问题),应该如何处理?
  2. 如果不想要直接销毁面板,应该如何处理?
  3. 如果想要为控件添加一些非默认事件应该处理?

具体代码

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

/// <summary>
/// 层级枚举
/// </summary>
public enum E_UILayer
{
Bottom,
Middle,
Top,
System,
}

/// <summary>
/// 管理所有UI面板的管理器,注意:面板预设体名要和面板名一致
/// </summary>
public class UIManager : BaseManager<UIManager>
{
private Camera uiCamara;
private Canvas uiCanvas;
private EventSystem uiEventSystem;
//层级父对象
private Transform bottomLayer;
private Transform middleLayer;
private Transform topLayer;
private Transform systemLayer;

/// <summary>
/// 用于存储所有的面板对象
/// </summary>
private Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>();

private UIManager()
{
//动态创建唯一的Canvas和EventSystem(摄像机)
uiCamara = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/UICamera")).GetComponent<Camera>();
//UI摄像机过场景不可移除,它专门用来渲染UI面板
GameObject.DontDestroyOnLoad(uiCamara);
//动态创建Canvas,并设置使用的UI摄像机,设置过场景不可移除
uiCanvas = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/Canvas")).GetComponent<Canvas>();
uiCanvas.worldCamera = uiCamara;
GameObject.DontDestroyOnLoad(uiCanvas);
//找到层级父对象
bottomLayer = uiCanvas.transform.Find("Bottom");
middleLayer = uiCanvas.transform.Find("Middle");
topLayer = uiCanvas.transform.Find("Top");
systemLayer = uiCanvas.transform.Find("System");
//动态创建EventSystem
uiEventSystem = GameObject.Instantiate(ResManager.Instance.Load<GameObject>("UI/EventSystem")).GetComponent<EventSystem>();
GameObject.DontDestroyOnLoad(uiEventSystem);
}

/// <summary>
/// 获取对应层级的父对象
/// </summary>
/// <param name="layer">层级枚举值</param>
/// <returns>层级父对象</returns>
public Transform GetLayerObj(E_UILayer layer)
{
switch (layer)
{
case E_UILayer.Bottom:
return bottomLayer;
case E_UILayer.Middle:
return middleLayer;
case E_UILayer.Top:
return topLayer;
case E_UILayer.System:
return systemLayer;
default:
return null;
}
}

/// <summary>
/// 显示面板
/// </summary>
/// <typeparam name="T">面板的类型</typeparam>
/// <param name="layer">面板要显示在哪个层级下</param>
/// <param name="callBack">面板加载出来后的接收面板的回调函数</param>
/// <param name="isSync">是否同步加载 默认为false</param>
public void ShowPanel<T>(E_UILayer layer = E_UILayer.Middle, UnityAction<T> callBack = null, bool isSync = false) where T : BasePanel
{
//获取面板名,预设体名必须与面板类名一致
string panelName = typeof(T).Name;
//存在面板
if (panelDic.ContainsKey(panelName))
{
panelDic[panelName].ShowMe();
//直接执行回调,直接传递出去即可
callBack?.Invoke(panelDic[panelName] as T);
return;
}
//不存在面板
ABResManager.Instance.LoadResAsync<GameObject>("ui", panelName, (res) =>
{
//层级的处理
Transform layerObj = GetLayerObj(layer);
//避免没有按照指定规则传递参数,避免为空
if (layerObj == null)
layerObj = middleLayer;
//将面板预设体创建到对应父对象下,并且保持原本的缩放大小
GameObject panelObj = GameObject.Instantiate(res, layerObj, false);
T panel = panelObj.GetComponent<T>(); //获取对应的UI控件返回出去
panel.ShowMe(); //显示面板时执行的默认方法
callBack?.Invoke(panel); //传递到外部使用
panelDic.Add(panelName, panel); //将显示出来的面板添加到字典内
}, isSync);
}

/// <summary>
/// 隐藏面板
/// </summary>
/// <typeparam name="T">面板名</typeparam>
public void HidePanel<T>() where T : BasePanel
{
string panelName = typeof(T).Name;
if (panelDic.ContainsKey(panelName))
{
//销毁后从容器中移除
panelDic[panelName].HideMe();
GameObject.Destroy(panelDic[panelName].gameObject);
panelDic.Remove(panelName);
}
}

/// <summary>
/// 获取面板
/// </summary>
/// <typeparam name="T">面板名</typeparam>
public T GetPanel<T>() where T : BasePanel
{
string panelName = typeof(T).Name;
if (panelDic.ContainsKey(panelName))
return panelDic[panelName] as T;
return null;
}
}