UF_OLDL9——UI管理模块

注意!该UI模块是基于UGUI的! 请先学习UGUI再学习这里的内容

在游戏中,各种游戏对象可能会打开或者隐藏各种UI面板
如果让它们直接相互关联,那这些对象和面板的联系会变成错综复杂难以管理,也就是说程序的耦合性变高了
制作UI模块的目的,就是为了降低这种耦合性

首先,对于 游戏对象(也有可能是一个面板) 显示隐藏一个UI面板的需求
我们可以构建一个UI管理器,通过调用UI管理器来控制各个UI面板的显示隐藏

对于各个UI面板(Panel),
它们的共同点在于:它们需要管理控制自己面板内的各个控件,需要显示隐藏等
这时我们就可以抽象出这些共同行为,将其写入一个UI基类,让面板对象继承这个UI基类

UI基类

首先可以利用.GetComponentsInChildren<>()获取所有的组件(它们都有一个父类UIBehaviour),再用字典存储
我们可以通过输入控件对象名和控件类型来获取某个控件
其次,每个UI都有隐藏显示的方法,所以我们需要首先在基类声明显示和隐藏的方法,到之后具体到某个子类在重写

使用方法:
让UI面板类继承它

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

/// <summary>
/// 面板基类,可以找到所有自己面板下的控件对象,以及显示隐藏的方法
/// </summary>
public abstract class BasePanel : MonoBehaviour
{
private Dictionary<string, List<UIBehaviour>> controlDic = new Dictionary<string, List<UIBehaviour>>();

protected virtual void Awake()
{
FindChildrenControl<Button>();
FindChildrenControl<Toggle>();
FindChildrenControl<InputField>();
FindChildrenControl<Slider>();
FindChildrenControl<ScrollRect>();
FindChildrenControl<Dropdown>();
FindChildrenControl<Image>();
FindChildrenControl<Text>();
}

/// <summary>
/// 得到对应控件名字的控件脚本
/// </summary>
/// <typeparam name="T">控件类型</typeparam>
/// <param name="controlName">控件名</param>
/// <returns>控件脚本或者null</returns>
protected T GetControl<T>(string controlName) where T : UIBehaviour
{
if (controlDic.ContainsKey(controlName))
{
for (int i = 0; i < controlDic[controlName].Count; i++)
{
if (controlDic[controlName][i] is T)
return controlDic[controlName][i] as T;
}
}
return null;
}

/// <summary>
/// 找到子对象的对应控件
/// </summary>
/// <typeparam name="T">UI控件类型</typeparam>
private void FindChildrenControl<T>() where T : UIBehaviour
{
T[] controls = this.GetComponentsInChildren<T>();
for (int i = 0; i < controls.Length; i++)
{
if (controlDic.ContainsKey(controls[i].gameObject.name))
controlDic[controls[i].gameObject.name].Add(controls[i]);
controlDic.Add(controls[i].gameObject.name, new List<UIBehaviour>() { controls[i] });
}
}

/// <summary>
/// 显示面板
/// </summary>
public abstract void ShowMe();

/// <summary>
/// 隐藏面板
/// </summary>
public abstract void HideMe();
}

UI管理器

最基础的UI管理器

使用方法:
显示面板
隐藏面板

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public enum E_UILayer
{
Bottom,
Middle,
Top,
System,
}

public class UIManager : BaseManager<UIManager>
{
//当前正在显示的面板
private Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>();
//Canvas的各个层级
private Transform top;
private Transform middle;
private Transform bottom;
private Transform system;

public UIManager()
{
//创建Canvas 让其过场景的时候 不被移除
GameObject canvasObj = ResourcesManager.Instance().Load<GameObject>("UI/Canvas");
GameObject.DontDestroyOnLoad(canvasObj);
Transform canvas = canvasObj.transform;
GameObject.DontDestroyOnLoad(ResourcesManager.Instance().Load<GameObject>("UI/EventSystem"));
//找到各层级
top = canvas.Find("Top");
middle = canvas.Find("Middle");
bottom = canvas.Find("Bottom");
system = canvas.Find("System");
}

/// <summary>
/// 显示面板
/// </summary>
/// <typeparam name="T">面板脚本类型</typeparam>
/// <param name="panelName">面板名</param>
/// <param name="layer">显示在哪一层</param>
/// <param name="callback">当面板预设体创建成功后要执行的方法</param>
public void ShowPanel<T>(string panelName, E_UILayer layer = E_UILayer.Middle, UnityAction<T> callback = null) where T : BasePanel
{
if (panelDic.ContainsKey(panelName))
{
if (callback != null)
{
panelDic[panelName].ShowMe();
callback(panelDic[panelName] as T);
}

}
else
{
ResourcesManager.Instance().LoadAsync<GameObject>("UI/" + panelName, (panelObj) =>
{
//设置层级,并设置父对象,并设置大小
Transform father = bottom;
switch (layer)
{
case E_UILayer.Bottom:
father = bottom;
break;
case E_UILayer.Middle:
father = middle;
break;
case E_UILayer.Top:
father = top;
break;
case E_UILayer.System:
father = system;
break;
}
panelObj.transform.SetParent(father, false);
panelObj.transform.localPosition = Vector3.zero;
panelObj.transform.localScale = Vector3.one;
(panelObj.transform as RectTransform).offsetMax = Vector2.zero;
(panelObj.transform as RectTransform).offsetMin = Vector2.zero;
//获取面板上的面板脚本,如果面板脚本存在,执行传入的回调函数,并将面板加入到字典内管理
T panel = panelObj.GetComponent<T>();
if (panel != null)
callback(panel);
panel.ShowMe();
panelDic.Add(panelName, panel);
});
}
}

/// <summary>
/// 隐藏面板
/// </summary>
/// <param name="panelName">面板名</param>
public void HidePanel(string panelName)
{
if (panelDic.ContainsKey(panelName))
{
panelDic[panelName].HideMe();
GameObject.Destroy(panelDic[panelName].gameObject);
panelDic.Remove(panelName);
}
}
}

优化面板基类事件监听的UI模块

首先修正了UI管理器在显示过某个面板后再执行显示面板方法仍然会加载该面板的bug
其次是增加的获取面板和获取层级对象的方法

然后是优化面板控件添加事件监听的方法,
原来的实现虽然优化了拖曳控件这一步,但仍然要通过控件名一个一个添加方法
这次优化,向控件添加方法的一部分实现,直接合并到UI基类的FindChildrenControl()的方法里
即某一种控件都添加一个空的有参的虚方法,虚方法参数为控件名字(用于区分不同的控件)
之后为某一种控件添加方法,只需要重写那个虚方法即可,使用switch通过控件名字参数来执行不同逻辑

UI管理器(修正后的)

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public enum E_UILayer
{
Bottom,
Middle,
Top,
System,
}

public class UIManager : BaseManager<UIManager>
{
//当前正在显示的面板
private Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>();
//Canvas以及它的各个层级
public RectTransform canvas;
private Transform top;
private Transform middle;
private Transform bottom;
private Transform system;

public UIManager()
{
//创建Canvas 让其过场景的时候 不被移除
GameObject canvasObj = ResourcesManager.Instance().Load<GameObject>("UI/Canvas");
GameObject.DontDestroyOnLoad(canvasObj);
canvas = canvasObj.transform as RectTransform;
GameObject.DontDestroyOnLoad(ResourcesManager.Instance().Load<GameObject>("UI/EventSystem"));
//找到各层级
top = canvas.Find("Top");
middle = canvas.Find("Middle");
bottom = canvas.Find("Bottom");
system = canvas.Find("System");
}

/// <summary>
/// 显示面板
/// </summary>
/// <typeparam name="T">面板脚本类型</typeparam>
/// <param name="panelName">面板名</param>
/// <param name="layer">显示在哪一层</param>
/// <param name="callback">当面板预设体创建成功后要执行的方法</param>
public void ShowPanel<T>(string panelName, E_UILayer layer = E_UILayer.Middle, UnityAction<T> callback = null) where T : BasePanel
{
if (panelDic.ContainsKey(panelName))
{
if (callback != null)
{
panelDic[panelName].ShowMe();
callback(panelDic[panelName] as T);
}

}
else
{
ResourcesManager.Instance().LoadAsync<GameObject>("UI/" + panelName, (panelObj) =>
{
//设置层级,并设置父对象,并设置大小
Transform father = bottom;
switch (layer)
{
case E_UILayer.Bottom:
father = bottom;
break;
case E_UILayer.Middle:
father = middle;
break;
case E_UILayer.Top:
father = top;
break;
case E_UILayer.System:
father = system;
break;
}
panelObj.transform.SetParent(father, false);
panelObj.transform.localPosition = Vector3.zero;
panelObj.transform.localScale = Vector3.one;
(panelObj.transform as RectTransform).offsetMax = Vector2.zero;
(panelObj.transform as RectTransform).offsetMin = Vector2.zero;
//获取面板上的面板脚本,如果面板脚本存在,执行传入的回调函数,并将面板加入到字典内管理
T panel = panelObj.GetComponent<T>();
if (callback != null)
callback(panel);
panel.ShowMe();
panelDic.Add(panelName, panel);
});
}
}

/// <summary>
/// 隐藏面板
/// </summary>
/// <param name="panelName">面板名</param>
public void HidePanel(string panelName)
{
if (panelDic.ContainsKey(panelName))
{
panelDic[panelName].HideMe();
GameObject.Destroy(panelDic[panelName].gameObject);
panelDic.Remove(panelName);
}
}

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

/// <summary>
/// 获取对应层级的父对象
/// </summary>
/// <param name="layer">层级枚举</param>
/// <returns>层级对应的父对象</returns>
public Transform GetLayer(E_UILayer layer)
{
switch (layer)
{
case E_UILayer.Bottom:
return this.bottom;
case E_UILayer.Middle:
return this.middle;
case E_UILayer.Top:
return this.top;
case E_UILayer.System:
return this.system;
}
return null;
}
}

UI基类(优化了事件监听方法)

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

/// <summary>
/// 面板基类,可以找到所有自己面板下的控件对象,以及显示隐藏的方法
/// </summary>
public abstract class BasePanel : MonoBehaviour
{
private Dictionary<string, List<UIBehaviour>> controlDic = new Dictionary<string, List<UIBehaviour>>();

protected virtual void Awake()
{
FindChildrenControl<Button>();
FindChildrenControl<Toggle>();
FindChildrenControl<InputField>();
FindChildrenControl<Slider>();
FindChildrenControl<ScrollRect>();
FindChildrenControl<Dropdown>();
FindChildrenControl<Image>();
FindChildrenControl<Text>();
}

/// <summary>
/// 得到对应控件名字的控件脚本
/// </summary>
/// <typeparam name="T">控件类型</typeparam>
/// <param name="controlName">控件名</param>
/// <returns>控件脚本或者null</returns>
protected T GetControl<T>(string controlName) where T : UIBehaviour
{
if (controlDic.ContainsKey(controlName))
{
for (int i = 0; i < controlDic[controlName].Count; i++)
{
if (controlDic[controlName][i] is T)
return controlDic[controlName][i] as T;
}
}
return null;
}

/// <summary>
/// 找到子对象的对应控件
/// </summary>
/// <typeparam name="T">UI控件类型</typeparam>
private void FindChildrenControl<T>() where T : UIBehaviour
{
T[] controls = this.GetComponentsInChildren<T>();
for (int i = 0; i < controls.Length; i++)
{
string objName = controls[i].gameObject.name;
if (controlDic.ContainsKey(objName))
controlDic[objName].Add(controls[i]);
else
controlDic.Add(objName, new List<UIBehaviour>() { controls[i] });

if (controls[i] is Button)
{
(controls[i] as Button).onClick.AddListener(() =>
{
OnClick(objName);
});
}
else if (controls[i] is Toggle)
{
(controls[i] as Toggle).onValueChanged.AddListener((value) =>
{
OnValueChanged(objName, value);
});
}
}
}

protected abstract void OnClick(string btnName);

protected abstract void OnValueChanged(string toggleName, bool value);

/// <summary>
/// 显示面板
/// </summary>
public virtual void ShowMe()
{

}

/// <summary>
/// 隐藏面板
/// </summary>
public virtual void HideMe()
{

}
}

UI基类使用示例

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

public class LoginPanel : BasePanel
{
public void InitInfo()
{
Debug.Log("初始化数据");
}

protected override void OnClick(string btnName)
{
switch (btnName)
{
case "btnStart":
Debug.Log("btnStart被点击");
break;
case "btnQuit":
Debug.Log("btnQuit被点击");
break;
}
}

protected override void OnValueChanged(string toggleName, bool value)
{
switch (toggleName) { }
}
}

自定义事件监听的UI模块

关于自定义事件监听请看这里 ——> EventTrigger

每次为EventTrigger添加自定义事件,都会写一些重复的代码,例如:声明Entry,修改entryID,添加回调函数等

我们可以将这些重复的代码在UI管理器写成一个方法,为UI控件添加自定义事件监听

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public enum E_UILayer
{
Bottom,
Middle,
Top,
System,
}

public class UIManager : BaseManager<UIManager>
{
//当前正在显示的面板
private Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>();
//Canvas以及它的各个层级
public RectTransform canvas;
private Transform top;
private Transform middle;
private Transform bottom;
private Transform system;

public UIManager()
{
//创建Canvas 让其过场景的时候 不被移除
GameObject canvasObj = ResourcesManager.Instance().Load<GameObject>("UI/Canvas");
GameObject.DontDestroyOnLoad(canvasObj);
canvas = canvasObj.transform as RectTransform;
GameObject.DontDestroyOnLoad(ResourcesManager.Instance().Load<GameObject>("UI/EventSystem"));
//找到各层级
top = canvas.Find("Top");
middle = canvas.Find("Middle");
bottom = canvas.Find("Bottom");
system = canvas.Find("System");
}

/// <summary>
/// 显示面板
/// </summary>
/// <typeparam name="T">面板脚本类型</typeparam>
/// <param name="panelName">面板名</param>
/// <param name="layer">显示在哪一层</param>
/// <param name="callback">当面板预设体创建成功后要执行的方法</param>
public void ShowPanel<T>(string panelName, E_UILayer layer = E_UILayer.Middle, UnityAction<T> callback = null) where T : BasePanel
{
if (panelDic.ContainsKey(panelName))
{
if (callback != null)
{
panelDic[panelName].ShowMe();
callback(panelDic[panelName] as T);
}

}
else
{
ResourcesManager.Instance().LoadAsync<GameObject>("UI/" + panelName, (panelObj) =>
{
//设置层级,并设置父对象,并设置大小
Transform father = bottom;
switch (layer)
{
case E_UILayer.Bottom:
father = bottom;
break;
case E_UILayer.Middle:
father = middle;
break;
case E_UILayer.Top:
father = top;
break;
case E_UILayer.System:
father = system;
break;
}
panelObj.transform.SetParent(father, false);
panelObj.transform.localPosition = Vector3.zero;
panelObj.transform.localScale = Vector3.one;
(panelObj.transform as RectTransform).offsetMax = Vector2.zero;
(panelObj.transform as RectTransform).offsetMin = Vector2.zero;
//获取面板上的面板脚本,如果面板脚本存在,执行传入的回调函数,并将面板加入到字典内管理
T panel = panelObj.GetComponent<T>();
if (callback != null)
callback(panel);
panel.ShowMe();
panelDic.Add(panelName, panel);
});
}
}

/// <summary>
/// 隐藏面板
/// </summary>
/// <param name="panelName">面板名</param>
public void HidePanel(string panelName)
{
if (panelDic.ContainsKey(panelName))
{
panelDic[panelName].HideMe();
GameObject.Destroy(panelDic[panelName].gameObject);
panelDic.Remove(panelName);
}
}

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

/// <summary>
/// 获取对应层级的父对象
/// </summary>
/// <param name="layer">层级枚举</param>
/// <returns>层级对应的父对象</returns>
public Transform GetLayer(E_UILayer layer)
{
switch (layer)
{
case E_UILayer.Bottom:
return this.bottom;
case E_UILayer.Middle:
return this.middle;
case E_UILayer.Top:
return this.top;
case E_UILayer.System:
return this.system;
}
return null;
}

/// <summary>
/// 向某个控件添加自定义监听事件
/// </summary>
/// <param name="control">要添加自定义监听事件控件的控件对象</param>
/// <param name="type">自定义监听事件类型</param>
/// <param name="callback">监听事件的相应函数</param>
public static void AddCustomEventTrigger(UIBehaviour control, EventTriggerType type, UnityAction<BaseEventData> callback)
{
EventTrigger trigger = control.GetComponent<EventTrigger>();
if (trigger == null)
trigger = control.gameObject.AddComponent<EventTrigger>();

EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = type;
entry.callback.AddListener(callback);

trigger.triggers.Add(entry);
}
}

使用示例

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class LoginPanel : BasePanel
{
private void Start()
{
UIManager.AddCustomEventTrigger(GetControl<Button>("btnStart"), EventTriggerType.PointerEnter, (data) =>
{
Debug.Log("进入");
});
UIManager.AddCustomEventTrigger(GetControl<Button>("btnStart"), EventTriggerType.PointerExit, (data) =>
{
Debug.Log("离开");
});
}


private void Drag(BaseEventData data)
{

}

public void InitInfo()
{
Debug.Log("初始化数据");
}

protected override void OnClick(string btnName)
{
switch (btnName)
{
case "btnStart":
Debug.Log("btnStart被点击");
break;
case "btnQuit":
Debug.Log("btnQuit被点击");
break;
}
}

protected override void OnValueChanged(string toggleName, bool value)
{

}
}