<@乌龟:>[原创]设计自己的3D图像引擎(3): WuguiEngine 之基础循环的实现

 

1. 实现一个可重用的Windows Class(WEWindow)

 

    下面的内容可能需要你知道一点简单的Windows程序编写的知识, 如果发现看不太懂, 也不用找很多的资料, 只要看看DirectX SDK中附带的Direct3D Turtorial就行了.

    窗体的建立主要是有如下的难点:

    1) 消息循环中的On Idle的处理函数不太好放.

    2) WinMain函数入口处的HInstance需要保存

    3) 窗体建立之后的HWND也是需要保存的

    为了方便起见,我使用了单件模式作为设计,如果大家有更好的想法,也欢迎讨论

    WEWindow类的声明看起来有点像这个样子:

 

   1: /*  IRenderable.h : 处理初始化窗体的类,使用单件模式
   2:     Programer     : Tan Wangda(LeftNotEasy)
   3:     Email          :    Wheeleast@gmail.com
   4:     Created Time  : 2009 - 8 - 10                    */
   5:  
   6: #ifndef _WEWINDOW_H
   7: #define _WEWINDOW_H
   8:  
   9: # include "WuguiEngine.h"
  10:  
  11: namespace WuguiEngine
  12: {
  13:     //这是一个Singleton
  14:     class WEWindow
  15:     {
  16:     public:
  17:         //得到窗体类的实例
  18:         static WEWindow* GetInstance();
  19:         
  20:         //设置HInstance
  21:         void SetHInstance(HINSTANCE hInst);
  22:         //获取HInstance
  23:         HINSTANCE GetHInstance();
  24:  
  25:         //设置窗体
  26:         void SetWEWindow(LPCWSTR szTitle,  //窗体标题
  27:             int iWidth,                       //宽度
  28:             int iHeight,                   //高度
  29:             DWORD iStyle);                 //样式
  30:  
  31:         void CreateMainWindow();
  32:  
  33:         int GetWidth();
  34:         int GetHeight();
  35:         
  36:         //获取窗体句柄
  37:         HWND GetHWnd();
  38:     protected:
  39:         HINSTANCE hInst;
  40:         HWND hWnd;
  41:  
  42:         //窗体标题
  43:         LPCWSTR szTitle;
  44:  
  45:         //宽度
  46:         int iWidth;
  47:  
  48:         //高度
  49:         int iHeight;
  50:  
  51:         //样式
  52:         DWORD iStyle;
  53:  
  54:         //创建窗体
  55:         void CreateWEWindow(LPCWSTR szTitle,  //窗体标题
  56:             int iWidth,                          //宽度
  57:             int iHeight,                      //高度
  58:             DWORD iStyle);                    //样式
  59:         
  60:         //进入消息循环
  61:         void EnterMessageLoop();          
  62:     private:
  63:         static WEWindow* weInstance;
  64:         WEWindow();
  65:     };
  66: }
  67:  
  68: #endif 

 

    代码的命名基本上也能够看出意义, 另外再给一段使用WEWindow的函数,位于Program.cpp里面

   1: INT WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int)
   2: {
   3:     //第一步,设置窗体
   4:     WEWindow* weWindow = WEWindow::GetInstance();
   5:     weWindow->SetHInstance(hInstance);
   6:     weWindow->SetWEWindow(L"Hello World", 640, 480, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
   7:     weWindow->CreateMainWindow();

 

 

    首先代码获取处WEWindow类的实例,然后设置参数,最后调用CreateMainWindow就完成了操作了

 

2. 完成一个基础的游戏类(BaseGame)

 

    还是先给出头文件的声明:

 

   1: class BaseGame : virtual public IDisposed, virtual public IRenderable, virtual public IUpdatable
   2: {
   3: public:
   4:     BaseGame(string name);                                             //Constructor
   5:     GraphicsDevice* GetGraphicsDevice();                               //获取GraphicsDevice
   6:     void Tick();                                                       //一帧完整的游戏
   7:     virtual void Run() = 0;                                            //开始一场游戏,里面建立消息循环
   8:     virtual void Dispose();
   9:     virtual void Update(DWORD dt);
  10:     virtual void Render(DWORD dt);
  11:     virtual void Initialize();
  12:     void RegisterCamera(BaseCamera* camera);
  13:     BaseCamera* GetCamera();
  14: protected:
  15:     //方法:
  16:     void SetTimeUsed();                                                //设置花费的时间
  17:     void ShowFPS();
  18:     void UpdateCamera(DWORD dt);
  19:  
  20:     //变量:
  21:     GraphicsDevice* pDevice;                                           //指向GraphicsDevice的指针
  22:     bool isInitialized;                                                //是否初始化
  23:     BaseCamera* pCamera;
  24: };

 

 

    其中, IDisposed,IRenderable, IUpdatable等I开头的类是接口类,里面具有纯虚函数,重写就可以实现具体的代码了.

    GraphicsDevice类是我准备将Direct3D中的IDirect3DDevice9类封装得到,使得使用起来更方便,而且增加一些实用的功能,不过目前的功能和本来的Device功能差不多,这里就不多说了.

    另外值得注意的是Tick函数,其描述的是一帧游戏的过程:

   1: void BaseGame::Tick()
   2: {
   3:     SetTimeUsed();                                           //设置花费了的时间
   4:     Update(TimeUsed::LastTickTimeUsed);                      //调用更新函数
   5:     Render(TimeUsed::LastTickTimeUsed);                      //调用渲染函数
   6: }

 

    Update和Render都是虚函数, 当我们将一个类继承自BaseGame的时候, 也就会调用相应的Update和Render类.

    代码中的TimeUsed是静态类,里面存储了时间相关的参数,比如一帧时间的长度, 从启动游戏到现在的时间长度等等.

    其他的函数都有注释, 如果还有不太理解的地方,也可以从GoogleCode上面把源代码下下来,很快就会明白了

 

3. 下面举一个基本的例子,来说明这些程序是怎样一步步进行的

 

    主要的类: TestGame(继承自BaseGame), WEWindow, BaseGame.

    1) 在WinMain()中,首先完成建立窗体:

   1: INT WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int)
   2: {
   3:     //第一步,设置窗体
   4:     WEWindow* weWindow = WEWindow::GetInstance();
   5:     weWindow->SetHInstance(hInstance);
   6:     weWindow->SetWEWindow(L"Hello World", 640, 480, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
   7:     weWindow->CreateMainWindow();

 

 

    2)在WinMain()中,创建一个TestGame类的实例,并且调用TestGame->Run(继承自BaseGame的纯虚函数)

   1: //第二部,初始化游戏
   2: TestGame* game = new TestGame("Matrix");
   3:  
   4: //第三步,开始游戏
   5: game->Run();

 

 

    3)在TestGame->Run()中, 进行初始化与建立消息循环的操作

   1: void TestGame::Run()
   2: {
   3:     //进行初始化
   4:     this->Initialize();
   5:  
   6:     //进入消息循环
   7:     this->EnterMessageLoop();
   8: }

 

 

    4)在TestGame::Initialize()中,完成一些初始化的工作, 为进入游戏的主循环做好准备

   1: void TestGame::Initialize()
   2: {
   3:     this->BaseGame::Initialize();
   4:     if (!isInitialized)
   5:     {
   6:         InitGeometry();
   7:         pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
   8:         pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
   9:         isInitialized = true;
  10:     }
  11: }

 

 

    这里的内容,每个不同的游戏逻辑都是不一样的, 只有调用this->BaseGame::initialize()是各个逻辑共有的

    5)在TestGame::EnterMessageLoop()中,完成消息循环的建立,开始游戏的主循环

   1: void TestGame::EnterMessageLoop()
   2: {
   3:     MSG kMessage;
   4:     HWND hWnd = WEWindow::GetInstance()->GetHWnd();
   5:  
   6:     while (1)
   7:     {
   8:         if (PeekMessage(&kMessage, hWnd, 0, 0, PM_REMOVE))
   9:         {
  10:             if (WM_QUIT == kMessage.message)
  11:             {
  12:                 Dispose();
  13:                 return;
  14:             }
  15:             else
  16:             {
  17:                 TranslateMessage(&kMessage);
  18:                 DispatchMessage(&kMessage);
  19:             }
  20:         }
  21:         else
  22:         {
  23:             Tick();
  24:         }
  25:     }
  26: }

 

 

    可以看到,第4行的内容就是单件模式方式调用WEWindow获取HWnd,这样就成功了建立了消息循环了

    6)在消息循环中. 每次都按照先Update,后Render的方式, 和很多的引擎就比较类似了.在这个地方,我主要参考了一下XNA的做法

    7)完成了!现在只需要在Initialize和Update,Render中添加相应的代码就可以完成需要的逻辑了.不算太复杂吧~!. 如果需要对这块的内容更清晰的了解,希望你可以将我的代码下载下来看看.地址在第一篇文章中就有

 

4. 下集预告:

 

    下一篇文章的主要内容是渲染中,包括Model, Effect, Texture, Transformation, Camera等类的组织.

 

posted on 2009-08-23 17:47  乌龟_毛驴  阅读(1650)  评论(4编辑  收藏  举报

导航