思考并回答以下问题:
- 画时序图和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 | /*** |
定义“基础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 | /*** |
BaseUIForm.cs
1 | /*** |
UIType.cs
1 | /*** |
日志调试与配置管理
所谓“配置管理”是指一个游戏项目(软件项目),很多需要经常变化的需求或者数据,最好以配置文件的形式存在,从而代替“硬编码”方式。
例如: 游戏项目语言的国际化、日志文件的保存路径等。
Json语法中 花括号保存对象,方括号保存数组。
开发Json配置管理器
定义通用配置管理器接口
开发实现IConfigManager 接口的通用配置管理器
配置管理器应用
UI管理器中关于“UI窗体预设路径”集合中,把前面“硬编码”改为应用Json配置管理的方式。
第1步在Resources 目录下建立关于“UIFormsConfigInfo”的Json 文件。
第2步对于UIManager.cs 中的“UI窗体预设路径”集合做配置管理。
日志调试
日志调试在游戏的开发全过程中占有非常重要的作用。
“地下守护神”项目中,首次提出自定义“日志调试”脚本插件的开发思路与具体实现。
现在为了更好的适用于PC与移动端调试的目的,进行再次重构。
第1: 对于读写文件内部方法做重构完善,使得日志文件实现自动侦测与创建写入操作等。
第2: 改以前的针对XML的配置文件的读取方式为Json 文件的读取。
目的一: 提高读取速度。
二: 更好的应用在移动端的部署
IConfigManager.cs
1 | /*** |
ConfigManagerByJson.cs
1 | /*** |
消息传递中心
Unity目前提供的消息传递方式
基于Unity技术的游戏与项目研发,目前提供的消息传递方式种类少,且耦合性很高。
1:脚本组件公共方法、字段的相互调用。
例如: GetComponnet
2:SendMesage 技术。
3: 单例模式数据传递。
开发一种低耦合,无需考虑被传递对象(脚本名称、组件名称)的技术非常有价值。
“消息传递中心”:
基于观察者模式,利用委托与事件的基本机制原理,进一步封装重构的技术实现。
消息传递中心原理
定义MessageCenter
基本原理:
窗体基类(BaseUIForms) 中对于“消息传递中心”类常用方法的封装。
发送消息 SendMessage()
接收消息 ReceiveMessage()
客户程序消息传递多组数据的演示。
客户程序建立“系统常量”类,方便程序复用与集中化管理
1 | /*** |
1 | /*** |
资源国际化
“资源国际化”对于游戏项目开发是指:语言、语音、贴图、模型等国际化
问题。
对于游戏项目,最常见的是针对不同国家的多语言版本的开发,也就是“语
言的国际化”。
多语言版本的实现,最基本的
原理就是根据ID去读取语言配
置表,不同的语言新建一个语
言配置表。
定义“语言管理器”
定义“语言管理器”(LanguageMgr)
基本原理:
1: 使用配置管理器脚本(继承 IConfigManager接口),读取不同语言的Json配置文件。
2: 使用 Dictionary
3:定义显示方法,根据ID查询出对应的语言信息。
重构
UI窗体基类(BaseUIForms) 对显示语言的重构。