UI框架设计

思考并回答以下问题:

  • 画时序图和UML图是最好的学习项目,理清思路的方式。
  • UI的Panel有三大类可以区分,1个是位置类型,1个是显示类型,1个是透明度。自身又有不同的生命周期。这几大类都分别有哪些子分类?别的非UI游戏对象可以这样分吗?
  • 为什么3大类在常量定义脚本中定义,而生命周期在BaseUIForm中定义?用什么结构定义?

本章涵盖:

  • 设计理念
  • 框架结构图
  • 最简版本
  • 窗体层级管理
  • 模态窗体管理
  • 日志调试与配置管理
  • 资源国际化

设计理念

没有UI框架容易出现的问题探讨

  • 以ARPG为例,多个场景会反复出现相同的“UI窗体”,造成多个场景中反复加载相同的UI窗体。例如最上层的血量红条,魔法蓝条,头像框。
  • 开发商业复杂项目时,各个UI(UI脚本)之间传值,容易出现“紧耦合”(相互交错,你中有我,我中有你,即互相引用)的情况。
  • 卡牌、RPG等游戏类型项目,很多情况下会出现“UI窗体”叠加现象。开发人员需要“手工”维护窗体中间的层级关系。先弹出商城,又弹出“是否确定购买”,就是3层了。
  • 商业开发项目中的多个“UI窗体”之间叠加出现时,必须保持“模态窗口”类型,否则容易出现误操作。

解决方案:

  • 1、“UI框架”需要缓存项目中常用的“UI窗体”。
  • 2、各个UI的生成、销毁、切换,都是通过框架(Manager)实现,各个UI之间不直接联系(传值)。备注说明:项目中“单例模式”传值,只适合框架内部,或者小型项目中使用。因为单例也是紧耦合。
  • 3、设计UI框架系统,使用“栈”的数据结构,保存与控制当前所有需要显示的“UI窗体”的层级关系。
  • 4、设计的框架本身,需要对当前显示的窗体做“遮挡处理”,即:不允许用户绕过当前“UI窗体”直接操作底层窗体,或者误操作点击项目中的3D游戏对象等。(两种策略:使用碰撞体或使用一个面板挡一下)。

从以上问题还可以推导出如下“UI框架”需要注意的设计问题:

  • UI框架,需要管理加载“窗体预设”,进行自动加载的管理。
  • UI框架,需要支持不同的语言环境,即语言的国际化。
  • 最后设计UI框架一个总的核心原则是:尽量让框架本身完成与具体业务无关的事务性工作,让开发人员只需要专注游戏业务逻辑的开发即可。(这个原则同样适用于其他框架的设计中)。

框架结构图

最简版本

开发最简版本功能设计:

  • 1:窗体自动加载管理。(Resource.Load预设)
  • 2:缓存UI窗体。(写入到Dictionary)
  • 3:窗体生命周期管理。

UI框架的核心类设计

  • 1:BaseUIForms 基础UI窗体(父类)
  • 2:UIManger.cs UI窗体管理器(核心)
  • 3:UIType 窗体类型
  • 4:SysDefine 系统定义类

建立必要的目录结构与核心类,导入素材。

BaseUIForms.cs
UIManager.cs
UIType.cs
SysDefine.cs [Config目录下]

建立框架中的三个重要枚举类型,在SysDefine中定义UIType类。

  • UIFormsType UI窗体(位置)类型
  • UIFormsShowMode UI窗体显示类型
  • UIFormsLucencyType 窗体透明度类型

SysDefine.cs

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
/***
*
* Title: "SUIFW" UI框架项目
* 主题: 框架核心参数
* Description:
* 功能:
* 1: 系统常量
* 2: 全局性方法。
* 3: 系统枚举类型
* 4: 委托定义
*
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SUIFW
{
#region 系统枚举类型

/// <summary>
/// UI窗体(位置)类型
/// </summary>
public enum UIFormType
{
// 普通窗体
Normal,
// 固定窗体
Fixed,
// 弹出窗体
PopUp
}

/// <summary>
/// UI窗体的显示类型
/// </summary>
public enum UIFormShowMode
{
// 普通
Normal,
// 反向切换
ReverseChange,
// 隐藏其他
HideOther
}

/// <summary>
/// UI窗体透明度类型
/// </summary>
public enum UIFormLucenyType
{
// 完全透明,不能穿透
Lucency,
// 半透明,不能穿透
Translucence,
// 低透明度,不能穿透
ImPenetrable,
// 可以穿透
Pentrate
}

#endregion

public class SysDefine : MonoBehaviour {
/* 路径常量 */
public const string SYS_PATH_CANVAS = "Canvas";
public const string SYS_PATH_UIFORMS_CONFIG_INFO = "UIFormsConfigInfo";
public const string SYS_PATH_CONFIG_INFO = "SysConfigInfo";

/* 标签常量 */
public const string SYS_TAG_CANVAS = "_TagCanvas";
/* 节点常量 */
public const string SYS_NORMAL_NODE = "Normal";
public const string SYS_FIXED_NODE = "Fixed";
public const string SYS_POPUP_NODE = "PopUp";
public const string SYS_SCRIPTMANAGER_NODE = "_ScriptMgr";
/* 遮罩管理器中,透明度常量 */
public const float SYS_UIMASK_LUCENCY_COLOR_RGB = 255 / 255F;
public const float SYS_UIMASK_LUCENCY_COLOR_RGB_A = 0F / 255F;

