Window Initialization
与所有Microsoft Windows程序一样,DirectX游戏和图形应用程序也需要通过窗口来显示,必须通过调用Windows API来创建这些窗口。列表10.5列出了Game类的头文件,主要包含了用于window initialization的成员。在第11章,“Direct3D Initialization”Game类中将会增加用于initializing Direct3D的成员变量和成员函数。
列表10.5 The Game.h Header File
#pragma once
#include <windows.h>
#include <string>
#include "GameClock.h"
#include "GameTime.h"
using namespace Library;
namespace Library
{
class Game
{
public:
Game(HINSTANCE instance, const std::wstring& windowClass, const std::wstring& windowTitle, int showCommand);
~Game();
HINSTANCE Instance() const;
HWND WindowHandle() const;
const WNDCLASSEX& Window() const;
const std::wstring& WindowClass() const;
const std::wstring& WindowTitle() const;
int ScreenWidth() const;
int ScreenHeight() const;
virtual void Run();
virtual void Exit();
virtual void Initialize();
virtual void Update(const GameTime& gameTime);
virtual void Draw(const GameTime& gameTime);
protected:
virtual void InitializeWindow();
virtual void Shutdown();
static const UINT DefaultScreenWidth;
static const UINT DefaultScreenHeight;
HINSTANCE mInstance;
std::wstring mWindowClass;
std::wstring mWindowTitle;
int mShowCommand;
HWND mWindowHandle;
WNDCLASSEX mWindow;
UINT mScreenWidth;
UINT mScreenHeight;
GameClock mGameClock;
GameTime mGameTime;
private:
Game(const Game& rhs);
Game& operator=(const Game& rhs);
POINT CenterWindow(int windowWidth, int windowHeight);
static LRESULT WINAPI WndProc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM lParam);
};
}
正如你所看到的,初始化一个窗口需要大量的声明。首先,Game类的构造函数接收一个instance handle(实例句柄)和一个window class name的参数。第一个参数是程序实例的一个句柄,包含了windows procedure,用于从操作系统向窗口传递消息的回调。第二个参数(window class name)只是一个string。这两个参数结合一起,就可以唯一的确定操作系统中大量窗口的其中一个。参数showCommand是由程序的入口点WinMain()函数中的showCommand参数传递过来的,用于确定窗口的显示方式。参数windowTitle是一个string应用于窗口的标题栏上。所有这些参数存储到对应的成员变量中,并用于InitializeWindow()函数中。列表10.6列出了InitializeWindow()函数的实现代码。
列表10.6 The Game::InitializeWindow() Method
void Game::InitializeWindow()
{
ZeroMemory(&mWindow, sizeof(mWindow));
mWindow.cbSize = sizeof(WNDCLASSEX);
mWindow.style = CS_CLASSDC;
mWindow.lpfnWndProc = WndProc;
mWindow.hInstance = mInstance;
mWindow.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
mWindow.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
mWindow.hCursor = LoadCursor(nullptr, IDC_ARROW);
mWindow.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
mWindow.lpszClassName = mWindowClass.c_str();
RECT windowRectangle = { 0, 0, mScreenWidth, mScreenHeight };
AdjustWindowRect(&windowRectangle, WS_OVERLAPPEDWINDOW, FALSE);
RegisterClassEx(&mWindow);
POINT center = CenterWindow(mScreenWidth, mScreenHeight);
mWindowHandle = CreateWindow(mWindowClass.c_str(), mWindowTitle.c_str(), WS_OVERLAPPEDWINDOW, center.x, center.y, windowRectangle.right - windowRectangle.left, windowRectangle.bottom - windowRectangle.top, nullptr, nullptr, mInstance, nullptr);
ShowWindow(mWindowHandle, mShowCommand);
UpdateWindow(mWindowHandle);
}
在Game::Initialization()函数中,通过调用RgisterClassEx()和CreateWindow()函数创建一个窗口,随后调用函数ShowWindow()显示窗口。通过WNDCLASSX结构体类型定义一个窗口,该结构体中包含了大量的成员用于自定义窗口的显示结果。通过AdjustWindowRect()函数可以在窗口里面创建一个指定大小的client area(窗口客户区),大小由类成员mScreenWidth和mScreenHeigth指定。Client area在窗口内部,不包括其他的窗口元素,如标题栏,状态栏或滚动条。
调用CenterWindow()函数可以把窗口放在屏幕的中心位置,但是该函数并不是一个Windows API函数。列表10.7列出了CenterWindow()函数的实现代码,以及WndProc()回调函数代码。
列表10.7 Implementations for Game::WndProc() and Game::CenterWindow()
LRESULT WINAPI Game::WndProc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(windowHandle, message, wParam, lParam);
}
POINT Game::CenterWindow(int windowWidth, int windowHeight)
{
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
POINT center;
center.x = (screenWidth - windowWidth) / 2;
center.y = (screenHeight - windowHeight) / 2;
return center;
}
在CenterWindow()函数中,通过调用Windows API GetSystemMetrics()函数获取屏幕的宽度和高度,并返回窗口位于屏幕中心的起点。所有的Windows应用程序都需要一个windows procedure。WndProc()函数的实现中,只是简单地把所有消息都传递给default windows procedure(默认的窗口消息处理函数DefWindowProc()),除了WM_DESTROY消息(销毁窗口的消息),用于在窗口退出时关闭程序。收到该消息时,调用PostQuitMessage()函数,向Windows的消息队列中发送一个WM_QUIT消息。当程序接收到WM_QUIT消息时,就会退出Windows消息循环,并结束运行。
根据这些代码,可以把Game::Run()函数更新为列表10.8所示的代码。
列表10.8 The Game::Run() Method
void Game::Run()
{
InitializeWindow();
Initialize();
MSG message;
ZeroMemory(&message, sizeof(message));
mGameClock.Reset();
while(message.message != WM_QUIT)
{
if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
else
{
mGameClock.UpdateGameTime(mGameTime);
Update(mGameTime);
Draw(mGameTime);
}
}
Shutdown();
}
Game::Run()函数就是game loop,但是增加了window initialization以及Windows消息队列的处理。Windows API函数PeekMessage()从消息队列中获取消息,并通过调用TranslateMessage()和DispathMessage()函数派发消息。WndProc()回调函数用于处理接收到的消息。如果想到了解更多关于窗口的创建以及Windows消息循环相关的内容可以查看在线文档,但是目前的内容已经足够在屏幕上显示一个窗口了。编译并运行该应用程序,就可以看到如图10.10所示的图片。
图10.10 A blank window created through the demo framework.
这个窗口可能看起来有一点无趣,但是距离在屏幕上渲染一些内容已经很接近了。下一步是初始化Direct3D。
总结
本章完成了渲染引擎的基础。建立了Visual Studio projects,并配置编译选项,设计一个game loop并使用Windows API初始化了一个窗口。
下一章,将进一步扩展该程序并创建第一个Direct3D示例。
Exercises
a blank window. Visit the book’s companion website to find an associated demo application.