2.創建適合遊戲的窗口和消息循環
本章前言:
創建遊戲窗口和處理消息循環是很重要的事情,我嘗試過幾種不同的窗口處理方式,這次打算使用WS_POPUP樣式的窗口(無邊框)。上一次的框架代碼把創建窗口和消息循環放入了一個新的線程,這樣在有不斷的消息的時候(例如拖動窗口)也不會讓主界面停止重繪。我曾經在一些書上看到有人這麼推薦,但實際效果並不理想,因爲消息處理函數也是在新線程被調用,這樣一些消息的處理(例如WM_CHAR消息)需要和主線程同步,反而導致很繁瑣。另外,可拖動改變大小的邊框,在窗口大小發生變化的時候需要讓DirectxX更改主顯示錶面大小以及處理設備丟失(DirectX 11沒有那麼麻煩,但還是要處理)。全屏模式下支持的分辨率和顯示器有關,即便是窗口模式,我認爲也應該符合顯示器支持的全屏模式分辨率大小。
目標要點總結:
1. 無邊框窗口
2. 窗口大小限制爲顯示器全屏下支持的分辨率
3. 消息處理在主線程
最終效果:
用戶建立Win32項目之後,只需要如下使用便可創建默認的窗口:
//////////////////////main.cpp////////////////////////
#include <DND.h>
using namespaceDND;
System* sys;
DNDMain()
{
sys = System::Instance();
sys->Enter_GameLoop();
}
//////////////////////////////////////////////////////
默認窗口的大小使用顯示器所支持的最小分辨率。其值爲通過DXGI枚舉顯示器支持的分辨率,取第一個。
前題簡要:
System爲系統單例類,主要引擎功能集中在此類。繼承於Singleton模板類,可以快速的實現單例的效果。
具體實現:
在主函數中,獲取System類實例後,調用Enter_GameLoop函數進入遊戲循環。此函數會設置幀函數(每幀調用的函數,分爲幀前和幀後),然後創建窗口,進入遊戲循環。
//////////////////////////////////////////////////////
void System_imp::Enter_GameLoop(void(*fixed_update)()/*= 0*/, void(*late_update)()/*= 0*/)
{
//設置幀函數
m_fixed_update = fixed_update;
m_late_update = late_update;
//創建窗口
_create_window();
//遊戲循環
MSG msg;
ZeroMemory(&msg,sizeof(MSG));
while (true)
{
//首先處理消息
if (PeekMessage(&msg,NULL, 0, 0, PM_REMOVE))
{
if (msg.message ==WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//遊戲內容
if (m_fixed_update)m_fixed_update();
//引擎需要執行的代碼
if (m_late_update)m_late_update();
}
}
//////////////////////////////////////////////////////
在while(true)中的遊戲循環中,首先處理窗口消息,然後執行幀函數。這個true也可以改爲一個標誌值代表是否結束程序。另外引擎的內部代碼可以穿插在裏面的各個部分。例如update和render放置在兩個幀函數之間,按鍵檢測放置在幀前函數之前。幀率計算和控制FPS都能通過在這裏添加語句來實現。
其中_create_window函數創建了窗口,來看看具體的代碼:
//////////////////////////////////////////////////////
//窗口類
WNDCLASS wc;
wc.style =CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc =WindowProc; //消息處理函數
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance =m_instance;//實例句柄
wc.hIcon = (HICON)::LoadImage(NULL,L"icon.ico", IMAGE_ICON, 0, 0,LR_DEFAULTSIZE | LR_LOADFROMFILE);
wc.hCursor =LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景白色
wc.lpszMenuName = 0;
wc.lpszClassName =Value::WINDOW_CLASS_NAME;//窗口類名
if (!RegisterClass(&wc))
{
assert(0&& L"註冊窗口類失敗!\n");
return;
}
//////////////////////////////////////////////////////
首先是註冊一個窗口類,其中WindowProc是窗口消息處理函數。m_instance是實例句柄,通過調用GetMoudleHandle(0)賦值。LoadImage函數加載一個圖像作爲圖標,由於這個方法是在程序運行的時候才執行,所以只對狀態欄和窗口左上角的圖標有效。
接下來是創建窗口
//////////////////////////////////////////////////////
//創建窗口
m_hwnd = CreateWindow(
Value::WINDOW_CLASS_NAME,
Value::WINDOW_DEFAULT_TITLE,
WS_MINIMIZEBOX | WS_POPUP,
-100,
-100,
100,
100,
0 /*parenthwnd*/, 0 /* menu */, m_instance, 0 /*extra*/);
if (!m_hwnd)
{
assert(0&& L"創建窗口失敗!\n");
return;
}
//////////////////////////////////////////////////////
其中WS_MINIMIZEBOX | WS_POPUP樣式讓窗口無邊框並且能夠被最小化。並且窗口大小爲100,100,顯示在屏幕區域之外。接着再應用窗口的大小和位置。
//////////////////////////////////////////////////////
//設置窗口
//設置大小
Set_Window_Size(m_info_system.window_size);
//設置窗口位置居中
Set_Window_Center();
ShowWindow(m_hwnd,SW_SHOW);
UpdateWindow(m_hwnd);
SetFocus(m_hwnd);
//////////////////////////////////////////////////////
上面主要調用Windows API MoveWindow來實現移動窗口和更改窗口大小。設置窗口居中的函數代碼如下:
//////////////////////////////////////////////////////
void System_imp::Set_Window_Center()
{
Size size =Get_Desktop_Size();//獲取桌面大小
m_info_system.window_pos= (size - m_info_system.window_size)/ 2;
//位置 等於 桌面大小減去窗口大小的一半
_apply_window();//這裏面調用MoveWindow
}
//////////////////////////////////////////////////////
獲取桌面大小:
//////////////////////////////////////////////////////
Size System_imp::Get_Desktop_Size()
{
return Size((GetSystemMetrics(SM_CXSCREEN)), (GetSystemMetrics(SM_CYSCREEN)));
}
//////////////////////////////////////////////////////
最後是窗口消息處理函數WindowProc,由於定義在System類內部,所以加上static聲明爲靜態函數。
//////////////////////////////////////////////////////
static LRESULTCALLBACKWindowProc(HWNDhwnd,UINTmsg,WPARAMwparam,LPARAMlparam);
//////////////////////////////////////////////////////
DXGI相關
在配置好DirectX 11環境後,,其中一些對象需要存儲,在頭文件裏是這樣:
//////////////////////dxgi////////////////////////
IDXGIFactory* m_dxgi;
//主顯卡
IDXGIAdapter* m_adapter;
//主顯示器
IDXGIOutput* m_output;
// 支持的全屏顯示模式的數組
DXGI_MODE_DESC* m_display_modes;
UINT m_display_mode_length;
//篩選後的信息
Array<Size>m_array_windowsize;
//////////////////////////////////////////////////////
首先需要創建DXGI對象:
//////////////////////////////////////////////////////
if (FAILED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&m_dxgi)))
{
assert(0&& L"DXGIFactory 創建失敗!");
return;
}
//////////////////////////////////////////////////////
接着取得第一個顯卡,也就是默認顯卡:
//////////////////////////////////////////////////////
if (m_dxgi->EnumAdapters(0,&m_adapter) ==DXGI_ERROR_NOT_FOUND)
{
assert(0&& L"未找到顯卡!");
return;
}
//////////////////////////////////////////////////////
然後通過顯卡取得默認的顯示器(output):
//////////////////////////////////////////////////////
if (m_adapter->EnumOutputs(0,&m_output) ==DXGI_ERROR_NOT_FOUND)
{
assert(0&& L"未找到顯示器!");
return;
}
//////////////////////////////////////////////////////
下面就可以枚舉全屏模式下所有支持的分辨率了:
////////////////////獲取顯示器支持的全屏顯示模式//////////////////////////
m_display_modes = 0;
m_display_mode_length = 0;
DXGI_FORMAT format = DXGI_FORMAT_B8G8R8A8_UNORM;//32位真彩色
m_output->GetDisplayModeList(format, 0,&m_display_mode_length,NULL);
m_display_modes = newDXGI_MODE_DESC[m_display_mode_length];
m_output->GetDisplayModeList(format,0, &m_display_mode_length,m_display_modes);
//////////////////////////////////////////////////////
其中返回的DXGI_MODE_DESC類型數組就包含分辨率大小信息。還包括刷新率、掃描方式、縮放方式等等數據,但我們只需要篩選出支持60HZ刷新率的分辨率大小,其他的不用關心。
作者:略遊
日期:17-05-22
QQ:1339484752