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
- 注意:將引用的dx目錄放到所有引用的最前面,以免引用到工程默認配置的東西
- 要include dxsdk的include。路徑:dxsdk安裝目錄/Include
- 要依賴dxsdkd lib庫。路徑: dxsdk安裝目錄/Lib/x86
這裏使用的是x86的庫 - 手動包含用到的庫,避免找不到庫
《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);
- 使用WNDCLASSEX結構體
- cbSize時整個結構體的長度。爲了便於指向結構體中的地址
- style是指窗口風格,這裏使用的
CS_HREDRAW : 若移動或改變了窗口的寬度,則刷新整個窗口
CS_VREDRAW : 若移動或改變了窗口的高度,則刷新整個窗口
CS_DBLCLKS : 當用戶雙擊鼠標時向窗口程序發送一個雙擊的信息,同事,光標位於屬於該類的窗口中
CS_OWNDC : 爲該類中每一個窗口分配一個單值的設備描述表 - lpfnWndProc 是用來處理windows事件的
- 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.主動查詢事件
- 阻塞線程方式:使用GetMessage()函數來主動獲取事件。
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
注意,GetMessage()會阻塞調用這個函數的線程,直到能夠獲取到事件
TranslateMessage()函數將獲取到的事件信息作了處理,是一個虛擬加速鍵翻譯器
DispatchMessage()函數是分發消息,將調用WinProc來進一步處理事件
- 實時查詢方式,不阻塞線程:使用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