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中的界面顯示.
寫完,睡覺.