您的位置:首页 > 编程语言

D3D之2D游戏编程(二)——游戏主窗口

2010-10-09 22:23 501 查看
在开始写游戏相关的代码之前,我们需要实现Win32程序的大体框架,其中最重要的是主窗口和消息循环。我不打算使用诸如WTL,MFC之类的GUI框架,在游戏编程中,这些框架除了增加复杂性之外并没有太大好处。最明智的做法是自己写一个简单但适合于游戏开发的视窗框架。我接下来将会实现它,这个框架用到代码非常的少,甚至没有类的存在,但它的确满足要求了。最重要的一点是,它非常容易理解。

创建工程

用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的代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: