原文链接:http://www.ceeger.com/forum/read.php?tid=21745&fid=2
发布一个基于NGUI编写的UI框架
1.加载,显示,隐藏,关闭页面,根据标示获得相应界面实例
2.提供界面显示隐藏动画接口
3.单独界面层级,Collider,背景管理
4.根据存储的导航信息完成界面导航
5.界面通用对话框管理(多类型Message Box)
6.便于进行需求和功能扩展(比如,在跳出页面之前添加逻辑处理等)
目标:编写一个简单通用UI框架用于管理页面和完成导航跳转
最终的实现效果和Demo请拉到最下方回复查看
框架具体实现的功能和需求
- 加载,显示,隐藏,关闭页面,根据标示获得相应界面实例
- 提供界面显示隐藏动画接口
- 单独界面层级,Collider,背景管理
- 根据存储的导航信息完成界面导航
- 界面通用对话框管理(多类型Message Box)
- 便于进行需求和功能扩展(比如,在跳出页面之前添加逻辑处理等)
- 打开,关闭,层级,页面跳转等管理问题集中化,将外部切换等逻辑交给UIManager处理
- 功能逻辑分散化,每个页面维护自身逻辑,依托于框架便于多人协同开发,不用关心跳转和显示关闭细节
- 通用性框架能够做到简单的代码复用和"项目经验"沉淀
- 窗口类设计:基本窗口对象,维护自身逻辑维护
- 窗口管理类:控制被管理窗口的打开和关闭等逻辑(具体设计请看下文)
- 动画接口:提供打开和关闭动画接口,提供动画完成回调函数等
- 层级,Collider背景管理
框架中设计的窗口类型和框架所需定义如下
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
enum
UIWindowType
{
Normal,
// 可推出界面(UIMainMenu,UIRank等)
Fixed,
// 固定窗口(UITopBar等)
PopUp,
// 模式窗口
}
public
enum
UIWindowShowMode
{
DoNothing,
HideOther,
// 闭其他界面
NeedBack,
// 点击返回按钮关闭当前,不关闭其他界面(需要调整好层级关系)
NoNeedBack,
// 关闭TopBar,关闭其他界面,不加入backSequence队列
}
public
enum
UIWindowColliderMode
{
None,
// 显示该界面不包含碰撞背景
Normal,
// 碰撞透明背景
WithBg,
// 碰撞非透明背景
}
|
复制代码
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
158
159
160
161
|
using
UnityEngine;
using
System.Collections;
using
System;
namespace
CoolGame
{
/// <summary>
/// 窗口基类
/// </summary>
public
class
UIBaseWindow : MonoBehaviour
{
protected
UIPanel originPanel;
// 如果需要可以添加一个BoxCollider屏蔽事件
private
bool
isLock =
false
;
protected
bool
isShown =
false
;
// 当前界面ID
protected
WindowID windowID = WindowID.WindowID_Invaild;
// 指向上一级界面ID(BackSequence无内容,返回上一级)
protected
WindowID preWindowID = WindowID.WindowID_Invaild;
public
WindowData windowData =
new
WindowData();
// Return处理逻辑
private
event
BoolDelegate returnPreLogic =
null
;
protected
Transform mTrs;
protected
virtual
void
Awake()
{
this
.gameObject.SetActive(
true
);
mTrs =
this
.gameObject.transform;
InitWindowOnAwake();
}
private
int
minDepth = 1;
public
int
MinDepth
{
get
{
return
minDepth; }
set
{ minDepth = value; }
}
/// <summary>
/// 能否添加到导航数据中
/// </summary>
public
bool
CanAddedToBackSeq
{
get
{
if
(
this
.windowData.windowType == UIWindowType.PopUp)
return
false
;
if
(
this
.windowData.windowType == UIWindowType.Fixed)
return
false
;
if
(
this
.windowData.showMode == UIWindowShowMode.NoNeedBack)
return
false
;
return
true
;
}
}
/// <summary>
/// 界面是否要刷新BackSequence数据
/// 1.显示NoNeedBack或者从NoNeedBack显示新界面 不更新BackSequenceData(隐藏自身即可)
/// 2.HideOther
/// 3.NeedBack
/// </summary>
public
bool
RefreshBackSeqData
{
get
{
if
(
this
.windowData.showMode == UIWindowShowMode.HideOther
||
this
.windowData.showMode == UIWindowShowMode.NeedBack)
return
true
;
return
false
;
}
}
/// <summary>
/// 在Awake中调用,初始化界面(给界面元素赋值操作)
/// </summary>
public
virtual
void
InitWindowOnAwake()
{
}
/// <summary>
/// 获得该窗口管理类
/// </summary>
public
UIManagerBase GetWindowManager
{
get
{
UIManagerBase baseManager =
this
.gameObject.GetComponent<UIManagerBase>();
return
baseManager;
}
private
set
{ }
}
/// <summary>
/// 重置窗口
/// </summary>
public
virtual
void
ResetWindow()
{
}
/// <summary>
/// 初始化窗口数据
/// </summary>
public
virtual
void
InitWindowData()
{
if
(windowData ==
null
)
windowData =
new
WindowData();
}
public
virtual
void
ShowWindow()
{
isShown =
true
;
NGUITools.SetActive(
this
.gameObject,
true
);
}
public
virtual
void
HideWindow(Action action =
null
)
{
IsLock =
true
;
isShown =
false
;
NGUITools.SetActive(
this
.gameObject,
false
);
if
(action !=
null
)
action();
}
public
void
HideWindowDirectly()
{
IsLock =
true
;
isShown =
false
;
NGUITools.SetActive(
this
.gameObject,
false
);
}
public
virtual
void
DestroyWindow()
{
BeforeDestroyWindow();
GameObject.Destroy(
this
.gameObject);
}
protected
virtual
void
BeforeDestroyWindow()
{
}
/// <summary>
/// 界面在退出或者用户点击返回之前都可以注册执行逻辑
/// </summary>
protected
void
RegisterReturnLogic(BoolDelegate newLogic)
{
returnPreLogic = newLogic;
}
public
bool
ExecuteReturnLogic()
{
if
(returnPreLogic ==
null
)
return
false
;
else
return
returnPreLogic();
}
}
}
|
界面可以继承该接口进行实现打开和关闭动画
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/// <summary>
/// 窗口动画
/// </summary>
interface
IWindowAnimation
{
/// <summary>
/// 显示动画
/// </summary>
void
EnterAnimation(EventDelegate.Callback onComplete);
/// <summary>
/// 隐藏动画
/// </summary>
void
QuitAnimation(EventDelegate.Callback onComplete);
/// <summary>
/// 重置动画
/// </summary>
void
ResetAnimation();
}
|
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
void
EnterAnimation(EventDelegate.Callback onComplete)
{
if
(twAlpha !=
null
)
{
twAlpha.PlayForward();
EventDelegate.Set(twAlpha.onFinished, onComplete);
}
}
public
void
QuitAnimation(EventDelegate.Callback onComplete)
{
if
(twAlpha !=
null
)
{
twAlpha.PlayReverse();
EventDelegate.Set(twAlpha.onFinished, onComplete);
}
}
public
override
void
ResetWindow()
{
base
.ResetWindow();
ResetAnimation();
}
|
导航功能实现通过一个显示窗口堆栈实现,每次打开和关闭窗口通过判断窗口属性和类型更新处理BackSequence数据
- 打开界面:将当前界面状态压入堆栈中更新BackSequence数据
- 返回操作(主动关闭当前界面或者点击返回按钮):从堆栈中Pop出一个界面状态,将相应的界面重新打开
- 怎么衔接:比如从一个界面没有回到上一个状态而是直接的跳转到其他的界面,这个时候需要将BackSequence清空因为当前的导航链已经被破坏,当BackSequence为空需要根据当前窗口指定的PreWindowId告知系统当从该界面返回,需要到达的指定页面,这样就能解决怎么衔接的问题,如果没断,继续执行导航,否则清空数据,根据PreWindowId进行导航
游戏中可以存在多个的Manager进行管理(一般在很少需求下才会使用),每个管理对象需要维护自己的导航信息BackSequence,每次退出一个界面需要检测当前退出的界面是否存在相应的Manager管理,如果存在则需要先执行Manager退出操作(退出过程分步进行)保证界面一层接着一层正确退出
窗口层级,Collider,统一背景添加如何实现?
有很多方式进行层级管理,该框架选择的方法如下
- 设置三个常用层级Root,根据窗口类型在加载到游戏中时添加到对应的层级Root下面即可,每次添加重新计算设置层级(通过UIPanel的depth实现)保证每次打开一个新窗口层级显示正确,每次窗口内通过depth的大小区分层级关系
- 根据窗口Collider和背景类型,在窗口的最小Panel上面添加Collider或者带有碰撞体的BackGround即可
具体实现如下:
复制代码
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
|
private
void
AdjustBaseWindowDepth(UIBaseWindow baseWindow)
{
UIWindowType windowType = baseWindow.windowData.windowType;
int
needDepth = 1;
if
(windowType == UIWindowType.Normal)
{
needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UINormalWindowRoot.gameObject,
false
) + 1, normalWindowDepth,
int
.MaxValue);
Debug.Log(
"[UIWindowType.Normal] maxDepth is "
+ needDepth + baseWindow.GetID);
}
else
if
(windowType == UIWindowType.PopUp)
{
needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UIPopUpWindowRoot.gameObject) + 1, popUpWindowDepth,
int
.MaxValue);
Debug.Log(
"[UIWindowType.PopUp] maxDepth is "
+ needDepth);
}
else
if
(windowType == UIWindowType.Fixed)
{
needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UIFixedWidowRoot.gameObject) + 1, fixedWindowDepth,
int
.MaxValue);
Debug.Log(
"[UIWindowType.Fixed] max depth is "
+ needDepth);
}
if
(baseWindow.MinDepth != needDepth)
GameUtility.SetTargetMinPanel(baseWindow.gameObject, needDepth);
baseWindow.MinDepth = needDepth;
}
/// <summary>
/// 窗口背景碰撞体处理
/// </summary>
private
void
AddColliderBgForWindow(UIBaseWindow baseWindow)
{
UIWindowColliderMode colliderMode = baseWindow.windowData.colliderMode;
if
(colliderMode == UIWindowColliderMode.None)
return
;
if
(colliderMode == UIWindowColliderMode.Normal)
GameUtility.AddColliderBgToTarget(baseWindow.gameObject,
"Mask02"
, maskAtlas,
true
);
if
(colliderMode == UIWindowColliderMode.WithBg)
GameUtility.AddColliderBgToTarget(baseWindow.gameObject,
"Mask02"
, maskAtlas,
false
);
}
|
这个应该是项目中一定会用到的功能,说下该框架简单的实现
- 三个按钮三种回调逻辑:左中右三个按钮,提供设置内容,设置回调函数的接口即可
- 提供接口设置核心Content
- 不同作用下不同的按钮不会隐藏和显示
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
void
SetCenterBtnCallBack(
string
msg, UIEventListener.VoidDelegate callBack)
{
lbCenter.text = msg;
NGUITools.SetActive(btnCenter,
true
);
UIEventListener.Get(btnCenter).onClick = callBack;
}
public
void
SetLeftBtnCallBack(
string
msg, UIEventListener.VoidDelegate callBack)
{
lbLeft.text = msg;
NGUITools.SetActive(btnLeft,
true
);
UIEventListener.Get(btnLeft).onClick = callBack;
}
public
void
SetRightBtnCallBack(
string
msg, UIEventListener.VoidDelegate callBack)
{
lbRight.text = msg;
NGUITools.SetActive(btnRight,
true
);
UIEventListener.Get(btnRight).onClick = callBack;
}
|
- 图集管理,针对大中型游戏对游戏内存要求苛刻的项目,一般都会对UI图集贴图资源进行动态管理,加载和卸载图集,保证UI贴图占用较少内存
- 增加一些通用处理:变灰操作,Mask遮罩(一般用于新手教程中)等
- 在进行切换的过程可以需要Load新场景需求,虽然这个也可以在UI框架外实现
- 对话系统也算是UI框架的功能,新手引导系统也可以加入到UI框架中,统一管理和处理新手引导逻辑
实现效果
整个框架的核心部分介绍完毕,有需要的朋友感兴趣的朋友可以下载参考下,希望能够给耐心看到结尾的朋友一点启发或者带来一点帮助,存在错误和改进的地方也希望留言交流共同进步学习~有些时候,我们总是知道这么个理明白该怎样实现,但是关键的就是要动手实现出来,实现的过程会发现自己的想法在慢慢优化,不断的需求和bug的产生让框架慢慢成熟,可以投入项目使用提升一些开发效率和减少工作量。
希望能够帮助到大家~
UI框架介绍
GitHub地址: https://github.com/tinyantstudio/UIFrameWork
持续更新,新的想法,针对新的需求框架进行扩展的优化~希望感兴趣的朋友一起针对游戏开发一起交流和学习~issues~