[引擎筆記]之一:windows/3D遊戲運行框架

1.工程配置

  • 配置爲win32應用程序
    注意,不是console控制檯程序。console控制檯程序基於dos,沒有窗口。win32應用程序是標準的windows事件響應的程序。

  • 安裝DX9.0和DX9.0SDK
    DX9.0Sdk是dx的開發工具包,裏面提供了調用dx的api。
    使用Dx9.0的原因是,作者使用7.0或者8.0的接口,9.0基本上是兼容的。
    不要用Dx10,Dx10改動比較大。

  • 引用Dx SDK

    1. 注意:將引用的dx目錄放到所有引用的最前面,以免引用到工程默認配置的東西
    2. 要include dxsdk的include。路徑:dxsdk安裝目錄/Include
    3. 要依賴dxsdkd lib庫。路徑: dxsdk安裝目錄/Lib/x86
      這裏使用的是x86的庫
    4. 手動包含用到的庫,避免找不到庫
      《windows編程大師技巧》中:ddraw.lib dinput.lib dinput8.lib dsound.lib d3dim.lib dxguid.lib winmm.lib
      《3D遊戲編程大師技巧》中: ddraw.lib dinput.lib dinput8.lib dsound.lib winmm.lib

2.創建Windows窗口

0.基礎準備知識
  • console應用的入口是main()

    void main()

    或者是:

    int main(int argc char *argv[])
    
  • win32應用的入口是WinMain()

    int WINAPI WinMain(HINSTANCE hinstance,
    HINSTANCE hprevinstance,
    LPSTR lpcmdline,
    int ncmdshow)
  • win32應用需要包含頭文件

    
    #define WIN32_LEAN_AND_MEAN             
    
    
    #include <windows.h>
    
    
    #include <windowsx.h>
    
    
    • #defind WIN32_LEAN_AND_MEAN:開發win32應用可以使用MFC和win sdk兩種。MFC是比較重量級的api,這裏告訴編譯器,只是用輕量級的win sdk
    • #include
1.創建WNDCLASSEX結構體

首先需要創建一個窗口的模板,如下所示

    WNDCLASSEX  wClass;                         // 使用WNDCLASSEX結構體
    wClass.cbSize = sizeof(WNDCLASSEX);         
    wClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC;
    wClass.lpfnWndProc = WinProc;
    wClass.cbClsExtra = 0;
    wClass.cbWndExtra = 0;
    wClass.hInstance = hInstance;
    wClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);                    
    wClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wClass.lpszMenuName = NULL;
    wClass.lpszClassName = "CLASSNAME_1";
    wClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
  1. 使用WNDCLASSEX結構體
  2. cbSize時整個結構體的長度。爲了便於指向結構體中的地址
  3. style是指窗口風格,這裏使用的
    CS_HREDRAW : 若移動或改變了窗口的寬度,則刷新整個窗口
    CS_VREDRAW : 若移動或改變了窗口的高度,則刷新整個窗口
    CS_DBLCLKS : 當用戶雙擊鼠標時向窗口程序發送一個雙擊的信息,同事,光標位於屬於該類的窗口中
    CS_OWNDC : 爲該類中每一個窗口分配一個單值的設備描述表
  4. lpfnWndProc 是用來處理windows事件的
  5. hInstance代表應用本身,用於追蹤應用本身的資源
2.註冊WNDCLASSEX

創建完成WNDCLASSEX之後,要將這個模板註冊到系統中:

    RegisterClassEx(&wClass);

這裏也使用的是RegisterClassEx(注意有Ex)。

3.創建Windows窗口

將模板註冊之後,就可以創建這個模板的窗口了。
使用CreateWindowEx方法。該方法返回一個HWND窗口的句柄,用於標記創建的窗口。

    HWND hWnd;
    if (!(hWnd = CreateWindowEx(
        NULL,
        WNDCLASS_NAME,                          
        WND_NAME, WS_OVERLAPPEDWINDOW | WS_VISIBLE,                 // 窗口的樣式
        0, 0,                                                       // 窗口左上角的位置
        400, 800,                                                   // 窗口的寬和高的像素
        NULL,
        NULL,
        hInstance,                                                  // 應用的實例
        NULL)))
    {
    // 如果創建失敗,彈出一個messagebox
        MessageBox(NULL, "create window error!!", "Create Window msg", MB_OK);
        return 0;
    }

    // 如果創建成功,則刷新顯示當前窗口
    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);

3.窗口事件和遊戲主循環

1.處理事件

Windows是一個事件驅動的操作系統,意思是說,Widnows系統會根據用戶的操作產生事件,不用開發者自己去寫什麼操作產生了什麼事件。
操作系統發現有事件了之後,會通知監聽事件的應用。開發者只要監聽系統給發事件,並且處理事件消息就可以了。
舉個例子,如果我們點擊了某個窗口的關閉按鈕,系統就會發送一個關閉了某某窗口的事件。
WNDCLASSEX中的lpfnWndProc就是用來處理系統發送的事件的。

處理事件的回調函數是:

LRESULT CALLBACK WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    switch (msg)
    {
    case WM_CREATE:
    {
        return 0;
    }
    break;
    case WM_PAINT:
    {
        hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
        return 0;

    }
    break;
    case WM_DESTROY:
    {
        PostQuitMessage(0);
        return 0;
    }
    break;
    default:
        break;
    }
    return (DefWindowProc(hWnd, msg, wParam, lParam));
}

如果是我們監聽的需要處理的事件,那麼我們在這個函數中,就做相應的處理,並且return 0,消息不在向後傳遞;
如果不是我們感興趣的事件,那麼,就交給系統默認的事件處理函數去處理。

這裏我們只對窗口的創建、窗口的重繪、窗口的銷燬事件感興趣:
WM_CREATE: 窗口創建時,我們應該初始化自己的資源
WM_PAINT: 窗口需要繪製的時候,我們就重繪一下。
windows的窗口繪製是隻繪製需要重畫地方,在PAINTSTRUCT的RECT參數代表重繪區域
HDC返回重繪的是哪個設備
WM_DESTROY: 當窗口銷燬的時候,我們調用PostQuitMessage來告訴系統,應用要停止了。
其他的消息我們不處理,而是交給系統默認處理DefWindowProc(hWnd, msg, wParam, lParam).

2.主動查詢事件
  1. 阻塞線程方式:使用GetMessage()函數來主動獲取事件。
    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);     
        DispatchMessage(&msg);
    }

注意,GetMessage()會阻塞調用這個函數的線程,直到能夠獲取到事件
TranslateMessage()函數將獲取到的事件信息作了處理,是一個虛擬加速鍵翻譯器
DispatchMessage()函數是分發消息,將調用WinProc來進一步處理事件

  1. 實時查詢方式,不阻塞線程:使用PeekMessage()函數來查詢事件
    MSG msg;
    while (true)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        Game_Main();        // 遊戲主循環
    }

在主線程中一直查詢事件,如果有事件,我們就處理事件。
並且,當我們收到WM_QUIT消息的時候,就退出while循環,結束主窗口.

  • 注意,GetMessage()和PeekMessage()的第二個參數HWND參數都是NULL,這是因爲:
    函數接收屬於調用線程的窗口的消息,線程消息由函數PostThreadMessage寄送給調用線程,
    不接收屬於其他線程或其他線程的窗口的消息,即使hWnd爲NULL。由PostThreadMessage寄送的線程消息,其消息hWnd值爲NULL。
    我們當前的應用程序只有一個主線程。(每個引用程序都默認會起一個線程,這個線程就是主線程)
3. 遊戲主循環流程總結

在創建窗口後,在while(true)中實時查詢事件和調用遊戲主循環Game_Main();

所有整體的流程是:

WinMain()
{
    創建WNDCLASSEX模板
    註冊WNDCLASSEX模板
    創建窗口
    while(ture)
    {
        查詢事件:如果有事件,就處理事件
        Game_Main(): 遊戲主循環
    }
}

WinProc():  處理事件的回調函數
Game_Main(): 遊戲主循環函數

4.完整程序

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <math.h>

#define WNDCLASS_NAME "WNDCLASS_1"
#define WND_NAME "延澈左 GameEngine"

LRESULT CALLBACK WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    switch (msg)
    {
    case WM_CREATE:
    {
        return 0;
    }
    break;
    case WM_PAINT:
    {
        hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
        return 0;

    }
    break;
    case WM_DESTROY:
    {
        PostQuitMessage(0);
        return 0;
    }
    break;
    default:
        break;
    }
    return (DefWindowProc(hWnd, msg, wParam, lParam));
}

void Game_Main()
{

}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    WNDCLASSEX  wClass;
    wClass.cbSize = sizeof(WNDCLASSEX);
    wClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC;
    wClass.lpfnWndProc = WinProc;
    wClass.cbClsExtra = 0;
    wClass.cbWndExtra = 0;
    wClass.hInstance = hInstance;
    wClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wClass.lpszMenuName = NULL;
    wClass.lpszClassName = WNDCLASS_NAME;
    wClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wClass);

    HWND hWnd;
    if (!(hWnd = CreateWindowEx(
        NULL,
        WNDCLASS_NAME,
        WND_NAME, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        0, 0,
        400, 800,
        NULL,
        NULL,
        hInstance,
        NULL)))
    {
        MessageBox(NULL, "create window error!!", "Create Window msg", MB_OK);
        return 0;
    }

    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);

    MSG msg;

//  while (GetMessage(&msg, NULL, 0, 0))
//  {
//      TranslateMessage(&msg);
//      DispatchMessage(&msg);
//  }

    while (true)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        Game_Main();
    }

    return msg.wParam;
}



轉載請註明作者及出處,本文作者爲延澈左,本文標題爲[引擎筆記]之一:windows遊戲運行框架
本文鏈接爲https://yanchezuo.com/en_4.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章