再看DXUT框架
2008-02-07 14:21
温故而知新,前面的DXUT框架我只看了一个外壳,我觉得这次框架的亮点就在于它对于图形界面的支持。如果说Win32 API SDK中gdi部分是主旋律,那么MFC就是一个流行歌曲作家发挥之后完成的作品,而DXUT的图形框架则继承了Platform SDK中的风格,代码行间给人一种高贵的感觉。-,-扯远了,拉回来。因为gdi函数都是C API,不利于代码的重用,DXUT框架则把他们包装成了C++的类,以便于用户继承更改控件特性。D3D的知识学到这里基本上可以看懂DXUT框架了, 所以不必担心,但要是不理解Windows消息机制的话还是不行。
  先了解一下DXUT中关于控件部分的设计架构比较好。这次看的代码大部分集中在DXUTgui.h和DXUTgui.cpp中。控件类的继承关系图是这样的:
   常用的控件大致都已经囊括在内,如果有特殊需要的话可以依葫芦画瓢从CDXUTControl继承。和这些控件有关系的还有几个重要的类,一个是 CDXUTDialog,这个类负责纪录一个对话框的所有属性以及它上面的所有控件信息。另一个是 CDXUTDialogResourceManager,这个类保存了所有注册过的对话框链表,以及这些对话框共享的资源。另外CDXUTElement 这个类保存了需要渲染的元素信息,经常会在渲染函数中用到,最后一个类是可动态增长的链表类CGrowableArray< TYPE >。这个模版类写的不错,不光能用在DXUT框架中,还可以用于很多其他场合。
  具体的代码太多了,这里挑重要的讲解。
1.CDXUTDialogAdd系列函数。
  在初始化一个CDXUTDialog之后就是往这个对话框中添加控件了。在以前的例子中通常是在InitApp函数中调用对话框的Add系列函数来给对话框添加控件的。例如
g_HUD.Init( &g_DialogResourceManager );
g_HUD.SetCallback( OnGUIEvent ); int iY = 10;
g_HUD.AddButton( IDC_TOGGLEFULLSCREEN, L"Toggle full screen", 35, iY, 125, 22 );
  前面两句分别是初始化和设定消息处理回调函数,最后一句是Add系列函数,这个用来在对话框特定位置添加一个特定控件。
2CDXUTDialog::MsgProc
  这个函数是处理对话框消息的。具体处理的消息包括该对话框的移动消息,优先处理焦点控件的消息,对话框大小和移动消息,获得焦点消息,键盘、鼠标消息,以及鼠标丢失消息。了解这些有助于我们充分利用这些功能,并添加自己想要的功能。
3.控件的初始化和显示
  每个控件都有其特定的属性。如果程序为每种控件定义一些默认属性,可以省去我们重复定义的很多麻烦。了解这些我们可以在此基础上修改这些默认属性,使你的控件更加个性化。在DXUT中控件的默认属性是按照这样的步骤定义和应用的。
     在初始化对话框的函数CDXUTDialog::Init中调用InitDefaultElements()为每种控件设定默认属性。
     将这些默认属性添加到CDXUTDialog::m_DefaultElements中
     用户代码调用Add系列函数添加控件。这个Add函数调用CDXUTDialog::AddControl函数,并完成这个控件的一些设置。
     CDXUTDialog::AddControl函数调用CDXUTDialog::InitControl初始化控件,并将该控件添加至对话框的CDXUTDialog::m_Control控件列表中。
     CDXUTDialog::InitControl函数中遍历对话框的默认控件列表,并找到和要添加控件类型相同的默认控件,获得它的属性并将其设定到这个控件对象中。这个操作由CDXUTControl::SetElement函数来完成。
     在CDXUTControl的继承类(例如CDXUTButton)的Render函数中使用这些属性并且通过CDXUTDialog的DrawSprite函数画出图形,用CDXUTDialog的DrawText画出文字。
   要做一个漂亮的界面肯定少不了对控件的背景进行设置,例如用来显示文字的static控件,如果能用圆形表示那多好。但遗憾的是CDXUT框架中没有给 我们提供这些功能,需要我们自己去实现。它甚至没有给我们提供映射纹理的功能,而仅仅是提供了修改控件背景和前景字体颜色的功能。这些信息都放在空间的 m_Elements属性中,并由上述过程初始化。
  控件的显示在上面第六步有说明,就是调用DrawSprite和DrawText来实现。
