UniVue目前有针对UGUI的扩展实现,后续有时间再慢慢增加对FariyGUI和UIToolkit的实现,当然你也可以自己扩展实现,这些扩展往往是针对不同底层UI框架组件的实现,大多数通用功能模块在UniVue中已经实现。
目前已经参加工作近一年,这一年里的工作经验彻底让我舍弃了过去的UniVue的设计,因为过去的设计不适合团队协作,以及程序员的心力负担较重。由于要上班,也是最近几个月零零散散的写框架,每天写一些,唉,怀念大学那会儿每天单纯写代码的日子,无忧无虑~
有时候在想现在的AI已经这么nb还需要做这些事情吗?每个人都有自己的看法吧,我不知道,我只是出于乐趣写这些东西,或许打发无聊的时间吧,单纯瘾大。
UniVue内部响应式本质是数据驱动+事件驱动,数据层主要是通过继承BaseModel,在数据发生变化时通知渲染层,事件层则是UI事件和自定义事件被渲染层监听时触发渲染。
渲染层的结构是一个多索引有向无环图,任意路径的末尾节点都是渲染函数,当数据变化或事件触发时,抵达渲染节点最多不会超过5层,即时间复杂度为O(1)。
带延迟性的响应式渲染。考虑到UI渲染大多数不需要很高的渲染频率,因此内部默认的渲染频率为0.1秒,即在这0.1秒内被触发的所有渲染只会被当作一次渲染调用,这种带延迟性的响应式渲染可以大幅降低重复渲染带来的问题。比如当前帧内会对模型的属性A、B、C修改多次,如果每次都是立即式渲染,渲染函数会被多次重复调用,然而事实上当前帧的所有变化都可以收敛为一次渲染调用。当然也可以修改延迟调用的频率,最低的渲染频率为延迟一帧。
M代表数据Model,E代表事件Event,P代表数据Model的属性,R代表渲染函数,G代表RGraph。
一次完整的渲染流程举例:当数据M1的P1属性发生了变化时,从Entry进入索引得到M1绑定了两个RGraph:G1和G4,从路径G1->M1->P1->R3得到渲染函数R3,从路径G4->M1->R5得到渲染函数R5,从路径G4->M1->R6得到渲染函数R6,得到所有要触发渲染函数为R3、R5、R6,这些函数不会立即执行,而是被放到一个HashSet中,等待渲染延迟结束后才被执行。如果在等待期间事件E2触发,从路径G2->E2->R2,G2->E2->R3,G4->32->R6得到渲染函数R2、R3、R6,放入HashSet,去重得到R2、R3、R5、R6,避免了重复渲染。特别是一些渲染函数要加载资源时,这种情况下会极大减少因为UI刷新导致的性能卡顿。
本质绑定就是一个watch函数,数据和事件被监听。
UniVue的UI工作流是基于组件模块化的,BaseComponet(受BaseView的管理)、BaseView是模块化的基础,你的项目中自己做好的预制体挂载这些脚本,内部已经实现了绝大多数的功能,如事件绑定、定时器、协程(UniVue内部实现的一套C#层面的协程,不是Unity的协程)、渲染绑定、帧回调、秒回调、动态创建子View、动态创建组件等等。
在初始化UIMgr时,你需要将你项目中的资源管理接口暴露给UniVue使用,即实现IUIPrefabLoader接口。
UniVue是一个很轻量的UI框架,最核心的功能就是响应式,解决实际项目中UI代码多而容易很杂的问题,响应式开发让我们只需要关系架构上的设计而非写这些毫无卵用的低级代码,固定的工作流交给AI可以获得高效同时犯错率极低的输出。
多语言、资源管理等这些UniVue都没有,因为这些功能每个功能都能单独拿出来做一个框架,市面上已经有很多优秀的这些框架,因此不再重复造轮子,UniVue只暴露了接口,需要你自己根据自己的项目使用的框架进行定制化实现。
目前由于精力有限,只针对了UGUI进行了实现。如果你想扩展其他UI框架,如FariyGUI、UI Toolkit,完全可以在现在代码的基础上剥离UGUI部分(只有极少部分,主要部分就是UGUI是基于GameObject的,因此内部BaseView、BaseComponet的设计也是基于此的)。你只需要单独实现BaseView、BaseComponet就能无缝实现基于其他框架的UniVue。
渲染层:RGraphs、RGraph、RNode;
数据层:BaseModel;
事件层:EventMgr;
BaseUI、BaseView、BaseComponent、UIMgr;(每个BaseUI持有一个唯一的RGraph节点)。
BaseView是可嵌套的树型结构单元,子View受到父View的管理。UIMgr只能管理根View,同时负责层级管理,如果你要根据自己的项目定制化实现层级管理,需要实现IUILayerMgr接口,在初始化UIMgr时给UniVue使用。
继承自BaseView、BaseComponent可以自动生成UI代码,生成对一些UI组件的属性引用,你可以扩展自己的生成规则,只需要继承UICodeGenRule类即可,父类的Traverse方法可以方便的遍历整个UI结构。
UniVue内部通过long类型生成红点树结构,相比传统的字符串方式手动构建红点树结构,大幅降低了内存占用,特别是一些小游戏中,红点系统是标配了,随着项目的扩大,后期的红点系统字符串占用比例相当高。但是long类型也有缺点,就是红点树的数量不能超过65535,这是因为内部使用高2字节填充根节点,树的递归深度不能超过6层,除了根层级,其余层级不能超过127个节点数量,在实际中,这百分百已经够用了。
同时配置有红点编辑器,可以在运行时方便地进行调试红点。
TimerMgr内部使用小顶堆数据结构得到每次需要等待的最少时间,这样等待时间结束只需要将堆顶的定时器拿出来执行即可,避免了每帧遍历所有定时器进行统计。堆的数据结构特点,每次删除、添加的时间复杂度都是O(logN),因此堆的更新也是非常快,内部定时器的存储任然是基于哈希表的,定时器的删除、添加时间复杂度为O(1)。
事件中心有个最烦人的问题就是不知道调用来源,不知道在哪儿注册的,调用堆栈里面拿不到源。EventMgr解决了这个问题,在Dispatch事件时可选择打印事件源,可拿到这个事件的来源信息。
UniVue内部使用的协程不是基于Unity的,为了统一管理BaseUI的生命周期,以及提供更强大的协程功能,而在C#层基于迭代器的思路实现了协程(Unity的协程本质也是这样,只不过由C++层的MonoBehaviour驱动)。这个框架也是我写的,源自:Avalon712/VCoroutine: C# Coroutine For Unity3D