在过去的8年里,我经过数次失败的GUI系统设计经历,最终找到了一种算是可以的方案。这个方案使用一种类似MVC的架构。主要有以下几个大的部分:
【sprite系统】
sprite是一个从古老的游戏中一直演化过来的概念,而在这里,它代表了屏幕上的一个次元(层)中所有可见元素。
它描述了一个屏幕上的矩形,以及和其他矩形的关系。
它具有自己的更新和绘制方法。
它具有自己的各种特性,比如可拖动,比如事件冒泡。
同时,它还有一个称为控制器的东西,这个东西会在创建或者attach的时候对sprite进行修改或者初始化。会处理sprite的输入事件,并可以接管sprite的绘图处理。
【UI元素】
UI元素是sprite的控制器接口的实现。并且会在初始化时去创建sprite,以达到显示的目的。
一个UI元素就代表一个UI控件,比如一个编辑框,或者是一个combobox。
每个UI元素上都有数个事件的插槽,应用程序可以通过向这些插槽插入事件处理来关注UI元素。
【UI脚本】
UI元素的插槽不仅可以插入一个事件处理,还可以插入一个UI脚本的方法索引。
而脚本系统和脚本方法索引,都是接口和无特定指向意义的数值类型,方便脚本扩展。
【引擎接口】
因为UI系统中会用到绘图,贴图,声音,为了通用性,这些都被抽象为接口,以便于不同引擎的支持。
但是这个里面有一点非常重要,就是一定不要依赖于某个特定引擎或者平台的特性进行接口设计,那会让你束手束脚。
【资源管理】
因为UI系统中会涉及到比较多资源,所以,所有资源都被统一管理起来。通过字符串来进行资源索引。返回的是资源的接口指针。
在资源管理内部,对每种资源都有一个缓冲池。当然只做按名字缓冲,毕竟比较深层次的资源缓冲还要依赖引擎接口来完成。
资源里主要有以下几种:图像(贴图组合动画),贴图,声音,字体。
【文字绘制】
文字绘制,由字体资源和绘图引擎接口共同来完成。
字体资源里有个中间件,用来管理字体的缓存,目前是采用256级灰度点阵来存储,在各种绘图引擎和平台中都可以方便的转化为屏幕图元,并且还有灰度来保障边缘混淆的正确显示。
【关于绘图系统】绘图系统中,GUI的需求主要是 贴图, 绘制简单的矩形边框矩形填充, 绘制线条, 裁剪。
GDI绘图,一开始就被放弃了,因为它的整个系统的绘图特性过多,会导致关注点的分散。为了替代GDI绘图,我实现了一套内存位图的绘图库。不过最终这个库还是会把结果丢到GDI的HDC上,只是中间过程里,都是在内存中进行图像的各种混合。
DX和OGL,因为这两个都可以支持贴图,所以实现UI系统需要的接口完全没有问题。在实际GUI的编码和测试中,我使用HGE这个DX的封装接口来间接实现了UI系统所需的绘图接口。
【数据源】UI系统里,每个UI元素都是从数据源中进行读取并创建的。
数据源是一个纯虚接口的形式,在实际编码和测试中,我使用的是XML作为数据源。
XML既有良好的可读性,又易于程序处理。非常适合在这里作为数据源。
【UI编辑器】
相应的,生成数据源的部分,就是靠UI编辑器。
UI编辑器本身也是通过UI系统实现的。
UI编辑器主要包括几个部分:布局编辑器、贴图捡取、属性编辑。其中布局编辑器这里,每个控件的绘制仍旧调用的是它自身的绘图方法,不过为了防止触发控件本身的事件,我用一个控件编辑容器树,代替了原来的控件元素树,树结构还是使用原有的结构,控件被作为空间编辑容器的子元素。
【历史】2002年 第一版UI系统 绘图使用DIRECTDRAW,采用UI元素和UI视觉元素统一的方法,采用一棵树进行绘制和UI自身的管理。没有数据源,没有编辑器,没有脚本系统,完全试验品。失败原因:UI元素的设计问题,以及架构不完善。
2005年 第二版UI系统 绘图采用D3D,采用UI元素和UI视觉元素统一的方法,采用一棵树进行绘制和管理。无数据源,无编辑器,无脚本系统,有事件处理插槽,在客户端进行了小规模的使用。
2008年 第三版UI系统 绘图采用抽象接口,有GDI(内存位图)和D3D实现。采用UI元素和UI视觉元素统一的方法,一棵树绘制和管理。有数据源,无编辑器,无脚本系统,仅在开发和测试环境内进行了使用。失败原因:纠结在UI元素布局的管理上,后发现是UI元素的设计有问题。
2009年 第四版UI系统 绘图采用抽象接口,有GDI(内存位图)和D3D(HGE),采用UI元素和UI视觉元素统一方法,一棵树绘制和管理,有数据源,有编辑器。失败原因:UI元素和视觉元素统一的方法,给UI元素的管理和UI元素的逻辑实现带来很大限制。
2011年 第五版UI系统 绘图采用抽象接口,有GDI(内存位图)和D3D(HGE),采用UI元素和UI视觉元素分开的方法,分为一颗逻辑树,一颗视觉树。数据源仍旧用XML,用第四版的编辑器实现方式。