4.对话框的初始化和显示
   DXUT框架中的对话框和GDI的有相似的地方,又有不同的地方。最大的不同就在于DXUT框架中的对话框是通过Draw*函数画出来的,因此每一桢都 需要进行渲染,并且这些对话框是在主窗口内;而GDI的对话框是弹出式的,因此不属于原窗口。对话框的初始化在CDXUTDialog::Init系列函 数中。这个函数除了注册给CDXUTDialogResourceManager之外还进行了纹理的设置。在默认情况下框架在 OnCreateDevice时调用CDXUTDialogResourceManager的CreateTexture函数从内存创建纹理,而如果在 CDXUTDialog::Init函数中指定了纹理路径,或者指定使用资源中的纹理时,程序就从指定地点获得纹理。对话框采用一个列表来维护它的纹理, 在通常情况下只有这个列表中只有一个纹理,除非用户调用相关函数手动添加。这个纹理是在显示控件的时候作为参数传给DrawSprite的,因此并不会用 来作为对话框背景。
  对话框的显示使用CDXUTDialog::OnRender函数来实现。
   这个函数很关键,研究这个函数可以知道一个对话框是如何渲染的。和gdi应用程序不一样,DXUT框架是通过Draw*函数将控件画出来的,因此您可以 按照喜欢的方式自己设定按钮等控件的样子。可以想象CDXUTDialog::OnRender的主要任务就是调用每个控件的OnRender函数。在调 用这些OnRender函数之前可以先将这个对话框的背景画好。因此这个函数的伪代码看起来就是这样子的:
  进行一些判断;
  为对话框背景设定渲染状态和纹理阶段状态;
  禁用顶点shader和像素shader,并且画出对话框背景;
  再次设定纹理阶段状态,为画控件做准备;
  调用各控件的Render函数画出各控件,对于获得焦点的控件做特殊处理;
  这个函数使用了状态块来记录设定过的状态,以备后用。
5.CD3DSettingDlg
   研究CD3DSettingDlg类可以学会如何使用上面的这些框架实现一个自己的对话框。我们可以在一个对话框上画另一个对话框,也可以选择在整个画 面中只画一个对话框。这些可以在主回调函数OnFrameRender中设定。例如在处理CD3DSettingDlg对话框的Active属性值被设定 为true的时候不处理其他对话框的渲染。
    if( g_SettingsDlg.IsActive() )
    {
        g_SettingsDlg.OnRender( fElapsedTime );
        return;
    }
  在主回调函数MsgProc处理消息的时候,如果CD3DSettingDlg已经处理过则不再传递给其他函数。
    if( g_SettingsDlg.IsActive() )
    {
        g_SettingsDlg.MsgProc( hWnd, uMsg, wParam, lParam );
        return 0;
    }
  CD3DSettingDlg::Init                            初始化
  CD3DSettingDlg::CreateControls         添加控件
  CD3DSettingDlg::OnCreateDevice       设定消息处理回调函数
  CD3DSettingDlg::StaticOnEvent          消息处理回调函数,调用OnEvent
  CD3DSettingDlg::OnEvent                   消息处理函数
  CD3DSettingDlg::OnRender                 渲染
  ……
   在游戏中经常要进行游戏状态的切换,用户通过输入来出发某个游戏状态切换时,我们就可以重新绘制场景,而设计一个类似于CD3DSettingDlg来 实现是一个不错的注意。由于CreateControls函数和OnEvent函数在不同的场景中代码不同,因此可以在基类中将其设为virtual。可 以根据需要将OnRender, OnXXX系列函数设为虚函数,以便子类实现相应功能。