程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 設計自己的3D圖像引擎(3):WuguiEngine之基礎循環的實現

設計自己的3D圖像引擎(3):WuguiEngine之基礎循環的實現

編輯:關於C++

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     :  [email protected]
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等類的組織.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved