Home

Awesome

UniVue

**UniVue是一给基于MVVM思想的Unity的UI解决方案框架。**它支持数据和模型的双向绑定,能够实现C#中WPF的功能,其底层UI模块依赖Unity的UGUI框架,除了数据和视图的双向绑定外,UniVue还实现高性能的UI事件系统,真正让你只关注游戏中数据的行为和视图的美术表现,这两者的交互全部依赖UniVue就能实现。

开发说明

由于每个版本的源码有变动,实现方式也有变化,之前下面讲解中使用的图中的某些函数的名字已经改名,可能某些流程也已经改变,但是由于个人精力有限,实在挤不出时间写更完善的文档了,只能每次一点一点的写。emmmm,如果在使用过程中遇到了问题可以发邮件或者加我QQ,也可以提交Issue!QQ:2947897147;邮箱:2947897147@qq.com

我会在博客中对UniVue中的功能的使用进行讲解已经底层的实现逻辑、算法的实现。由于目前的电脑配置很差,开启Unity后录屏很卡,所有无法进行开视频进行讲解,emmm,后面升级装备了会在B站开视频进行讲解。

CSDN个人博客:Avalon712-CSDN博客

这个仓库之后发布功能稳定的版本,因此很多最新的功能在这里是无法看见的,如果你想预览最新的功能,不妨fork一下这个仓库:https://github.com/Avalon712/UniVue-Develop 。这个仓库是我开发UniVue框架使用的项目,所有最新以及正在开发的功能都在这里可以看见,里面对每个功能都会有单元测试,也是学习UniVue框架的不二选择。

UniVue的扩展框架:UniVue源生成器

仓库地址:https://github.com/Avalon712/UniVue-SourceGenerator

关于UniVue中源生成器更详细的说明,请看这篇博客:UniVue更新日志:使用源生成器优化Model和ViewModel层的设计-CSDN博客

注:C#的源生器将会是完全替代反射的必要框架。通过生成低级代码完成对反射的替换,同时大幅度提高框架的易用性;

版本说明

目前只对2021及其以上有支持,其它版本没试过,不知道什么情况,源码采用了很多C#9的语法,C#9似乎实在Unity2021开始支持的,如果你要使用源生器或许只能使用2021版本以上。同时不会对Unity都遗弃的功能进行支持,如:Text组件。因为如果对这些相同功能的组件也进行支持会导致ViewModel非常冗余,如果你要进行支持建议自己通过继承PropertyUI,然后看对于的组件是怎么实现更新逻辑的,照抄一遍就可以了。ViewModel的PropertyUI有将近30多个类,emmm,这就是我不打算支持的原因。后面PropertyUI还会进行扩展,可能会有70多个类。后续有时间了再看看要不要对2021版本以下的进行测试支持。

大版本核心功能一栏表

v1.0.0

第一个版本只能算是一个基础的版本,实现了几乎所有核心的模块(还有几个核心模块在开发中。)

v1.1.0

v1.2.0

v1.3.0

v1.4.0

v1.5.0

v2.0.0-preview

未来版本核心功能

未来版本将会出现的核心功能

  1. UI特效。目前第一个版本的Tween补帧动画模块较为简单,无法支撑起更多复杂的UI效果,在后续版本中将引入强大的UI特效模块实现更多花哨的UI效果;
  2. UI优化算法。UniVue在后续的版本可能会对UGUI的部分功能进行扩展,特别是Canvas的重绘逻辑,或许将基于屏幕后处理的原理实现一个更高性能的UI合批算法,降低UI的合批次数,减少Canvas的重绘,这个模块预计在UniVue3.0发布;
  3. C#源生成器。第一个版本中的UniVue的源生成器只能对Model层进行优化,在后续版本中将彻底进行完善源生器框架,使用源生器生成所有的低级代码,将所有的低效代码进行优化重组,后续源生器或许将作为内置的选项而不是可选项;
  4. **其它解放编码的功能:**更多的视图组件,如:灵活可变的循环列表视图,可多级展开的树形视图、多级菜单视图、大小可自定义的视图等等。
  5. 对Lua的支持:后序将会实现对Lua的支持。

二、Model层的使用

不使用UniVue的源生成器

在不使用UniVue的源生成器时,你需要编写更多的低级代码,即在属性改变时调用Vue.Updater方法去更新UI,同时实现View层更高Model层数据的行为,此外为每个实现IBindableModel接口的类提供BindableTypeInfo对象,描述模型的绑定信息。