public const float SYS_UIMASK_TRANS_LUCENCY_COLOR_RGB = 220 / 255F;
public const float SYS_UIMASK_TRANS_LUCENCY_COLOR_RGB_A = 50F / 255F;

public const float SYS_UIMASK_IMPENETRABLE_COLOR_RGB = 50 / 255F;
public const float SYS_UIMASK_IMPENETRABLE_COLOR_RGB_A = 200F / 255F;

/* 摄像机层深的常量 */

/* 全局性的方法 */
//Todo...

/* 委托的定义 */
//Todo....

}
}

定义“基础UI窗体” BaseUIForms

1
dsg

定义 “UI管理器” UIManager

1
wq42

1: 定义“窗体路径”与“窗体预设”的集合字段。

2:定义“窗体预设”与管理脚本加载用的节点对象。
普通节点、固定节点、弹出节点、管理脚本节点。

3:Unity编辑器中,定义“Canvas 根窗体”预设。

  • 1> 在测试场景中建立UI Panel 。
    • 2> 共建立3个Panel,与一个挂载脚本的空对象。
    • 3> 建立UI摄像机,设置参数。
    • 4> Game 视图定义800*600 分辨率。
    • 5> 针对Canvas UI对象,设置合理参数。

4:定义“登陆窗体”、“选择角色窗体”、“主窗体”等。

* 1> 建立各个窗体的Panel (注意:必须在Canvas 内部建立)
* 2> 给各个窗体添加背景贴图与必要按钮等。
* 3> 定义的窗体都作为“预设”

5:继续开发UIManger 脚本,实现窗体的加载功能,且测试。

* 1> 主场景中,确保拖拽到层级视图中的Canvas 预设正确显示。
* 2> 编写Awake 事件函数,对于常量都统一定义在SysDefine中。
* 3> 编写“显示UI窗体”公共方法。
* 4> 框架外建立测试脚本,测试UI窗体的基本加载功能。
    * 1] 建立框架外启动加载脚本。
    * 2] 针对每个“窗体预设”都需要建立对应的窗体脚本(继承BaseUIForms)
    [ 备注:如果出现“baseUIForms==null, 请先确认克隆对象上是否加载了
    BaseUIForms的子类”,说明“窗体预设”上必须添加BaseUIForms 的子类]

6:继续开发UIManger 脚本,实现窗体的加载功能,且测试。

* 5> 测试以上所有步骤,成功如右图所属。

窗体层级管理

栈数据结构

什么是“栈”数据结构?

是一种“先进后出”的数据结构,是一种常用算法。

生活中的“汉诺塔”游戏、“摞烧饼”、“盘子堆”都是一种典型的“栈”结构。

C#语言中提供Stack泛型集合,来直接实现这种结构。

常用属性与方法:

  • Count 属性 查询栈内元素数量
  • Push() 压栈
  • Pop() 出栈
  • Peek() 查询栈顶元素
  • GetEnumerator() 遍历栈中所有元素

反向切换

开发“UI管理器”的“栈”数据结构,维护窗体的层级结构。

定义Stack 类型字段。

显示UI窗体 ShowUIForms() 方法中

1>“反向切换”属性的窗体,定义“压栈”方法
 关闭(或返回上一个UI)窗体方法中
1>“普通”显示属性的窗体,定义关闭方法。
2> 对于“反向切换”属性的窗体,定义返回上一个窗体的方法(即:关闭)。

隐藏其他

显示UI窗体 ShowUIForms() 方法中

1>“反向切换”属性窗体,定义“压栈”方法
2> “ 隐藏其他 ” 属性窗体,定义显示业务逻辑方法
关闭(或返回上一个UI)窗体方法中
1>“普通”显示属性的窗体,定义关闭方法。
2> 对于“反向切换”属性的窗体,定义返回上一个窗体的方法。 (即:关闭)。
3> “ 隐藏其他 ” 属性窗体, 定义 关闭 逻辑

清空栈集合

在多个UI业务窗体中,有时候需要客户端程序主动清空“栈集合”中的当前数据,防止业务逻辑混乱。

例如: RPG中的“商场系统”、“背包系统”、“任务系统”等。

具体代码实现:
1: 在UIType 类中,定义是否需要“清空反向切换”的字段(或者属性)。
2: 在UI管理器脚本中,关于显示UI窗体的方法中,加入判断清空栈中数据的业务逻辑即可。

客户程序调用测试

定义如下窗体编写代码测试UI框架功能:
 登陆窗体:
注意事项: 所有窗体脚本都要继承BaseUIForms
定义本窗体的类型(位置、显示、透明度三大属性),不写则采用默认数值。
注册窗体按钮事件。
 选择英雄窗体:
 主城窗体:
 商城窗体:
 商品信息窗体:

重构—定义帮助类

程序重构发现,在UI框架内部与客户调用程序中都存在一些反复被使用的技术。
 对于层级视图的节点查找。
(扩展方法)
 获取子节点(物体)的脚本
 给子节点(物体)添加脚本
 给子节点(物体)添加父对象

重构—定义窗体基类方法

程序重构发现,在客户程序(UI框架)调用中,会反复出现一些常用的定义方式,例如:
 按钮的事件监听与注册方法
 打开指定窗体
 关闭指定窗体
 发送消息
 显示语言信息

模态窗体管理

UI窗体中,很多弹出窗体要求玩家不能点击“父窗体”,这就是“模态窗体”。
 这里我们设计了四种模式类型:
完全透明、半透明、低透明度、透明且可以穿透。

基本原理

在Canvas根窗体预设中(PopUp节点下)定义“UI遮挡面板”(_UIMaskPanel)窗体。

 “UI遮挡面板” 就是一个普通的Panel。

平时这个面板是“不可见”状态。

 当需要进行“模态”显示的时候,则定义脚本,控制其在PopUp节点下倒数第二的位置,起到遮挡作用。

 定义一个专门的控制脚本:“UIMaskMgr.cs”,以及在“窗体基类”(BaseUIForms.cs) 中控制“遮挡面板”的显示与隐藏。

UIManager.cs

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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
/***
*
* Title: "SUIFW" UI框架项目
* 主题: UI管理器
* Description:
* 功能: 是整个UI框架的核心,用户程序通过本脚本,来实现框架绝大多数的功能实现。
*
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
* 软件开发原则:
* 1: “高内聚,低耦合”。
* 2: 方法的“单一职责”
*
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SUIFW
{
public class UIManager : MonoBehaviour
{
/* 字段 */
private static UIManager _Instance = null;
//UI窗体预设路径(参数1:窗体预设名称,2:表示窗体预设路径)
private Dictionary<string, string> _DicFormsPaths;
//缓存所有UI窗体
private Dictionary<string, BaseUIForm> _DicALLUIForms;
//当前显示的UI窗体
private Dictionary<string, BaseUIForm> _DicCurrentShowUIForms;
//定义“栈”集合,存储显示当前所有[反向切换]的窗体类型
private Stack<BaseUIForm> _StaCurrentUIForms;
//UI根节点
private Transform _TraCanvasTransfrom = null;
//全屏幕显示的节点
private Transform _TraNormal = null;
//固定显示的节点
private Transform _TraFixed = null;
//弹出节点
private Transform _TraPopUp = null;
//UI管理脚本的节点
private Transform _TraUIScripts = null;


/// <summary>
/// 得到实例
/// </summary>
/// <returns></returns>
public static UIManager GetInstance()
{
if (_Instance==null)
{
_Instance = new GameObject("_UIManager").AddComponent<UIManager>();
}
return _Instance;
}

//初始化核心数据,加载“UI窗体路径”到集合中。
public void Awake()
{
//字段初始化
_DicALLUIForms=new Dictionary<string, BaseUIForm>();
_DicCurrentShowUIForms=new Dictionary<string, BaseUIForm>();
_DicFormsPaths=new Dictionary<string, string>();
_StaCurrentUIForms = new Stack<BaseUIForm>();
//初始化加载(根UI窗体)Canvas预设
InitRootCanvasLoading();
//得到UI根节点、全屏节点、固定节点、弹出节点
_TraCanvasTransfrom = GameObject.FindGameObjectWithTag(SysDefine.SYS_TAG_CANVAS).transform;
_TraNormal = UnityHelper.FindTheChildNode(_TraCanvasTransfrom.gameObject, SysDefine.SYS_NORMAL_NODE);
_TraFixed = UnityHelper.FindTheChildNode(_TraCanvasTransfrom.gameObject, SysDefine.SYS_FIXED_NODE);
_TraPopUp = UnityHelper.FindTheChildNode(_TraCanvasTransfrom.gameObject, SysDefine.SYS_POPUP_NODE);
_TraUIScripts = UnityHelper.FindTheChildNode(_TraCanvasTransfrom.gameObject,SysDefine.SYS_SCRIPTMANAGER_NODE);

//把本脚本作为“根UI窗体”的子节点。
this.gameObject.transform.SetParent(_TraUIScripts, false);
//"根UI窗体"在场景转换的时候,不允许销毁
DontDestroyOnLoad(_TraCanvasTransfrom);
//初始化“UI窗体预设”路径数据
InitUIFormsPathData();
}

/// <summary>
/// 显示(打开)UI窗体
/// 功能:
/// 1: 根据UI窗体的名称,加载到“所有UI窗体”缓存集合中
/// 2: 根据不同的UI窗体的“显示模式”,分别作不同的加载处理
/// </summary>
/// <param name="uiFormName">UI窗体预设的名称</param>
public void ShowUIForms(string uiFormName)
{
BaseUIForm baseUIForms=null; //UI窗体基类

//参数的检查
if (string.IsNullOrEmpty(uiFormName)) return;

//根据UI窗体的名称,加载到“所有UI窗体”缓存集合中
baseUIForms = LoadFormsToAllUIFormsCatch(uiFormName);
if (baseUIForms == null) return;

//是否清空“栈集合”中得数据
if (baseUIForms.CurrentUIType.IsClearStack)
{
ClearStackArray();
}

//根据不同的UI窗体的显示模式,分别作不同的加载处理
switch (baseUIForms.CurrentUIType.UIForms_ShowMode)
{
case UIFormShowMode.Normal: //“普通显示”窗口模式
//把当前窗体加载到“当前窗体”集合中。
LoadUIToCurrentCache(uiFormName);
break;

case UIFormShowMode.ReverseChange: //需要“反向切换”窗口模式
PushUIFormToStack(uiFormName);
break;

case UIFormShowMode.HideOther: //“隐藏其他”窗口模式
EnterUIFormsAndHideOther(uiFormName);
break;

default:
break;
}
}

/// <summary>
/// 关闭(返回上一个)窗体
/// </summary>
/// <param name="uiFormName"></param>
public void CloseUIForms(string uiFormName)
{
BaseUIForm baseUiForm; //窗体基类

//参数检查
if (string.IsNullOrEmpty(uiFormName)) return;
//“所有UI窗体”集合中,如果没有记录,则直接返回
_DicALLUIForms.TryGetValue(uiFormName,out baseUiForm);

if(baseUiForm==null ) return;
//根据窗体不同的显示类型,分别作不同的关闭处理
switch (baseUiForm.CurrentUIType.UIForms_ShowMode)
{
case UIFormShowMode.Normal:
//普通窗体的关闭
ExitUIForms(uiFormName);
break;

case UIFormShowMode.ReverseChange:
//反向切换窗体的关闭
PopUIFroms();
break;

case UIFormShowMode.HideOther:
//隐藏其他窗体关闭
ExitUIFormsAndDisplayOther(uiFormName);
break;

default:
break;
}
}

#region 显示“UI管理器”内部核心数据,测试使用

/// <summary>
/// 显示"所有UI窗体"集合的数量
/// </summary>
/// <returns></returns>
public int ShowALLUIFormCount()
{
if (_DicALLUIForms != null)
{
return _DicALLUIForms.Count;
}
else
{
return 0;
}
}

/// <summary>
/// 显示"当前窗体"集合中数量
/// </summary>
/// <returns></returns>
public int ShowCurrentUIFormsCount()
{
if (_DicCurrentShowUIForms != null)
{
return _DicCurrentShowUIForms.Count;
}
else
{
return 0;
}
}

/// <summary>
/// 显示“当前栈”集合中窗体数量
/// </summary>
/// <returns></returns>
public int ShowCurrentStackUIFormsCount()
{
if (_StaCurrentUIForms != null)
{
return _StaCurrentUIForms.Count;
}
else
{
return 0;
}
}

#endregion

#region 私有方法
//初始化加载(根UI窗体)Canvas预设
private void InitRootCanvasLoading()
{
ResourcesMgr.GetInstance().LoadAsset(SysDefine.SYS_PATH_CANVAS, false);
}

/// <summary>
/// 根据UI窗体的名称,加载到“所有UI窗体”缓存集合中
/// 功能: 检查“所有UI窗体”集合中,是否已经加载过,否则才加载。
/// </summary>
/// <param name="uiFormsName">UI窗体(预设)的名称</param>
/// <returns></returns>
private BaseUIForm LoadFormsToAllUIFormsCatch(string uiFormsName)
{
BaseUIForm baseUIResult = null; //加载的返回UI窗体基类

_DicALLUIForms.TryGetValue(uiFormsName, out baseUIResult);

if (baseUIResult==null)
{
//加载指定名称的“UI窗体”
baseUIResult = LoadUIForm(uiFormsName);
}

return baseUIResult;
}

/// <summary>
/// 加载指定名称的“UI窗体”
/// 功能:
/// 1:根据“UI窗体名称”,加载预设克隆体。
/// 2:根据不同预设克隆体中带的脚本中不同的“位置信息”,加载到“根窗体”下不同的节点。
/// 3:隐藏刚创建的UI克隆体。
/// 4:把克隆体,加入到“所有UI窗体”(缓存)集合中。
///
/// </summary>
/// <param name="uiFormName">UI窗体名称</param>
private BaseUIForm LoadUIForm(string uiFormName)
{
string strUIFormPaths = null; //UI窗体路径
GameObject goCloneUIPrefabs = null; //创建的UI克隆体预设
BaseUIForm baseUiForm=null; //窗体基类


//根据UI窗体名称,得到对应的加载路径
_DicFormsPaths.TryGetValue(uiFormName, out strUIFormPaths);
//根据“UI窗体名称”,加载“预设克隆体”
if (!string.IsNullOrEmpty(strUIFormPaths))
{
goCloneUIPrefabs = ResourcesMgr.GetInstance().LoadAsset(strUIFormPaths, false);
}
//设置“UI克隆体”的父节点(根据克隆体中带的脚本中不同的“位置信息”)
if (_TraCanvasTransfrom != null && goCloneUIPrefabs != null)
{
baseUiForm = goCloneUIPrefabs.GetComponent<BaseUIForm>();
if (baseUiForm == null)
{
Debug.Log("baseUiForm==null! ,请先确认窗体预设对象上是否加载了baseUIForm的子类脚本! 参数 uiFormName=" + uiFormName);
return null;
}
switch (baseUiForm.CurrentUIType.UIForms_Type)
{
case UIFormType.Normal: //普通窗体节点
goCloneUIPrefabs.transform.SetParent(_TraNormal, false);
break;

case UIFormType.Fixed: //固定窗体节点
goCloneUIPrefabs.transform.SetParent(_TraFixed, false);
break;

case UIFormType.PopUp: //弹出窗体节点
goCloneUIPrefabs.transform.SetParent(_TraPopUp, false);
break;

default:
break;
}

//设置隐藏
goCloneUIPrefabs.SetActive(false);
//把克隆体,加入到“所有UI窗体”(缓存)集合中。
_DicALLUIForms.Add(uiFormName, baseUiForm);
return baseUiForm;
}
else
{
Debug.Log("_TraCanvasTransfrom==null Or goCloneUIPrefabs==null!! ,Plese Check!, 参数uiFormName="+uiFormName);
}

Debug.Log("出现不可以预估的错误,请检查,参数 uiFormName="+uiFormName);
return null;
}//Mehtod_end

