UFL7-2——UI管理器
UFL7-2——UI管理器
制作UI管理器的目的
方便管理所有UI面板,控制UI面板的显示隐藏,提供公共API供外部使用,比如:
- 显示面板
- 隐藏面板
- 获取面板
- 添加自定义事件
等等
UI管理器层级规划
主要思路:
-
UI面板在任何场景都会显示,因此
Canvas
和EventSystem
对象应该过场景不移除,并且保证唯一性和动态创建-
UI管理器 为 不继承MonoBehaviour的单例模式
1
2
3
4public class UIManager : BaseManager<UIManager>
{
private UIManager() { }
} -
在构造函数中动态创建设置好的
Canvas
和EventSystem
预设体(如果使用了UI摄像机,也需要单独处理摄像机预设体)先在场景上创建
Canvas
和EventSystem
以及 UI摄像机 预设体
然后,在UI管理器的构造函数内动态创建这三个预设体(方便在切换场景的情况下使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23using 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);
}
}
-
-
UI面板的显示可以存在层级(前后)关系,我们可以预先创建好层级对象,提供获取层级对象的方法
-
在
Canvas
下创建好管理层级的子对象,之后面板作为对应层级对象的子对象达到分层作用
-
提供获取层级对象的方法
在构造函数内从
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
67using 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
2
3
4/// <summary>
/// 用于存储所有的面板对象
/// </summary>
private Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>(); -
显示面板
由于面板的加载可能会不可避免的是异步的
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);
} -
隐藏面板
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);
}
} -
获取面板
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 | using UnityEngine.UI; |
1 | void Update() |
按下S键显示面板:
显示面板时输出:
隐藏面板时输出:
抛出问题
- 如果同一帧连续显示同一个面板(可能导致重复加载面板的问题),应该如何处理?
- 如果不想要直接销毁面板,应该如何处理?
- 如果想要为控件添加一些非默认事件应该处理?
具体代码
1 | using System.Collections.Generic; |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 文KRIFE齐的博客!