使用UniVue的源生器

使用UniVue源生器需要你的Unity版本在2022及其以上,经过我的测试,在这个版本以下使用源生成器会出现源生成器生成的源文件无法被Unity的编译器编译的BUG。

在使用源生成器后,以下特性的功能将是可用的:

三、ViewModel

​ ViewModel实现类是PropertyUI和UIBundle类。

1.PropertyUI

​ 这是一个细腻度的类,它关心的是一个UI与Model的某个属性值的绑定关系,以及如何将绑定的数据显示到绑定的UI组件上。该类是一个抽象类,其子类有很多实现,所有子类实现的目标都是一个:如何将可绑定的各种属性类型的数据显示的UI上。如:string类型的数据可以显示到TMP_Text、TMP_InputField两个UI组件上,而枚举类型可以显示当ToggleGroup、TMP_Dropdown、TMP_Text、TMP_InputField组件上......详见代码中的各种实现。

2.ModelUI

​ 这个类顾名思义,是维护了一个View与一个Model的关系(注意:一个View可以绑定多个Model,一个Model也可以绑定多个View)。同时这个类提供了对于那些可交互UI组件的对模型更新的通知行为。当ModelUI管理的PropertyUI中有UI更改了模型的数据就可以通过此类来通知Model进行更新,而更新权在IModelUpdater上,这意味者你可以实现单向的绑定。

3.ViewUpdater

管理所有的ModelUI对象,负责视图的更新,维护VMTable。

四、View

视图是本框架一个很重要的概念,简单而言他是一个由很多UI组件构建成的一个完整用户界面。UniVue所有规则的最小区别单元就是一个View,每个View都必须有一个承载它的GameObject,这个GameObject在UniVue中成为ViewObject。

六个等级:Transient(瞬态,即打开后一段时间后会自动关闭)、Common(通用型,即可打开可关闭)、Modal(模态级别,打开后如果不关闭将不允许打开任何视图)、System(系统级,每次打开的同级视图中的系统级别视图只能有一个)、Permanent(持久级,不会被关闭,永远处于打开状态)、Unmanaged(非托管级,这个级别的视图的打开关闭不会被检查,同时不会被压入视图堆栈)

一种关系:父子关系

父子关系的确定是根据ViewObject的层级关系确定的,拥有同一个父亲的视图处于同级视图,所有根视图(没有父亲的视图)也属于同级视图

四个事件:Open、Close、Skip、Return。

==所有视图将自动被ViewRouter管理,ViewRouter将实现上面所有的功能。同时对所有非Unmanaged级别的视图的打开、关闭都必须经过ViewRouter。==

2.IView

定义了视图的所有行为,实现此接口的类被视为一个View对象,只有View对象都有它的ViewObject,这个ViewObject是自动加载的。

3.ViewRouter

这个类管理这所有的视图以及控制视图的打开、关闭行为。

五、事件系统

UniVue除了提供实现数据、视图的双向绑定外还提供了强大的事件系统。事件系统可以方便地实现对UI进行事件绑定,以及从UI组件上获取到事件参数。

1.EventCallAttribute

在方法上注解该特性,当此事件触发时将会进行调用此函数。标记此特性的方法不能是一个泛型方法以及方法参数不能有in、out关键字。同时该方法中的方法参数的类型以及参数名如果与UI中标记的一致,那么将会对其进行自动赋值。更强大的是,如果方法参数是一个自定义的实体类型,在满足实体的属性名以及类型与UI中标记的一致,也能对其进行赋值。(有关这一部分的详细细节,参阅命名系统关于事件参数映射为方法参数的说明)

==注解该特性的方法支持的方法参数类型:int、float、string、bool、enum(需要能够从UniVue.ViewModel.Enums中获取到枚举的信息)、EventUI、ArgumentUI、EventCall、Sprite、自定义类型(自定义类型需要注册IComstomArgument接口)。==

2.EventUI

在UI组件中命名的事件将会对应一个EventUI事件。该事件封装的信息包括:触发该事件的视图名称、事件参数。注意:即使是一个相同的事件,在不同的视图下触发时,其事件参数是不同。

3.ArgumentUI

从UI组件上获取到的事件参数。

4.EventCall

每个注解了EventCallAttribute的方法将会对应一个EventCall对象。他表示当前正在被执行的事件。

5.IEventRegister

该接口定义了向事件管理器中注册事件、注销事件的行为。

8.EventManager

管理所有的事件。

六、简单的UI缓动动画系统

由于不想框架依赖诸如DOTween等第三方缓动动画技术框架,UniVue内部实现了一个简单的缓动系统。

1.DefaultViewTweens

所有默认的UI动画都由该类进行实现。

2.TweenBehavior

通过该类可以方便创建各种缓动效果。

3.TweenTaskExecutor

执行缓动任务。

七、命名系统

​ 命名系统是整个UniVue的核心驱动,UniVue的所有核心模块都依靠命名系统完成。

1.命名规则解析引擎 - RuleEngine

这个类实现了所有命名规则的解析、匹配。如果你的对命名还是不太清楚,可以看此类的源码。

2.通用说明

UniVue不支持所有遗留的UI组件,如:Text、InputField、Dropdown。

UI组件绑定模型支持的类型:

各种类型允许绑定的UI组件

UI组件名称

关于首字母大小写取决于命名格式(NamingFormat)

3.数据绑定命名规则

数据绑定的命名规则决定了你的UI组件与模型数据是否能够实现双向绑定的关键。

1)命名规则说明

==[ModelName|TypeName] + PropertyName + UI组件名称==

2)举例说明

4.视图事件绑定命名规则

1)命名规则说明

规则说明:

​ ==路由事件(Open|Skip|Close|Return) + 视图名称 + 按钮UI组件的名称(仅支持Button和Toggle)==

2)命名举例说明

5.事件命名规则

1)命名规则说明

①事件触发器命名

​ ==Evt+ 事件名+ UI组件名称==

②事件参数命名

​ ==Arg + 事件名[参数名] + UI组件名称==

③事件触发器+事件参数命名

==Evt&Arg(或evt&arg) + 事件名[参数名] + UI组件名称==

2)举例说明

6.综合命名

如果一个UI组件即绑定了数据又是一个事件参数,则使用" & "进行隔开两个命名规则,**注意'' & ''中&字符的前后均有一个空格!**如:Player_Name_Txt & Arg_Login[Name]_Txt。可以在VueConfig中修改这个字符。

举例说明:

7.关于事件参数映射为方法参数的说明

如果事件参数是Arg_Login[name]_Input,那么方法参数的类型是string同时方法参数名为name,这样才能为方法参数正确赋值;简单而言就是赋值的过程必须是参数类型和参数名和事件参数返回的类型以及参数名一致才能正确赋值。

如果方法参数绑定的是一个自定义类型,那么要让事件参数成功映射为一个自定义类型,那么就要保证属性名、属性类型与事件参数的类型、参数名一致。下面以一个用户注册页面作为讲解:

假如这个页面具有这些UI:

注册事件(事件名为"Signup")回调方法如下:

[EventCall("Signup")]
private void UserSignup(User user){
 //......
}

要想成功将上面的事件参数映射为User自定义类型的对象,则User的各个属性以及属性名应该与事件参数相对应,即如下:

public class User{
    public string Name{get;set;} //Arg_Signup[Name]_Input
    public string Password{get;set;} //Arg_Signup[Password]_Input
    public string Email{get;set;} //Arg_Signup[Email]_Input 
    
    public User(){} //无参构造函数是必要的
}

同时你需要实现ICustomArgument接口同时注册到EventManager中来完成这个对象映射的过程。否则这个回调将永远不会被调用,为了防止异常,事件调用是保守性的,即所有参数都不为null才会进行调用。

八、编辑器扩展功能

在Unity的最上方的工具栏可以看见"UniVue"字样,点击,会出现提供的三个扩展功能。

RenameEditorWindow

位置: UniVu > RenameEditor

功能:对视图下的所有GameObject按一定的规则进行重新命名,重新命名可以极大的加快组件查找效率,删去所有不必要的组件匹配与查找,提高程序性能。(通过对树结构进行减枝,来实现加快查找)

重名后的访问规则如下:

RuntimeDebugerWindow

位置:UniVue > RuntimeDebuger

功能:可以在运行时实时调试,无需对代码进行调试,方便定位问题;

十、源生成器

最近的大更新中使用了源生成器来提高效率,关于UniVue中的源生成器,请看这篇博客:UniVue更新日志:使用源生成器优化Model和ViewModel层的设计-CSDN博客