/// <summary>
/// 把当前窗体加载到“当前窗体”集合中
/// </summary>
/// <param name="uiFormName">窗体预设的名称</param>
private void LoadUIToCurrentCache(string uiFormName)
{
BaseUIForm baseUiForm; //UI窗体基类
BaseUIForm baseUIFormFromAllCache; //从“所有窗体集合”中得到的窗体

//如果“正在显示”的集合中,存在整个UI窗体,则直接返回
_DicCurrentShowUIForms.TryGetValue(uiFormName, out baseUiForm);
if (baseUiForm != null) return;
//把当前窗体,加载到“正在显示”集合中
_DicALLUIForms.TryGetValue(uiFormName, out baseUIFormFromAllCache);

if (baseUIFormFromAllCache!=null)
{
_DicCurrentShowUIForms.Add(uiFormName, baseUIFormFromAllCache);
baseUIFormFromAllCache.Display(); //显示当前窗体
}
}

/// <summary>
/// UI窗体入栈
/// </summary>
/// <param name="uiFormName">窗体的名称</param>
private void PushUIFormToStack(string uiFormName)
{
BaseUIForm baseUIForm; //UI窗体

//判断“栈”集合中,是否有其他的窗体,有则“冻结”处理。
if(_StaCurrentUIForms.Count>0)
{
BaseUIForm topUIForm=_StaCurrentUIForms.Peek();
//栈顶元素作冻结处理
topUIForm.Freeze();
}
//判断“UI所有窗体”集合是否有指定的UI窗体,有则处理。
_DicALLUIForms.TryGetValue(uiFormName, out baseUIForm);

if (baseUIForm!=null)
{
//当前窗口显示状态
baseUIForm.Display();
//把指定的UI窗体,入栈操作。
_StaCurrentUIForms.Push(baseUIForm);
}else
{
Debug.Log("baseUIForm==null,Please Check, 参数 uiFormName=" + uiFormName);
}
}

/// <summary>
/// 退出指定UI窗体
/// </summary>
/// <param name="strUIFormName"></param>
private void ExitUIForms(string strUIFormName)
{
BaseUIForm baseUIForm; //窗体基类

//"正在显示集合"中如果没有记录,则直接返回。
_DicCurrentShowUIForms.TryGetValue(strUIFormName, out baseUIForm);

if(baseUIForm==null) return ;

//指定窗体,标记为“隐藏状态”,且从"正在显示集合"中移除。
baseUIForm.Hiding();
_DicCurrentShowUIForms.Remove(strUIFormName);
}

//(“反向切换”属性)窗体的出栈逻辑
private void PopUIFroms()
{
if(_StaCurrentUIForms.Count>=2)
{
//出栈处理
BaseUIForm topUIForms = _StaCurrentUIForms.Pop();
//做隐藏处理
topUIForms.Hiding();
//出栈后,下一个窗体做“重新显示”处理。
BaseUIForm nextUIForms = _StaCurrentUIForms.Peek();
nextUIForms.Redisplay();
}
else if (_StaCurrentUIForms.Count ==1)
{
//出栈处理
BaseUIForm topUIForms = _StaCurrentUIForms.Pop();
//做隐藏处理
topUIForms.Hiding();
}
}

/// <summary>
/// (“隐藏其他”属性)打开窗体,且隐藏其他窗体
/// </summary>
/// <param name="strUIName">打开的指定窗体名称</param>
private void EnterUIFormsAndHideOther(string strUIName)
{
BaseUIForm baseUIForm; //UI窗体基类
BaseUIForm baseUIFormFromALL; //从集合中得到的UI窗体基类

//参数检查
if (string.IsNullOrEmpty(strUIName)) return;

_DicCurrentShowUIForms.TryGetValue(strUIName, out baseUIForm);
if (baseUIForm != null) return;

//把“正在显示集合”与“栈集合”中所有窗体都隐藏。
foreach (BaseUIForm baseUI in _DicCurrentShowUIForms.Values)
{
baseUI.Hiding();
}
foreach (BaseUIForm staUI in _StaCurrentUIForms)
{
staUI.Hiding();
}

//把当前窗体加入到“正在显示窗体”集合中,且做显示处理。
_DicALLUIForms.TryGetValue(strUIName, out baseUIFormFromALL);
if (baseUIFormFromALL!=null)
{
_DicCurrentShowUIForms.Add(strUIName, baseUIFormFromALL);
//窗体显示
baseUIFormFromALL.Display();
}
}

/// <summary>
/// (“隐藏其他”属性)关闭窗体,且显示其他窗体
/// </summary>
/// <param name="strUIName">打开的指定窗体名称</param>
private void ExitUIFormsAndDisplayOther(string strUIName)
{
BaseUIForm baseUIForm; //UI窗体基类


//参数检查
if (string.IsNullOrEmpty(strUIName)) return;

_DicCurrentShowUIForms.TryGetValue(strUIName, out baseUIForm);
if (baseUIForm == null) return;

//当前窗体隐藏状态,且“正在显示”集合中,移除本窗体
baseUIForm.Hiding();
_DicCurrentShowUIForms.Remove(strUIName);

//把“正在显示集合”与“栈集合”中所有窗体都定义重新显示状态。
foreach (BaseUIForm baseUI in _DicCurrentShowUIForms.Values)
{
baseUI.Redisplay();
}
foreach (BaseUIForm staUI in _StaCurrentUIForms)
{
staUI.Redisplay();
}
}

