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

来源:互联网 发布:matlab 矩阵元素运算 编辑:程序博客网 时间:2024/05/29 15:21

 

在开始写游戏相关的代码之前,我们需要实现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的代码。

 
 


 

原创粉丝点击