快速架构一个简单易用的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框架相关推荐

  1. JQuery Smart UI 简介(五) — 框架设计【前篇】(前台UI层架构)

    有不少朋友,在使用演示demo时,出现了打不开access数据库的情况,经园友@smalltide的帮助下,找到了问题,数据层读取默认连接信息时有误(该问题也可能存在其他多数据并存的系统),暂时不更新 ...

  2. WPF案例 (六) 动态切换UI布局

    原文:WPF案例 (六) 动态切换UI布局 这个Wpf示例对同一个界面支持以ListView或者CardView的布局方式呈现界面,使用控件ItemsControl绑定数据源,使用DataTempla ...

  3. 跨屏html ui,Amaze UI(HTML5 跨屏前端框架) v2.7.2

    Amaze UI 2.7.2 更新日志:2016-08-17 JS: Improved #900 处理Modal元素停止冒泡引发的使用不变问题: Improved #901 调整Tabs样式,适应元素 ...

  4. 基于vue(element ui) + ssm + shiro 的权限框架

    zhcc 基于vue(element ui) + ssm + shiro 的权限框架 引言 心声 现在的Java世界,各种资源很丰富,不得不说,从分布式,服务化,orm,再到前端控制,权限等等玲琅满目 ...

  5. 精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能)

    精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能) 原文:精通 WPF UI Virtualization (提升 OEA 框架中 TreeGri ...

  6. Ui自动化概念+Web自动化测试框架介绍

    目录 UI 1.UI自动化测试概念:我们先明确什么是UI 2.为什么对UI采用自动化测试? 3.什么项目适合做UI自动化测试? 4.UI自动化测试介入时机 5.UI自动化测试所属分类 Web自动化测试 ...

  7. 【Unity】虚拟现实 VRTK插件使用教程(六)VR UI (UGUI和VRTK的事件处理机制)

    文章目录 UI 2D UI 画布设置 相机设置 用途 3D UI 画布设置 交互 UGUI事件处理流程 VRTK事件处理流程 VRTK源码解析 UI UI通常按类别分为三种,即2D UI.3D UI. ...

  8. 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 ...

  9. Jetpack Compose--声明式UI替代传统命令式UI的新产品

    Jetpack Compose--声明式UI替代传统命令式UI的新产品 声明式UI与传统UI创建方式(命令式UI)的区别 Jetpack Compose的优势 Jetpack Compose的缺点 最 ...

最新文章

  1. Spring之定时任务基本使用篇
  2. 微信在诺基亚手机登录服务器繁忙,马化腾正式宣布:禁止在该手机上登录QQ和微信?网友:诺基亚再见...
  3. JS实现select去除option的使用注意事项
  4. 以Java的视角来聊聊BIO、NIO与AIO的区别
  5. 2019年各大银行最新存款利率,这么存可以获得更多的利息!
  6. sqlserver 获取所有表的字段类型等信息
  7. Codeforces 18C C. Stripe
  8. java getSource()和 getActionCommand()区别
  9. 云物大智题库--人工智能
  10. ACCESS数据库查询
  11. ps,pr,ae安装插件出现“无法加载扩展,因为它未正确签署”解决办法
  12. 信捷plc485通信上位机_STM32L476R快速入门——串口与上位机通信
  13. 美团点评2020校园招聘商业分析师一面(2019.9.5)
  14. 在指定字符串后面插入字符串
  15. 32位cpu和64位cpu对内存地址和内存单元的差别
  16. 2019TFE计算机科学排名,2019TFE美国会计专业研究生排名前50
  17. 石川: 主流多因子模型巡礼
  18. 浪潮nf5220服务器做系统,【浪潮NF5220参数】浪潮NF5220系列服务器参数-ZOL中关村在线...
  19. vim底部查找最后一个关键字
  20. Python实现局域网IP端口扫描

热门文章

  1. centos8重启网卡命令nmcli
  2. Python爬虫爬取动态网页
  3. 端到端的网络流量监控
  4. revit二次开发——如何选取元素(revit2016)
  5. 蓝牙mesh中的TTL解读
  6. 《小白兔到大黑牛》第十四篇Hadoop中五个进程作用
  7. Oracle数据库常用SQL语句查询
  8. 计算机表格数字整体加,excel表格整体数据加1怎么处理-怎样才可以在EXCEL表格的数字中全部加1??...
  9. 2020-06-11
  10. Linux下hmmer安装避坑必看