原英文版地址:http://www.rastertek.com/dx11tut02.html
在开始使用Directx11编码之前,我建议构建一个简单的代码框架。这个框架将处理基本的Windows功能,并提供一种以有组织的、可读的方式扩展代码的简单方法。由于这些教程的目的只是尝试Directx11的不同功能,因此我们将有目的地使框架尽可能简单。
框架
框架工程将从四个项目开始。它将有一个winmain函数来处理应用程序的入口点。它还具有一个系统类,该类封装了将从winmain函数中调用的整个应用程序。在系统类中,我们将有一个用于处理用户输入的输入类和一个用于处理DirectX图形代码的图形类。以下是框架设置的示意图:
现在我们了解了框架的设置方式,让我们从main.cpp文件中的winmain函数开始。
程序进入点
// Filename: main.cpp
#include "systemclass.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
SystemClass* System;
bool result;
// Create the system object.
System = new SystemClass;
if(!System)
{
return 0;
}
// Initialize and run the system object.
result = System->Initialize();
if(result)
{
System->Run();
}
// Shutdown and release the system object.
System->Shutdown();
delete System;
System = 0;
return 0;
}
如您所见,我们保持了winmain函数相当简单。我们创建系统类,然后初始化它。如果它初始化时没有问题,那么我们调用系统类运行函数。run函数将运行自己的循环,并执行所有应用程序代码,直到完成为止。运行函数完成后,我们关闭系统对象,并对系统对象进行清理。所以我们保持它非常简单,并将整个应用程序封装在系统类中。现在让我们看看系统类头文件。
Systemclass.h
// Filename: systemclass.h
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_
在这里我们定义WIN32_LEAN_AND_MEAN。我们这样做是为了加快构建过程,它通过排除一些使用较少的API来减小win32头文件的大小。
///
// PRE-PROCESSING DIRECTIVES //
///
#define WIN32_LEAN_AND_MEAN
其中包括windows.h,这样我们就可以调用函数来创建/销毁窗口,并能够使用其他有用的win32函数。
//
// INCLUDES //
//
#include <windows.h>
此时,我们已经将其他两个类的头包含在框架中,这样我们就可以在系统类中使用它们。
///
// MY CLASS INCLUDES //
///
#include "inputclass.h"
#include "graphicsclass.h"
类的定义相当简单。我们看到了在这里定义的winmain中调用的initialize、shutdown和run函数。还有一些私有函数将在这些函数内部调用。我们在类中还放置了一个messagehandler函数来处理Windows系统消息,这些消息将在应用程序运行时发送到应用程序。最后,我们还有一些私有变量m_input和m_graphics,它们将作为两个对象的指针,处理图形和输入。
// Class name: SystemClass
class SystemClass
{
public:
SystemClass();
SystemClass(const SystemClass&);
~SystemClass();
bool Initialize();
void Shutdown();
void Run();
LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
private:
bool Frame();
void InitializeWindows(int&, int&);
void ShutdownWindows();
private:
LPCWSTR m_applicationName;
HINSTANCE m_hinstance;
HWND m_hwnd;
InputClass* m_Input;
GraphicsClass* m_Graphics;
};
/
// FUNCTION PROTOTYPES //
/
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
/
// GLOBALS //
/
static SystemClass* ApplicationHandle = 0;
#endif
wndproc函数和applicationhandle指针也包含在这个类文件中,因此我们可以将Windows系统消息重新定向到系统类内的messagehandler函数中。
现在让我们来看看系统类源文件:
Systemclass.cpp
// Filename: systemclass.cpp
#include "systemclass.h"
在类构造函数中,我将对象指针初始化为空。这很重要,因为如果这些对象的初始化失败,则进一步打开的关机功能将尝试清理这些对象。如果对象不是空的,那么它假定它们是有效创建的对象,并且需要清除它们。在应用程序中将所有指针和变量初始化为空也是一个好的实践。如果不这样做,一些发布版本将失败。
SystemClass::SystemClass()
{
m_Input = 0;
m_Graphics = 0;
}
在这里,我创建一个空的复制构造函数和空的类析构函数。在这个类中,我不需要它们,但是如果没有定义,一些编译器将为您生成它们,在这种情况下,我希望它们是空的。
您还会注意到我没有在类析构函数中清理任何对象。相反,我会在关闭功能中清理所有对象,您将看到更详细的内容。原因是我不相信有人叫我。某些Windows函数(如exitthread())因不调用类析构函数而导致内存泄漏而闻名。当然,现在可以调用这些函数的更安全的版本,但在Windows上编程时,我只是小心而已。
SystemClass::SystemClass(const SystemClass& other)
{
}
SystemClass::~SystemClass()
{
}
以下初始化函数完成应用程序的所有设置。它首先调用InitializeWindows,这将创建应用程序要使用的窗口。它还创建并初始化输入对象和图形对象,应用程序将使用这些对象处理用户输入并向屏幕呈现图形。
bool SystemClass::Initialize()
{
int screenWidth, screenHeight;
bool result;
// Initialize the width and height of the screen to zero before sending the variables into the function.
screenWidth = 0;
screenHeight = 0;
// Initialize the windows api.
InitializeWindows(screenWidth, screenHeight);
// Create the input object. This object will be used to handle reading the keyboard input from the user.
m_Input = new InputClass;
if(!m_Input)
{
return false;
}
// Initialize the input object.
m_Input->Initialize();
// Create the graphics object. This object will handle rendering all the graphics for this application.
m_Graphics = new GraphicsClass;
if(!m_Graphics)
{
return false;
}
// Initialize the graphics object.
result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
if(!result)
{
return false;
}
return true;
}
Shutdown功能可进行清理。它关闭并释放与图形和输入对象相关的所有内容。此外,它还关闭窗口并清理与之关联的句柄。
void SystemClass::Shutdown()
{
// Release the graphics object.
if(m_Graphics)
{
m_Graphics->Shutdown();
delete m_Graphics;
m_Graphics = 0;
}
// Release the input object.
if(m_Input)
{
delete m_Input;
m_Input = 0;
}
// Shutdown the window.
ShutdownWindows();
return;
}
在我们决定退出之前,run函数是我们的应用程序循环并执行所有应用程序处理的地方。应用程序处理是在称为每个循环的框架函数中完成的。这是一个需要理解的重要概念,因为现在我们的应用程序的其余部分必须记住这一点。伪代码如下所示:
while not done
检查Windows系统消息
处理系统消息
流程应用循环
检查用户是否希望在帧处理期间退出
void SystemClass::Run()
{
MSG msg;
bool done, result;
// Initialize the message structure.
ZeroMemory(&msg, sizeof(MSG));
// Loop until there is a quit message from the window or the user.
done = false;
while(!done)
{
// Handle the windows messages.
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If windows signals to end the application then exit out.
if(msg.message == WM_QUIT)
{
done = true;
}
else
{
// Otherwise do the frame processing.
result = Frame();
if(!result)
{
done = true;
}
}
}
return;
}
下面的frame函数是我们应用程序的所有处理完成的地方。到目前为止,这相当简单,我们检查输入对象,看看用户是否按了escape并想退出。如果他们不想退出,那么我们调用图形对象来进行帧处理,这将为该帧呈现图形。随着应用程序的增长,我们将在这里放置更多的代码。
bool SystemClass::Frame()
{
bool result;
// Check if the user pressed escape and wants to exit the application.
if(m_Input->IsKeyDown(VK_ESCAPE))
{
return false;
}
// Do the frame processing for the graphics object.
result = m_Graphics->Frame();
if(!result)
{
return false;
}
return true;
}
messagehandler函数是引导Windows系统消息进入的位置。这样我们就可以倾听我们感兴趣的某些信息。目前,我们只需读取是否按下了一个键或者是否释放了一个键,并将该信息传递给输入对象。我们将传递回Windows默认消息处理程序的所有其他信息。
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
switch(umsg)
{
// Check if a key has been pressed on the keyboard.
case WM_KEYDOWN:
{
// If a key is pressed send it to the input object so it can record that state.
m_Input->KeyDown((unsigned int)wparam);
return 0;
}
// Check if a key has been released on the keyboard.
case WM_KEYUP:
{
// If a key is released then send it to the input object so it can unset the state for that key.
m_Input->KeyUp((unsigned int)wparam);
return 0;
}
// Any other messages send to the default message handler as our application won't make use of them.
default:
{
return DefWindowProc(hwnd, umsg, wparam, lparam);
}
}
}
initializeWindows函数是我们将代码放置在其中以构建将用于呈现的窗口。它将屏幕宽度和屏幕高度返回到调用函数,这样我们就可以在整个应用程序中使用它们。我们使用一些默认设置创建窗口,以初始化无边框的纯黑色窗口。该函数将根据名为“全屏”的全局变量生成一个小窗口或全屏窗口。如果设置为真,那么我们将使屏幕覆盖整个用户桌面窗口。如果设置为假,我们只需在屏幕中间创建一个800x600窗口。我把全屏幕全局变量放在graphicclass.h文件的顶部,以防您修改它。稍后,我为什么要将全局文件放在该文件中而不是该文件的头文件中,这是有意义的。
void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
WNDCLASSEX wc;
DEVMODE dmScreenSettings;
int posX, posY;
// Get an external pointer to this object.
ApplicationHandle = this;
// Get the instance of this application.
m_hinstance = GetModuleHandle(NULL);
// Give the application a name.
m_applicationName = L"Engine";
// Setup the windows class with default settings.
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = m_hinstance;
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hIconSm = wc.hIcon;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_applicationName;
wc.cbSize = sizeof(WNDCLASSEX);
// Register the window class.
RegisterClassEx(&wc);
// Determine the resolution of the clients desktop screen.
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
// Setup the screen settings depending on whether it is running in full screen or in windowed mode.
if(FULL_SCREEN)
{
// If full screen set the screen to maximum size of the users desktop and 32bit.
memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
dmScreenSettings.dmSize = sizeof(dmScreenSettings);
dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth;
dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight;
dmScreenSettings.dmBitsPerPel = 32;
dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
// Change the display settings to full screen.
ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
// Set the position of the window to the top left corner.
posX = posY = 0;
}
else
{
// If windowed then set it to 800x600 resolution.
screenWidth = 800;
screenHeight = 600;
// Place the window in the middle of the screen.
posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2;
posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2;
}
// Create the window with the screen settings and get the handle to it.
m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP,
posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL);
// Bring the window up on the screen and set it as main focus.
ShowWindow(m_hwnd, SW_SHOW);
SetForegroundWindow(m_hwnd);
SetFocus(m_hwnd);
// Hide the mouse cursor.
ShowCursor(false);
return;
}
ShutdownWindows就是这样。它将屏幕设置恢复正常,并释放窗口及其关联的句柄。
void SystemClass::ShutdownWindows()
{
// Show the mouse cursor.
ShowCursor(true);
// Fix the display settings if leaving full screen mode.
if(FULL_SCREEN)
{
ChangeDisplaySettings(NULL, 0);
}
// Remove the window.
DestroyWindow(m_hwnd);
m_hwnd = NULL;
// Remove the application instance.
UnregisterClass(m_applicationName, m_hinstance);
m_hinstance = NULL;
// Release the pointer to this class.
ApplicationHandle = NULL;
return;
}
wndproc函数是Windows向其发送消息的位置。当我们在上面的initializeWindows函数中用wc.lpfnwndproc=wndproc初始化window类时,您会注意到我们告诉Windows它的名称。我将它包含在这个类文件中,因为我们通过让它将所有消息发送到SystemClass内定义的MessageHandler函数,将它直接绑定到系统类中。这允许我们将消息传递功能直接挂钩到类中并保持代码的整洁。
LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
switch(umessage)
{
// Check if the window is being destroyed.
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
// Check if the window is being closed.
case WM_CLOSE:
{
PostQuitMessage(0);
return 0;
}
// All other messages pass to the message handler in the system class.
default:
{
return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
}
}
}
Inputclass.h
为了保持教程的简单性,我暂时使用了Windows输入,直到我在DirectInput上做了一个教程(这是非常优越的)。输入类处理来自键盘的用户输入。此类由SystemClass::MessageHandler函数提供输入。输入对象将把每个键的状态存储在键盘数组中。当被查询时,它会告诉调用函数是否按了某个键。这是标题:
// Filename: inputclass.h
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_
// Class name: InputClass
class InputClass
{
public:
InputClass();
InputClass(const InputClass&);
~InputClass();
void Initialize();
void KeyDown(unsigned int);
void KeyUp(unsigned int);
bool IsKeyDown(unsigned int);
private:
bool m_keys[256];
};
#endif
Inputclass.cpp
// Filename: inputclass.cpp
#include "inputclass.h"
InputClass::InputClass()
{
}
InputClass::InputClass(const InputClass& other)
{
}
InputClass::~InputClass()
{
}
void InputClass::Initialize()
{
int i;
// Initialize all the keys to being released and not pressed.
for(i=0; i<256; i++)
{
m_keys[i] = false;
}
return;
}
void InputClass::KeyDown(unsigned int input)
{
// If a key is pressed then save that state in the key array.
m_keys[input] = true;
return;
}
void InputClass::KeyUp(unsigned int input)
{
// If a key is released then clear that state in the key array.
m_keys[input] = false;
return;
}
bool InputClass::IsKeyDown(unsigned int key)
{
// Return what state the key is in (pressed/not pressed).
return m_keys[key];
}
Graphicsclass.h
图形类是由系统类创建的另一个对象。这个应用程序中的所有图形功能都将封装在这个类中。我还将使用此文件中的头文件来设置所有与图形相关的全局设置,如全屏或窗口模式。当前这个类是空的,但在以后的教程中将包含所有图形对象。
// Filename: graphicsclass.h
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
//
// INCLUDES //
//
#include <windows.h>
/
// GLOBALS //
/
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
We'll need these four globals to start with.
// Class name: GraphicsClass
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();
bool Initialize(int, int, HWND);
void Shutdown();
bool Frame();
private:
bool Render();
private:
};
#endif
Graphicsclass.cpp
因为我们只是在为本教程构建框架,所以我暂时将这个类保持为空。
// Filename: graphicsclass.cpp
#include "graphicsclass.h"
GraphicsClass::GraphicsClass()
{
}
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
GraphicsClass::~GraphicsClass()
{
}
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
return true;
}
void GraphicsClass::Shutdown()
{
return;
}
bool GraphicsClass::Frame()
{
return true;
}
bool GraphicsClass::Render()
{
return true;
}
总结
所以现在我们有了一个框架和一个将在屏幕上弹出的窗口。这个框架现在将成为所有未来教程的基础,因此理解这个框架工作是相当重要的。在继续下一个教程之前,请尝试“待办事项”练习,以确保代码能够编译并为您工作。如果您不理解这个框架结构,那么您仍然可以继续学习其他教程,这些教程在框架结构完成得更多之后可能对您更有意义。
做练习
1。在graphicsClass.h头中将FULL_SCREEN参数更改为true,然后重新编译并运行程序。在窗口显示后按Escape键退出。
(如果要看其他课的中文翻译版,请到我博客目录查找,我会抽时间把后续的课目都翻译出来,这取决于我有空闲时间。)
时间仓促,只是粗略翻译,可能有多处失误,请谅解。朋友如有发现哪里有错误,欢迎指正,联·系w新licheng16886