DirectX 11 Tutorial 2 中文翻译版教程:创建框架和窗口

原英文版地址:http://www.rastertek.com/dx11tut02.html

在开始使用Directx11编码之前,我建议构建一个简单的代码框架。这个框架将处理基本的Windows功能,并提供一种以有组织的、可读的方式扩展代码的简单方法。由于这些教程的目的只是尝试Directx11的不同功能,因此我们将有目的地使框架尽可能简单。
框架
框架工程将从四个项目开始。它将有一个winmain函数来处理应用程序的入口点。它还具有一个系统类,该类封装了将从winmain函数中调用的整个应用程序。在系统类中,我们将有一个用于处理用户输入的输入类和一个用于处理DirectX图形代码的图形类。以下是框架设置的示意图:

111111111111111111111111111111111111111111

现在我们了解了框架的设置方式,让我们从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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值