/// <summary>
/// 是否清空“栈集合”中得数据
/// </summary>
/// <returns></returns>
private bool ClearStackArray()
{
if (_StaCurrentUIForms != null && _StaCurrentUIForms.Count>=1)
{
//清空栈集合
_StaCurrentUIForms.Clear();
return true;
}

return false;
}

/// <summary>
/// 初始化“UI窗体预设”路径数据
/// </summary>
private void InitUIFormsPathData()
{
IConfigManager configMgr = new ConfigManagerByJson(SysDefine.SYS_PATH_UIFORMS_CONFIG_INFO);
if (configMgr!=null)
{
_DicFormsPaths = configMgr.AppSetting;
}
}

#endregion

}//class_end
}

BaseUIForm.cs

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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/***
*
* Title: "SUIFW" UI框架项目
* 主题: UI窗体的父类
* Description:
* 功能:定义所有UI窗体的父类。
* 定义四个生命周期
*
* 1:Display 显示状态。
* 2:Hiding 隐藏状态
* 3:ReDisplay 再显示状态。
* 4:Freeze 冻结状态。
*
*
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
*/
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using UnityEngine;

namespace SUIFW
{
public class BaseUIForm : MonoBehaviour
{
/*字段*/
private UIType _CurrentUIType=new UIType();

/* 属性*/
//当前UI窗体类型
public UIType CurrentUIType
{
get
{
return _CurrentUIType;
}
set
{
_CurrentUIType = value;
}
}


#region 窗体的四种(生命周期)状态,给外界(UIManager)调用

/// <summary>
/// 显示状态
/// </summary>
public virtual void Display()
{
this.gameObject.SetActive(true);
//设置模态窗体调用(必须是弹出窗体)
if (_CurrentUIType.UIForms_Type==UIFormType.PopUp)
{
UIMaskMgr.GetInstance().SetMaskWindow(this.gameObject,_CurrentUIType.UIForm_LucencyType);
}
}

/// <summary>
/// 隐藏状态
/// </summary>
public virtual void Hiding()
{
this.gameObject.SetActive(false);
//取消模态窗体调用
if (_CurrentUIType.UIForms_Type == UIFormType.PopUp)
{
UIMaskMgr.GetInstance().CancelMaskWindow();
}
}

/// <summary>
/// 重新显示状态
/// </summary>
public virtual void Redisplay()
{
this.gameObject.SetActive(true);
//设置模态窗体调用(必须是弹出窗体)
if (_CurrentUIType.UIForms_Type == UIFormType.PopUp)
{
UIMaskMgr.GetInstance().SetMaskWindow(this.gameObject, _CurrentUIType.UIForm_LucencyType);
}
}

/// <summary>
/// 冻结状态
/// </summary>
public virtual void Freeze()
{
this.gameObject.SetActive(true);
}


#endregion

#region 封装子类常用的方法

/// <summary>
/// 注册按钮事件
/// </summary>
/// <param name="buttonName">按钮节点名称</param>
/// <param name="delHandle">委托:需要注册的方法</param>
protected void RigisterButtonObjectEvent(string buttonName,EventTriggerListener.VoidDelegate delHandle)
{
GameObject goButton = UnityHelper.FindTheChildNode(this.gameObject, buttonName).gameObject;
//给按钮注册事件方法
if (goButton != null)
{
EventTriggerListener.Get(goButton).onClick = delHandle;
}
}

/// <summary>
/// 打开UI窗体
/// </summary>
/// <param name="uiFormName"></param>
protected void OpenUIForm(string uiFormName)
{
UIManager.GetInstance().ShowUIForms(uiFormName);
}

/// <summary>
/// 关闭当前UI窗体
/// </summary>
protected void CloseUIForm()
{
string strUIFromName = string.Empty; //处理后的UIFrom 名称
int intPosition = -1;

strUIFromName=GetType().ToString(); //命名空间+类名
intPosition=strUIFromName.IndexOf('.');
if (intPosition!=-1)
{
//剪切字符串中“.”之间的部分
strUIFromName = strUIFromName.Substring(intPosition + 1);
}

UIManager.GetInstance().CloseUIForms(strUIFromName);
}

/// <summary>
/// 发送消息
/// </summary>
/// <param name="msgType">消息的类型</param>
/// <param name="msgName">消息名称</param>
/// <param name="msgContent">消息内容</param>
protected void SendMessage(string msgType,string msgName,object msgContent)
{
KeyValuesUpdate kvs = new KeyValuesUpdate(msgName,msgContent);
MessageCenter.SendMessage(msgType, kvs);
}

/// <summary>
/// 接收消息
/// </summary>
/// <param name="messagType">消息分类</param>
/// <param name="handler">消息委托</param>
public void ReceiveMessage(string messagType,MessageCenter.DelMessageDelivery handler)
{
MessageCenter.AddMsgListener(messagType, handler);
}

/// <summary>
/// 显示语言
/// </summary>
/// <param name="id"></param>
public string Show(string id)
{
string strResult = string.Empty;

strResult = LauguageMgr.GetInstance().ShowText(id);
return strResult;
}

#endregion
}
}

