D3D之2D游戏编程(二)——游戏主窗口
2010-10-09 22:23
501 查看
在开始写游戏相关的代码之前,我们需要实现Win32程序的大体框架,其中最重要的是主窗口和消息循环。我不打算使用诸如WTL,MFC之类的GUI框架,在游戏编程中,这些框架除了增加复杂性之外并没有太大好处。最明智的做法是自己写一个简单但适合于游戏开发的视窗框架。我接下来将会实现它,这个框架用到代码非常的少,甚至没有类的存在,但它的确满足要求了。最重要的一点是,它非常容易理解。
- main/
App.cpp
- lib/
Commons.h
WindowFrame.h
WindowFrame.cpp
App.cpp 是程序的主执行流程。
Commons.h 用于存放公共的类型声明和辅助函数,以后会逐步丰富这个头文件的代码。
WindowFrame.h/cpp 实现下面的功能:
一个顶层窗口
一个消息循环
FPS计算
后面给出的Demo下载就可以看到全貌了。
BOOL InitWindow(LPCTSTR title, int
clientWidth, int clientHeight, WINDOWPROC
procWnd)
{
#define WND_CLASSNAME
_T("colin.windows.class")
_WndProc = procWnd;
//
注册窗口类
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW |
CS_VREDRAW;
wcex.lpfnWndProc =
(WNDPROC)_WindowProc;
wcex.cbClsExtra =
0;
wcex.cbWndExtra = 0;
wcex.hInstance = ThisModuleHandle();
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL,
IDC_ARROW);
wcex.hbrBackground =
(HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = WND_CLASSNAME;
wcex.hIconSm = NULL;
if
(!RegisterClassEx(&wcex))
{
_ASSERT(!"RegisterClassEx
failed!");
return
FALSE;
}
// 创建窗口
int width = clientWidth
+ 2 * GetSystemMetrics(SM_CXDLGFRAME);
int height = clientHeight
+ 2 * GetSystemMetrics(SM_CYDLGFRAME) + GetSystemMetrics(SM_CYCAPTION);
_Hwnd =
CreateWindow(
WND_CLASSNAME,
title,
WS_OVERLAPPED
| WS_CAPTION | WS_SYSMENU
| WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
width, height,
NULL, NULL,
ThisModuleHandle(), NULL);
if
(!_Hwnd)
{
_ASSERT(!"CreateWindow
failed!");
return
FALSE;
}
//
显示窗口
ShowWindow(_Hwnd,
SW_SHOWNORMAL);
UpdateWindow(_Hwnd);
return TRUE;
}
第1个参数指定窗口标题;
第2、2个参数指定窗口客户区的大小,这更适合于游戏的需要。
第4个参数是窗口的回调函数,这个函数的原型并非标准的窗口过程:
// 窗口回调函数原型
//
如果返回FALSE,将调用系统默认的窗口过程,ret被忽略。
//
如果返回TRUE,不调用系统默认的窗口过程,ret将返回给系统
typedef BOOL (*WINDOWPROC)(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam, LRESULT& ret);
可以看到InitWindow里面有几个变量:_Hwnd,
_WndProc,这是声明在WindowFrame.cpp里面的全局变量,因为游戏一般只需要一个顶层窗口,所以声明成全局变量是比较合适的:
HWND _Hwnd; // 主窗口
WINDOWPROC _WndProc; // 窗口回调
int _FPS;
// 每秒渲染帧数
外部如果要取主窗口句柄,可通过下面的函数取到:
// 窗口句柄
HWND
GetHwnd();
ThisModuleHandle是Commons.h的一个函数,用于取当前代码所在模块的句柄,有了这个函数,就不用在程序入口处保存模块句柄的全局变量了。
其他的细节,可以参考后面给出的Demo代码。
void MessageLoop(DWORD renderTime, RENDERPROC
procRender)
{
MSG msg = {0};
DWORD currTick;
DWORD lastTick = GetTick();
DWORD lastSecTick = lastTick;
int
renderCount = 0;
while(msg.message !=
WM_QUIT)
{
if(PeekMessage(&msg,
NULL, 0U, 0U, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
currTick = GetTick();
//
FPS
if (currTick - lastSecTick >= 1000)
{
_FPS = renderCount;
renderCount = 0;
lastSecTick = currTick;
}
// 渲染
if
((currTick - lastTick >= renderTime) &&
procRender)
{
++renderCount;
lastTick =
currTick;
procRender(currTick);
}
else
{
Sleep(1);
}
}
}
}
第一个参数指定每一帧渲染的时间间隔,单位是毫秒;
第二个参数是渲染回调函数:
//
渲染回调函数原型
// tick指定当前时钟计数
typedef void
(*RENDERPROC)(DWORD
tick);
MessageLoop内部其实作了三个事件,一个是正常的消息循环,一个是FPS的计算,另一个是渲染调用。
Sleep使得当没有消息,且未到渲染时间的时候,可以交出时间片给CPU;如果没有这一句,程序的CPU占用会比较高。
GetTick是Commons.h实现的一个函数,作用类似于GetTickCount,但由于GetTickCount的精度太低,所以实际上用的是timeGetTime。
BOOL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam,
LRESULT& ret)
{
return FALSE;
}
void DoRender(DWORD tick)
{
}
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int
nCmdShow)
{
if
(InitWindow(_T("测试窗口"), 800, 600, &WindowProc))
{
MessageLoop(20, &DoRender);
}
return 0;
}
以后的游戏都会复用这个简单的程序框架,WindowFrame不大会更改,Commons.h则会不断的丰富。
这是整个Demo的代码。
创建工程
用VS创建一个Win32工程,工程文件结构大致是这样的:- main/
App.cpp
- lib/
Commons.h
WindowFrame.h
WindowFrame.cpp
App.cpp 是程序的主执行流程。
Commons.h 用于存放公共的类型声明和辅助函数,以后会逐步丰富这个头文件的代码。
WindowFrame.h/cpp 实现下面的功能:
一个顶层窗口
一个消息循环
FPS计算
后面给出的Demo下载就可以看到全貌了。
创建窗口
创建窗口的代码包装成一个函数,只需传递几个参数就可以把窗口创建并显示出来,下面是原代码:BOOL InitWindow(LPCTSTR title, int
clientWidth, int clientHeight, WINDOWPROC
procWnd)
{
#define WND_CLASSNAME
_T("colin.windows.class")
_WndProc = procWnd;
//
注册窗口类
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW |
CS_VREDRAW;
wcex.lpfnWndProc =
(WNDPROC)_WindowProc;
wcex.cbClsExtra =
0;
wcex.cbWndExtra = 0;
wcex.hInstance = ThisModuleHandle();
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL,
IDC_ARROW);
wcex.hbrBackground =
(HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = WND_CLASSNAME;
wcex.hIconSm = NULL;
if
(!RegisterClassEx(&wcex))
{
_ASSERT(!"RegisterClassEx
failed!");
return
FALSE;
}
// 创建窗口
int width = clientWidth
+ 2 * GetSystemMetrics(SM_CXDLGFRAME);
int height = clientHeight
+ 2 * GetSystemMetrics(SM_CYDLGFRAME) + GetSystemMetrics(SM_CYCAPTION);
_Hwnd =
CreateWindow(
WND_CLASSNAME,
title,
WS_OVERLAPPED
| WS_CAPTION | WS_SYSMENU
| WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
width, height,
NULL, NULL,
ThisModuleHandle(), NULL);
if
(!_Hwnd)
{
_ASSERT(!"CreateWindow
failed!");
return
FALSE;
}
//
显示窗口
ShowWindow(_Hwnd,
SW_SHOWNORMAL);
UpdateWindow(_Hwnd);
return TRUE;
}
第1个参数指定窗口标题;
第2、2个参数指定窗口客户区的大小,这更适合于游戏的需要。
第4个参数是窗口的回调函数,这个函数的原型并非标准的窗口过程:
// 窗口回调函数原型
//
如果返回FALSE,将调用系统默认的窗口过程,ret被忽略。
//
如果返回TRUE,不调用系统默认的窗口过程,ret将返回给系统
typedef BOOL (*WINDOWPROC)(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam, LRESULT& ret);
可以看到InitWindow里面有几个变量:_Hwnd,
_WndProc,这是声明在WindowFrame.cpp里面的全局变量,因为游戏一般只需要一个顶层窗口,所以声明成全局变量是比较合适的:
HWND _Hwnd; // 主窗口
WINDOWPROC _WndProc; // 窗口回调
int _FPS;
// 每秒渲染帧数
外部如果要取主窗口句柄,可通过下面的函数取到:
// 窗口句柄
HWND
GetHwnd();
ThisModuleHandle是Commons.h的一个函数,用于取当前代码所在模块的句柄,有了这个函数,就不用在程序入口处保存模块句柄的全局变量了。
其他的细节,可以参考后面给出的Demo代码。
消息循环
消息循环同样用一个函数包装起来,为了符合游戏编程的需要,传入的参数是与渲染相关的,请看下面代码:void MessageLoop(DWORD renderTime, RENDERPROC
procRender)
{
MSG msg = {0};
DWORD currTick;
DWORD lastTick = GetTick();
DWORD lastSecTick = lastTick;
int
renderCount = 0;
while(msg.message !=
WM_QUIT)
{
if(PeekMessage(&msg,
NULL, 0U, 0U, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
currTick = GetTick();
//
FPS
if (currTick - lastSecTick >= 1000)
{
_FPS = renderCount;
renderCount = 0;
lastSecTick = currTick;
}
// 渲染
if
((currTick - lastTick >= renderTime) &&
procRender)
{
++renderCount;
lastTick =
currTick;
procRender(currTick);
}
else
{
Sleep(1);
}
}
}
}
第一个参数指定每一帧渲染的时间间隔,单位是毫秒;
第二个参数是渲染回调函数:
//
渲染回调函数原型
// tick指定当前时钟计数
typedef void
(*RENDERPROC)(DWORD
tick);
MessageLoop内部其实作了三个事件,一个是正常的消息循环,一个是FPS的计算,另一个是渲染调用。
Sleep使得当没有消息,且未到渲染时间的时候,可以交出时间片给CPU;如果没有这一句,程序的CPU占用会比较高。
GetTick是Commons.h实现的一个函数,作用类似于GetTickCount,但由于GetTickCount的精度太低,所以实际上用的是timeGetTime。
程序主流程
有了上面实现的几个函数之后,程序主流程就非常简单了,几行代码就搞定:BOOL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam,
LRESULT& ret)
{
return FALSE;
}
void DoRender(DWORD tick)
{
}
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int
nCmdShow)
{
if
(InitWindow(_T("测试窗口"), 800, 600, &WindowProc))
{
MessageLoop(20, &DoRender);
}
return 0;
}
以后的游戏都会复用这个简单的程序框架,WindowFrame不大会更改,Commons.h则会不断的丰富。
这是整个Demo的代码。
相关文章推荐
- D3D之2D游戏编程(一)——D3D图形管道
- D3D之2D游戏编程(二)——使用DxSDK
- C语言小程序:Windows窗口游戏编程模板
- 2D游戏编程4—Windows事件
- android2D游戏编程总结
- bada 2D游戏编程之八——逐帧动画
- bada 2D游戏编程之四——设计游戏循环
- 游戏环境下如何实现真正D3D的窗口
- WPF游戏编程--2D人物动画
- bada 2D游戏编程——开篇说明
- bada 2D游戏编程之十——关键帧动画原理
- bada 2D游戏编程之四――设计游戏循环
- bada 2D游戏编程之二――图像绘制(1)
- 【DirectX 2D游戏编程基础】DirectX精灵的创建
- 【图形学与游戏编程】开发笔记-入门篇3:d3d,opengl以及GPU
- 2D 游戏编程技巧
- bada 2D游戏编程之六――一个基于线程的游戏循环
- 【DirectX 2D游戏编程基础】DirectX环境的搭建
- 游戏环境下如何实现真正D3D的窗口