MyGUI源码还是比较简单的,我们在这里只是简单分析相应控件如何生成,如何渲染。
我们分成三个部分来说明,分别是资源类型,控件生成,控件渲染。
资源类型:
ResourceSkin:用于记录各个控件状态的纹理坐标位置,关联的图片,如何划分SubSkin及文本。
- SubSkin有主要有几种Type,主要是SubSkin, MainSkin, EditText, SimpleText, RoatingSkin, PolygonalSkin, TileRect. 分别对应ISubWidget里的几种子类实现。
- ResourceSkin用来生成上面所说的各种ISubWidget.
ResourceLayout:用于记录控件结构,用于表示Widget的树成分,Widget与子Widget类型,大小,属性等。
ILayer:表示层的概念,和Ogre中的Overlay比较类似,一般我用把在某些时间一起显示的控件放入同一层中,如刚进游戏界面和进入游戏后的界面UI应该用不同的Larer.
- MyGUI默认给我们分配一些Layer,请看文件MyGUI_Layers.xml,如Overlapped,Main,Popup等是我们比较常用的。
- MyGUI注册了二种类型的Layer,分别是SharedLayer与OverlappedLayer,其中SharedLayer层上控件如果不重叠可以用这个,否则用OverlappedLayer,原因见后面.
控件生成:
ISubWidget:前面说ResourceSkin有提到,他的子类实现主要有EditText, MainSkin, SubSkin, TileRect, PolygonalSkin, RotationSkin ,SimpleText这些,ISubWidget负责最后的渲染,就是说控件到最后都是组合成ISubWidget来进行渲染.
- 上面所说的子类实现,主要是从二种ISubWidget的子类继承,一种是ISubWidgetRect,一种是ISubWidgetText.简单来说,一种没有文本,只有位置,颜色信号,一种包含文本信息,如字体名,字体大小等.上面说的EditText与SimpleText是从ISubWidgetText继承,别的ISubWidget都是从ISubWidgetRect继承.
LayerItem:一种树型结构,叶子节点为ISubWidget,非叶子节点为LayerItem,能绑定到Layer和LayerNode上,也能解绑.
SkinItem: LayerItem的子类.提供根据资源ResourceSkin文件生成ISubWidget列表功能,并提取一个ISubWidgetRect到字段mMainSkin,一个ISubWidgetText到mText上,没有则为空.
Widget:MyGUI中控件的父类,这个类就相当于winForm中的Control,本身是树型结构(Widget包含子Widget列表).可以对照上面的ResourceLayout的图.
- 继承多个类,其中继承SkinItem类能根据ResourceSkin生成子ISubWidget列表,继承WidgetInput 提供与键盘和鼠标事件的交互,继承IcroppedRectangle提供大小,绝对位置或相对父位置信息.
- Widget可以由ResourceLayout资源序列化得到,其中每个节点Widget属性上的Type指点对应生成子类控件(Button/EditBox等),而对应的Skin则指明对应的ResourceSkin,生成SkinItem信息.
Button/EditBox等:这些基本控件MyGUI中的WidgetManager注册,并且由MyGUI本身提供的基本Templates文件提供的ResoureceLayout生成Widget.
控件渲染:
IVertexBuffer:一块缓冲区数据,数据块格式P3fT2fC1i,就是每个顶点用三个float数据表示顶点,二个float表示纹理坐标,一个uint数据表示颜色,根据不同的平台来实现这个子类.
IRenderTarget:渲染目标,一般是当前窗口.
RenderItem:简单来说,把多个SubWidget组合在一起渲染,前面我们可以看到,同一类型的Skin一般都是用一张图,那么只要记录各顶点的纹理坐标位置就可以合成一个Pass渲染完成.MyGUI效率高从这个类可以看到原因.
LayerNode:管理Layer中的RenderItem,当把Widget加入Layer中,实际是把Widget中的顶层ISubWidget添加到对应的LayerNode中的RenderItem中,这样ISubWidget,RenderItem,LayerNode三者就联系在一起了.
- 对应上面的Layer, 一个OverlappedLayer包含多个LayerNode,而一个SharedLayer只有一个共用的SharedLayerNode.后面LayerNode是一个接一个渲染的,那么如果各顶层ISubWidget有覆盖,然后又添加到SharedLayerNode,他并不能保证覆盖渲染正确.
- 并不是所有的Widget都能加入到LayerNode, CroppedParent为空或是没有父Widget的Widget才会生成并添加到LayerNode中,其中没有父Widget的Widget一般是那种窗口,而CroppedParent为空一般是Popup类型(如PopupMenu).
加载控件与渲染流程
1.LayoutManager::loadLayout 加载layoutName 得到ResoureceLayout.
2.ResourceLayout::createLayout 根据ResoureceLayout中的WidgetInfo(对应ResoureceLayout资源文件中的Widget节点)创建Widget.
3.ResourceLayout::createWidget 根据控件是否是模版控件(对应的LayoutResourece file是否是模版文件,默认的Button /EditBox 等都在一个模板LayoutResource 文件里).分别调用不同的方法生成Widget.
4.LayerManager::attachToLayerNode 在上面创建Widget后,会把对应的Widget添加到他所属于的Layer层中.(注意前面说过,CroppedParent为空或是没有父Widget的Widget才会添加到Layer中的LayerNode中.这样,只有顶层Widget才会添加到LayerNode中.)其中OverlappedLayer每个顶层Widget生成一个对应的LayerNode,而SharedLayer只会生成一个SharedLayerNode,所有顶层Widget同享这个SharedLayerNode.这样前面说的控件如果不重叠可以用SharedLayerNode就有了解释,因为同层LayerNode中的RenderItem的渲染顺序我们控制不了,但是不同LayerNode我们可以控制渲染顺序,如二个部分重合的窗口,我激活那个,我就让那个RenderItem后渲染.这样激活的那个就显示在后面了.
5.LayerNode::attachLayerItem 把顶层Widget附加到LayerNode中.
6.LayerItem::attachToLayerItemNode 编历LayerItem(前面有说过)中的树层次,把所有叶子节点的ISubWidget放入一个列表.
7.ISubWidget::createDrawItem(注意,ISubWidget没有这方法,但是其最后的实体子类都实现了这个方法).在这里根据上面的LayerItem叶子节点中的ISubWidget列表,我们来创建对应LayerNode中的RenderItem.
8.LayerNode::addToRenderItem 根据ISubWidget中的图片决定是否生成新的RenderItem.并把ISubWidget添加到RenderItem中.
- 其中ISubWidgetText的子类添加到LayerNode的第二通道上,而ISubWidgetRect添加到LayerNode的第一通道上.
- 在这里并不是一个ISubWidget创建一个RenderItem,如果多个ISubWidget都用的相同图片,则只生成一个RenderItem.
如上这些,是在初始化Layout资源文件发生的过程,那么渲染是如何进行的.
在MyGUI.OgrePlatform中,Ogre渲染平台实现了MyGUI中的RenderManager实现类OgreRenderManager.其中OgreRenderManager 实现了监听类Ogre::RenderQueueListener,这个监听类具体方法以及作用请看Ogre 监听类与渲染流程.
当我们调用OgrePlatform::initialise时,把OgreRenderManager注册到SceneManager中的RenderQueueListener中.这样Ogre开始渲染通道时,进入到OgreRenderManager::renderQueueStarted中,然后交给RenderManager::onRenderToTarget.
1.RenderManager::onRenderToTarget MyGUI开始渲染.
2.ILayer::renderToTarget MyGUI中的每个层开始渲染.
3.LayerNode::renderToTarget 每层中的每个LayerNode开始渲染.
4.RenderItem::renderToTarget 渲染二个通道里的每个RenderItem.(RenderItem如何来的,请看前面.)
5.ISubWidget::doRender 渲染RenderItem里的ISubWidget列表.根据当前ISubWidget计算位置,大小,颜色,纹理坐标等等,整合所有ISubWidget成一个IVertexBuffe与一个ITexture.
6.OgreRenderManager::doRender 调用Ogre渲染上面的IVertexBuffe与ITexture.
如上一个渲染结束,大致总结下,MyGUI最后就是ISubWidget在更新渲染的数据,而ISubWidget根据资源文件ResourceSkin生成.一个窗口里的控件如果用的是同一样式,同一字体,那么一般二次渲染就行了.
ResourceSkin如果上面说的不是很清楚,可以看下面TabHeaderButtonSkin在Skin editor中的显示.
如下是上面ComboBox中的Layout文件在Layout editor中的界面显示.
写完,睡觉.