UIType.cs

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
/***
*
* Title: "SUIFW" UI框架项目
* 主题: 窗体类型
* Description:
* 功能:
*
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SUIFW
{
public class UIType
{
//是否清空“栈集合”
public bool IsClearStack = false;
//UI窗体(位置)类型
public UIFormType UIForms_Type = UIFormType.Normal;
//UI窗体显示类型
public UIFormShowMode UIForms_ShowMode = UIFormShowMode.Normal;
//UI窗体透明度类型
public UIFormLucenyType UIForm_LucencyType = UIFormLucenyType.Lucency;
}
}

日志调试与配置管理

所谓“配置管理”是指一个游戏项目(软件项目),很多需要经常变化的需求或者数据,最好以配置文件的形式存在,从而代替“硬编码”方式。

例如: 游戏项目语言的国际化、日志文件的保存路径等。

Json语法中 花括号保存对象,方括号保存数组。

开发Json配置管理器

定义通用配置管理器接口

 开发实现IConfigManager 接口的通用配置管理器

配置管理器应用

UI管理器中关于“UI窗体预设路径”集合中,把前面“硬编码”改为应用Json配置管理的方式。
第1步在Resources 目录下建立关于“UIFormsConfigInfo”的Json 文件。

第2步对于UIManager.cs 中的“UI窗体预设路径”集合做配置管理。

日志调试

日志调试在游戏的开发全过程中占有非常重要的作用。

 “地下守护神”项目中,首次提出自定义“日志调试”脚本插件的开发思路与具体实现。
现在为了更好的适用于PC与移动端调试的目的,进行再次重构。

第1: 对于读写文件内部方法做重构完善,使得日志文件实现自动侦测与创建写入操作等。
第2: 改以前的针对XML的配置文件的读取方式为Json 文件的读取。
目的一: 提高读取速度。

二: 更好的应用在移动端的部署

IConfigManager.cs

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
/***
*
* Title: "SUIFW" UI框架项目
* 主题: 通用配置管理器接口
* Description:
* 功能:
* 基于“键值对”配置文件的通用解析
*
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
*/

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SUIFW
{
public interface IConfigManager
{
/// <summary>
/// 只读属性: 应用设置
/// 功能: 得到键值对集合数据
/// </summary>
Dictionary<string, string> AppSetting { get; }

/// <summary>
/// 得到配置文件(AppSeting)最大的数量
/// </summary>
/// <returns></returns>
int GetAppSettingMaxNumber();

}

[Serializable]
internal class KeyValuesInfo
{
//配置信息
public List<KeyValuesNode> ConfigInfo = null;
}

[Serializable]
internal class KeyValuesNode
{
//键
public string Key = null;
//值
public string Value = null;
}
}

ConfigManagerByJson.cs

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
/***
*
* Title: "SUIFW" UI框架项目
* 主题:基于Json 配置文件的“配置管理器”
* Description:
* 功能:
*
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
*/

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SUIFW
{
public class ConfigManagerByJson : IConfigManager
{
//保存(键值对)应用设置集合
private static Dictionary<string, string> _AppSetting;

/// <summary>
/// 只读属性: 得到应用设置(键值对集合)
/// </summary>
public Dictionary<string, string> AppSetting
{
get { return _AppSetting; }
}

/// <summary>
/// 构造函数
/// </summary>
/// <param name="jsonPath">Json配置文件路径</param>
public ConfigManagerByJson(string jsonPath)
{
_AppSetting=new Dictionary<string, string>();
//初始化解析Json 数据,加载到(_AppSetting)集合。
InitAndAnalysisJson(jsonPath);
}

/// <summary>
/// 得到AppSetting 的最大数值
/// </summary>
/// <returns></returns>
public int GetAppSettingMaxNumber()
{
if (_AppSetting!=null && _AppSetting.Count>=1)
{
return _AppSetting.Count;
}
else
{
return 0;
}
}

/// <summary>
/// 初始化解析Json 数据,加载到集合众。
/// </summary>
/// <param name="jsonPath"></param>
private void InitAndAnalysisJson(string jsonPath)
{
TextAsset configInfo = null;
KeyValuesInfo keyvalueInfoObj = null;

//参数检查
if (string.IsNullOrEmpty(jsonPath)) return;
//解析Json 配置文件
try{
configInfo = Resources.Load<TextAsset>(jsonPath);
keyvalueInfoObj=JsonUtility.FromJson<KeyValuesInfo>(configInfo.text);
}
catch{
throw new JsonAnlysisException(GetType() + "/InitAndAnalysisJson()/Json Analysis Exception ! Parameter jsonPath=" + jsonPath);
}
//数据加载到AppSetting 集合中
foreach (KeyValuesNode nodeInfo in keyvalueInfoObj.ConfigInfo)
{
_AppSetting.Add(nodeInfo.Key,nodeInfo.Value);
}
}



}
}

消息传递中心

Unity目前提供的消息传递方式

基于Unity技术的游戏与项目研发,目前提供的消息传递方式种类少,且耦合性很高。
 1:脚本组件公共方法、字段的相互调用。
例如: GetComponnet().TestMethod();
 2:SendMesage 技术。
 3: 单例模式数据传递。

开发一种低耦合,无需考虑被传递对象(脚本名称、组件名称)的技术非常有价值。
 “消息传递中心”:

基于观察者模式,利用委托与事件的基本机制原理,进一步封装重构的技术实现。

消息传递中心原理

定义MessageCenter
基本原理:

窗体基类(BaseUIForms) 中对于“消息传递中心”类常用方法的封装。
发送消息 SendMessage()
接收消息 ReceiveMessage()
 客户程序消息传递多组数据的演示。
 客户程序建立“系统常量”类,方便程序复用与集中化管理

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
/***
*
* Title: "SUIFW" UI框架项目
* 主题: 消息(传递)中心
* Description:
* 功能: 负责UI框架中,所有UI窗体中间的数据传值。
*
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SUIFW
{
public class MessageCenter {
//委托:消息传递
public delegate void DelMessageDelivery(KeyValuesUpdate kv);

//消息中心缓存集合
//<string : 数据大的分类,DelMessageDelivery 数据执行委托>
public static Dictionary<string, DelMessageDelivery> _dicMessages = new Dictionary<string, DelMessageDelivery>();

/// <summary>
/// 增加消息的监听。
/// </summary>
/// <param name="messageType">消息分类</param>
/// <param name="handler">消息委托</param>
public static void AddMsgListener(string messageType,DelMessageDelivery handler)
{
if (!_dicMessages.ContainsKey(messageType))
{
_dicMessages.Add(messageType,null);
}
_dicMessages[messageType] += handler;
}

/// <summary>
/// 取消消息的监听
/// </summary>
/// <param name="messageType">消息分类</param>
/// <param name="handele">消息委托</param>
public static void RemoveMsgListener(string messageType,DelMessageDelivery handele)
{
if (_dicMessages.ContainsKey(messageType))
{
_dicMessages[messageType] -= handele;
}

}

/// <summary>
/// 取消所有指定消息的监听
/// </summary>
public static void ClearALLMsgListener()
{
if (_dicMessages!=null)
{
_dicMessages.Clear();
}
}

/// <summary>
/// 发送消息
/// </summary>
/// <param name="messageType">消息的分类</param>
/// <param name="kv">键值对(对象)</param>
public static void SendMessage(string messageType,KeyValuesUpdate kv)
{
DelMessageDelivery del; //委托

if (_dicMessages.TryGetValue(messageType,out del))
{
if (del!=null)
{
//调用委托
del(kv);
}
}
}


}

/// <summary>
/// 键值更新对
/// 功能: 配合委托,实现委托数据传递
/// </summary>
public class KeyValuesUpdate
{ //键
private string _Key;
//值
private object _Values;

/* 只读属性 */

public string Key
{
get { return _Key; }
}
public object Values
{
get { return _Values; }
}

public KeyValuesUpdate(string key, object valueObj)
{
_Key = key;
_Values = valueObj;
}
}


}
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
/***
*
* Title: "SUIFW"UI框架项目
* 主题: 事件触发监听
* Description:
* 功能: 实现对于任何对象的监听处理。
* Date: 2017
* Version: 0.1版本
* Modify Recoder:
*
*
*/
using UnityEngine;
using UnityEngine.EventSystems;

namespace SUIFW
{
public class EventTriggerListener :UnityEngine.EventSystems.EventTrigger
{
public delegate void VoidDelegate(GameObject go);
public VoidDelegate onClick;
public VoidDelegate onDown;
public VoidDelegate onEnter;
public VoidDelegate onExit;
public VoidDelegate onUp;
public VoidDelegate onSelect;
public VoidDelegate onUpdateSelect;




/// <summary>
/// 得到“监听器”组件
/// </summary>
/// <param name="go">监听的游戏对象</param>
/// <returns>
/// 监听器
/// </returns>
public static EventTriggerListener Get(GameObject go)
{
EventTriggerListener lister = go.GetComponent<EventTriggerListener>();
if (lister==null)
{
lister = go.AddComponent<EventTriggerListener>();
}
return lister;
}

public override void OnPointerClick(PointerEventData eventData)
{
if(onClick!=null)
{
onClick(gameObject);
}
}

public override void OnPointerDown(PointerEventData eventData)
{
if (onDown != null)
{
onDown(gameObject);
}
}

public override void OnPointerEnter(PointerEventData eventData)
{
if (onEnter != null)
{
onEnter(gameObject);
}
}

public override void OnPointerExit(PointerEventData eventData)
{
if (onExit != null)
{
onExit(gameObject);
}
}

public override void OnPointerUp(PointerEventData eventData)
{
if (onUp != null)
{
onUp(gameObject);
}
}

public override void OnSelect(BaseEventData eventBaseData)
{
if (onSelect != null)
{
onSelect(gameObject);
}
}

public override void OnUpdateSelected(BaseEventData eventBaseData)
{
if (onUpdateSelect != null)
{
onUpdateSelect(gameObject);
}
}

}//Class_end
}

资源国际化

“资源国际化”对于游戏项目开发是指:语言、语音、贴图、模型等国际化
问题。
 对于游戏项目,最常见的是针对不同国家的多语言版本的开发,也就是“语
言的国际化”。

多语言版本的实现,最基本的
原理就是根据ID去读取语言配
置表,不同的语言新建一个语
言配置表。

定义“语言管理器”

定义“语言管理器”(LanguageMgr)
基本原理:
1: 使用配置管理器脚本(继承 IConfigManager接口),读取不同语言的Json配置文件。
2: 使用 Dictionary 集合缓存“语言键值对”。
3:定义显示方法,根据ID查询出对应的语言信息。

重构

UI窗体基类(BaseUIForms) 对显示语言的重构。

0%