UI(六) - 如何架构UI框架
快速架构一个简单易用的UI框架
在前面架构章节中,我们讲述了架构的需要注意的特性,以及设计架构时所使用的抽象方法。我们在经历几个项目后,会总结所有经历过的这些项目的经验,这些经验很好的支撑了构建架构的基础。
我们从宏观的角度看UI框架。
只有从宏观的角度看问题,才能看的更明白。我们项目中拥有众多UI界面,我们要统一管理所有UI,这样才能使得每个UI界面都能得到有效的调配。不仅如此,如果每个UI界面都是可扩充的那就太棒了。UI有一个很关键的系统是输入事件系统,UI内的每个按钮都需要有一个处理输入的句柄。所以我们需要写一个统计的管理类,以及每个UI都要有统一的基类,并且每个UI按钮元素都对应一个处理输入的句柄。另外对于UI来说,有通用UI,也有非通用UI,有常用UI和非常用UI之分。接下来我们把细节规划一下。
Ⅰ.管理类。
整个UI是由N个界面构成的。这些UI界面有基本的功能,生成,展示,销毁,查找。如果说,我们分别对N个UI界面的这些功能进行编程,就会有大量的工作产生,而且维护起来的工作量也是巨大的。
我们需要用一个单例实例来管理所有的UI界面,让他们能有统一的接口进行以上的活动,创建UI管理类是最好的选择,我们可以命名它为 UIManager,这个名字符合它代表的功能。
那么 UIManager 具体里面要做些什么呢。它需要创建UI,需要查找现有的某个UI,以及需要销毁UI,以及一些UI的统一接口调用和调配工作。UIManager 承担了所有UI的管理工作,因此UI在生成出来后的实例都将存储在这里。不仅如此,一些UI常用变量也存储在里面,比如屏幕的适配标准大小,比如UI的Camera实例等等。
这样一来,第一个方向确定了,那就是UIManager是UI界面的管理员,统筹管理UI问题。它包括了UI的众多统筹需求,比如下层UI切换到上层,比如加载方式变更,比如选择性预加载UI等,都需要在UIManager里编写。
public class ScreenManager : CSingleton<ScreenManager>
{protected Transform _transform = null;private Dictionary<string, UIScreenBase> _DicScreens = new Dictionary<string, UIScreenBase>();// 关闭所有界面public void CloseAll(){...}// 是否UI正打开public bool IsShow(string screenID){...}// 关闭界面public void CloseScreen(UIScreenBase screen){...}// 创建所有界面public T CreateMenu<T>() where T : UIScreenBase{...}// 找出某个界面public T FindMenu<T>() where T : UIScreenBase{...}...
}
Ⅱ.基类。
项目中有很多界面,这N个界面他们有自己的共性,比如最基本的,他们都需要进行初始化,他们都需要有展示接口,他们都可以关闭,共性产生统一特征的接口,Init,Open和Close。继承基类又使得管理起来比较方便,在上面提到的 UIManager 里存储的UI实例时,可以统一使用基类的方式存储。我们可以把基类的名字称为 UIScreenBase,每个UI界面都继承自它,Screen一词很形象贴切的描述了屏幕上显示的界面。
我们将所有UI都定义为基类的子类,对有需要做特殊处理的UI界面,可以重写Init,Open和Close。为了能更方便的知道UI的状态,我们也可以定义一个UI状态,比如OpenState为打开状态,CloseState为关闭状态,HidenState为隐藏状态,PreopenState为预加载状态,以状态的形式来判断UI现在的情况。
到这里,我们的每个界面有了基类,自己成为了扩展界面功能的一个类实体,可以自主定义自己的功能性的接口,同时还会受到管理类的统一调配。做到了,既满足有序管理,又能满足自定义需求。看似简单的几行代码,里面蕴含着复杂的思考过程,抽象的意义就在于此。
public abstract class UIScreenBase : MonoBehaviour
{protected bool mInitialized = false;protected UIState mState = UIState.None;public UIState State { get { return mState; } }public delegate void OnScreenHandlerEventHandler(UIScreenBase screen);public event OnScreenHandlerEventHandler onCloseScreen;// 初始化protected virtual void Init(){ mInitialized = true;}//打开public virtual void Open() {}//关闭public virtual void Close() {}
}
Ⅲ.输入事件响应机制。
UI中输入事件的响应机制比较重要,好的输入事件响应机制能提高更多的效率,让程序员编写逻辑的时候更加舒服。
Unity3D的UGUI输入事件响应机制建立通常有2种,一种是继承型,一种是注册型。
继承型是指事件先响应到基类,再由基类反应给父类,由父类做处理,这样UI既可以得到对输入事件的响应,也可以自行修改自己需要的逻辑。比如我们写了个处理事件的基类组件UIEventBase是父类能接受各种输入事件响应,UIEventButton是继承UIEventBase的子类,当输入事件传入时UIEventButton能做出响应,因为它继承了父类。
绑定型是指在对输入事件响应之前,我们对UI元素绑定一个事件响应的组件。比如编写一个绑定型事件类 UIEvent,当某个UI元素需要输入事件回调时,对这个物体加绑一个 UIEvent,并且对 UIEvent 里需要的相关响应事件进行赋值或注册操作函数。当输入事件响应时,由 UIEvent 来区分输入的是什么类型的事件,再分别调用响应到具体函数。
继承型和绑定型都有一个共同的特点,都需要与UI元素关联,区别是继承型融入在了各种组件内,而绑定型以独立的组件形式体现。
继承型UI事件输入响应机制需要关联到组件内,UGUI和NGUI都已经有了自己的基础的组件,所以很难在这上面使用,而在另一些比较特殊的GUI系统内可以很好的适应。比如我曾经做过一个项目,我们构建的一套新的UI系统的完全独立于UGUI和NGUI的GUI系统之外,我们将输入事件处理注入到这个系统的各个组件内,达到了输入事件处理与组件融合的效果。
绑定型的方式更适合在已经建立了GUI系统的基础上,对输入事件进行封装处理。通常在UGUI和NGUI上都会使用绑定型对输入事件处理进行封装。
例如,在UI初始化中,对需要输入事件响应的,绑定一个事件处理类,比如命名为 UIEvent,然后对事件句柄进行赋值,例如,ui_event.onclick = OnClickLogin,OnClickLogin就是响应登录按钮的事件句柄。
这样的赋值方式,让程序员写逻辑时看起来更加清爽,简洁,直观。
/// <summary>
/// UI 事件
/// </summary>
public class UI_Event : UnityEngine.EventSystems.EventTrigger
{protected const float CLICK_INTERVAL_TIME = 0.2f; //const click interval timeprotected const float CLICK_INTERVAL_POS = 2; //const click interval pospublic delegate void PointerEventDelegate ( PointerEventData eventData , UI_Event ev);public delegate void BaseEventDelegate ( BaseEventData eventData , UI_Event ev);public delegate void AxisEventDelegate ( AxisEventData eventData , UI_Event ev);public Dictionary<string,object> mArg = new Dictionary<string,object>();public BaseEventDelegate onDeselect = null;public PointerEventDelegate onBeginDrag = null;public PointerEventDelegate onDrag = null;public PointerEventDelegate onEndDrag = null;public PointerEventDelegate onDrop = null;public AxisEventDelegate onMove = null;public PointerEventDelegate onClick = null;public PointerEventDelegate onDown = null;public PointerEventDelegate onEnter = null;public PointerEventDelegate onExit = null;public PointerEventDelegate onUp = null;public PointerEventDelegate onScroll = null;public BaseEventDelegate onSelect = null;public BaseEventDelegate onUpdateSelect = null;public BaseEventDelegate onCancel = null;public PointerEventDelegate onInitializePotentialDrag = null;public BaseEventDelegate onSubmit = null;private static PointerEventData mPointData = null;// 设置参数public void SetData(string key , object val){mArg[key] = val;}// 获取参数public D GetData<D>(string key){if(mArg.ContainsKey(key)){return (D)mArg[key];}return default(D);}...public static UI_Event Get(GameObject go){UI_Event listener = go.GetComponent<UI_Event>();if (listener == null) listener = go.AddComponent<UI_Event>();return listener;}public override void OnBeginDrag( PointerEventData eventData ) { ... }public override void OnDrag( PointerEventData eventData ) { ... }public override void OnEndDrag( PointerEventData eventData ) { ... }public override void OnDrop( PointerEventData eventData ) { ... }public override void OnMove( AxisEventData eventData ) { ... }public override void OnPointerClick(PointerEventData eventData){...if(onClick != null){onClick(eventData , this);}...}public override void OnPointerDown (PointerEventData eventData) { ... }public override void OnPointerEnter (PointerEventData eventData) { ... }public override void OnPointerExit (PointerEventData eventData) { ... }public override void OnPointerUp (PointerEventData eventData) { ... }public override void OnScroll( PointerEventData eventData ) { ... }
}
如上代码,篇幅有限,我把事件部分最重要的部分摘了出来,组件的挂在,事件的调用,以及参数的设置。
到这里我们有了统一管理UI的管理类,有了界面的基类,有了处理输入事件句柄的事件类,就能开始拓展UI了,大部分UI界面我们都能够处理,但很多原生的组件用起来不是很好,效率也特别的差,所以我们需要构建自己的高效的UI自定义组件。
Ⅳ.自定义组件。
除了NGUI和UGUI本身的组件外,我们自己的自定义组件是必不可少的,特别是游戏项目,无论大小,都需要有自己的自定义组件,自定义组件不仅能让程序员在写逻辑时快速上手,满足项目的设计需求,而且也能起到对UI优化的作用,尤其在元素多的组件内。
下面介绍项目中最常改造的组件:
① UI动画组件。
动画在UI中扮演重要的角色,这里主要说的是Animation的K线动画。如何让Animation在美术人员手里自如的制作,并且让程序员能方便调用是关键。UI动画组件里应该有什么呢?我们暂时命名为 UIAnimation 好了。首先它肯定要依赖 Unity3D 的 Animator 组件 [RequireComponent (typeof(Animator))]。其次它要有播放(Play)接口用来播放指定动画,Play的参数包括,动画名,播放完毕后的回调函数委托。再次他可以在无需程序调用的情况下自动播放,因此在 public 变量中需要 AutoPlay 这个参数,这样美术人员就可以在 Unity3D 界面上设置自动播放而无需程序调用了。最后美术人员需要在自动播放时选择指定的动画名和是否循环播放,以及循环播放间隔。这样就基本成形了,接下来要做的事就是我们对抽象的 UIAnimation 里完善以上的功能。
② 按钮播放音效组件。
在点击按钮时会需要播放音效,这是每个项目必要的组件。功能也挺简单,当输入事件触发Click事件时发出绑定的声音文件就可以了。不过很多项目用到的音效系统并不是Unity3D原生态的音效系统,需要自己为这些系统定制组件。
③ UI跟随3D物体组件。
项目中很多时候需要UI元素来跟随它们,比如游戏中的血条,又比如场景中建筑物头上的标志等等,因此UI跟随3D物体的组件非常必要。它的功能实现起来也挺简单的,不断地计算3D物体在屏幕中的位置,来确定UI位置,并且在前后位置不同时再进行更改以避免不必要的移动。
④ 无限滚动页面组件。
在滚动的菜单栏里,通常类似于游戏中的背包界面,如果有几百个UI元素同时生成,或同时滚动时,效率会非常低,因为UI在每帧都需要重新构建Mesh,每一次的滚动都会引起不小的CPU消耗。因此一个自定义的无限滚动页面组件来,替换原来的模式,让CPU花最小的代价来运行这个滚动页面是非常有必要的。那么这个无限滚动页面组件关键点在哪呢?设想下,这么多UI元素一起生成,一起移动,都是一件很费力的事,我们需要减少UI元素的数量。最好减少到与在屏幕上显示的数量差不多,利用看不见的UI元素,来补充能看见的元素,可以描述为一个把上下UI元素不可见时的再利用过程。我们就拿游戏里的背包界面来举例吧,500个物品在背包界面中时,实例化,初始化,滚动都会很费劲,我们可以减少UI元素在背包界面里的显示数量。当UI元素滚动时一部分元素被遮挡住时,不再需要他们显示了,这时我们就可以对这些元素进行再利用。当上面有一行元素被遮挡住,可以被再利用时,我们就把他们移动到下面去,让他们变成下面的背包物品元素。这样不断得滚动,在表现上跟真的有500个物品滚动过程一模一样。这样就可以大量地削减组件消耗的CPU,不管有多少物品在背包里面,也不会引起CPU的负担了。
⑤ 其他组件。
其他组件,比如美术数字组件,让美术制定的数字展示得更好,又比如暴击数字是特殊的图片数字等。又比如计数组件,可以让数字滚动的更加漂亮,又比如在获得游戏币时数字会像动画一样跳动由慢到快。再比如,针对UGUI改变颜色动画时过于消耗CPU而设计的优化组件,让动画只改变组件的颜色值,由组件来改变UI元素的材质球颜色,这样能省去很多重构Mesh导致的CPU消耗。
编写自定义的UI组件的目标就是,增加更多通用的组件,减少重复劳动,让程序员在编写UI界面时更加快捷高效,同时也提升了UI的运行效率。拥有属于自己的一套自定义套件,对项目来说也是非常有价值和高效的一件事。
UI(六) - 如何架构UI框架相关推荐
- JQuery Smart UI 简介(五) — 框架设计【前篇】(前台UI层架构)
有不少朋友,在使用演示demo时,出现了打不开access数据库的情况,经园友@smalltide的帮助下,找到了问题,数据层读取默认连接信息时有误(该问题也可能存在其他多数据并存的系统),暂时不更新 ...
- WPF案例 (六) 动态切换UI布局
原文:WPF案例 (六) 动态切换UI布局 这个Wpf示例对同一个界面支持以ListView或者CardView的布局方式呈现界面,使用控件ItemsControl绑定数据源,使用DataTempla ...
- 跨屏html ui,Amaze UI(HTML5 跨屏前端框架) v2.7.2
Amaze UI 2.7.2 更新日志:2016-08-17 JS: Improved #900 处理Modal元素停止冒泡引发的使用不变问题: Improved #901 调整Tabs样式,适应元素 ...
- 基于vue(element ui) + ssm + shiro 的权限框架
zhcc 基于vue(element ui) + ssm + shiro 的权限框架 引言 心声 现在的Java世界,各种资源很丰富,不得不说,从分布式,服务化,orm,再到前端控制,权限等等玲琅满目 ...
- 精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能)
精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能) 原文:精通 WPF UI Virtualization (提升 OEA 框架中 TreeGri ...
- Ui自动化概念+Web自动化测试框架介绍
目录 UI 1.UI自动化测试概念:我们先明确什么是UI 2.为什么对UI采用自动化测试? 3.什么项目适合做UI自动化测试? 4.UI自动化测试介入时机 5.UI自动化测试所属分类 Web自动化测试 ...
- 【Unity】虚拟现实 VRTK插件使用教程(六)VR UI (UGUI和VRTK的事件处理机制)
文章目录 UI 2D UI 画布设置 相机设置 用途 3D UI 画布设置 交互 UGUI事件处理流程 VRTK事件处理流程 VRTK源码解析 UI UI通常按类别分为三种,即2D UI.3D UI. ...
- 6. Unity3d Lua框架 - xLua - UIManager UI管理系统:提供UI操作、UI层级、UI消息、UI资源加载、UI调度、UI缓存等管理
UIManager.lua – UI管理系统:提供UI操作.UI层级.UI消息.UI资源加载.UI调度.UI缓存等管理 – 注意: – 1.Window包括:Model.Ctrl.View.和Acti ...
- Jetpack Compose--声明式UI替代传统命令式UI的新产品
Jetpack Compose--声明式UI替代传统命令式UI的新产品 声明式UI与传统UI创建方式(命令式UI)的区别 Jetpack Compose的优势 Jetpack Compose的缺点 最 ...
最新文章
- Spring之定时任务基本使用篇
- 微信在诺基亚手机登录服务器繁忙,马化腾正式宣布:禁止在该手机上登录QQ和微信?网友:诺基亚再见...
- JS实现select去除option的使用注意事项
- 以Java的视角来聊聊BIO、NIO与AIO的区别
- 2019年各大银行最新存款利率,这么存可以获得更多的利息!
- sqlserver 获取所有表的字段类型等信息
- Codeforces 18C C. Stripe
- java getSource()和 getActionCommand()区别
- 云物大智题库--人工智能
- ACCESS数据库查询
- ps,pr,ae安装插件出现“无法加载扩展,因为它未正确签署”解决办法
- 信捷plc485通信上位机_STM32L476R快速入门——串口与上位机通信
- 美团点评2020校园招聘商业分析师一面(2019.9.5)
- 在指定字符串后面插入字符串
- 32位cpu和64位cpu对内存地址和内存单元的差别
- 2019TFE计算机科学排名,2019TFE美国会计专业研究生排名前50
- 石川: 主流多因子模型巡礼
- 浪潮nf5220服务器做系统,【浪潮NF5220参数】浪潮NF5220系列服务器参数-ZOL中关村在线...
- vim底部查找最后一个关键字
- Python实现局域网IP端口扫描