跳转到内容

DirectX/10.0/Direct3D/Direct Input

来自 Wikibooks,开放世界中的开放书籍

本教程将介绍如何在 DirectX 11 中使用 Direct Input。本教程中的代码将基于之前的字体教程代码。

Direct Input 是 DirectX API 提供的与输入设备交互的高速方法。在 DirectX 11 中,API 的 Direct Input 部分与之前的版本相比没有改变,仍然是版本 8。但是,Direct Input 在最初的实现中非常完善(类似于直接声音),因此没有必要更新它。Direct Input 比常规的 Windows 输入系统提供了难以置信的速度。任何需要高度响应输入设备的高性能应用程序都应该使用 Direct Input。

本教程将重点介绍如何为键盘和鼠标设备实现 Direct Input。我们还将使用 TextClass 来显示鼠标指针的当前位置。由于之前的教程已经有一个 InputClass,我们将使用 Direct Input 重新编写它,而不是之前使用过的 Windows 方法。

Inputclass.h

[edit | edit source]
////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_

您需要在头文件中定义您正在使用的 Direct Input 版本,否则编译器会生成烦人的消息,表明它默认使用版本 8。

///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define DIRECTINPUT_VERSION 0x0800

以下两个库需要链接才能使 Direct Input 工作。

/////////////
// LINKING //
/////////////
#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "dxguid.lib")

这是 Direct Input 所需的头文件。

//////////////
// INCLUDES //
//////////////
#include <dinput.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: InputClass
////////////////////////////////////////////////////////////////////////////////
class InputClass
{
public:
	InputClass();
	InputClass(const InputClass&);
	~InputClass();

	bool Initialize(HINSTANCE, HWND, int, int);
	void Shutdown();
	bool Frame();

	bool IsEscapePressed();
	void GetMouseLocation(int&, int&);

private:
	bool ReadKeyboard();
	bool ReadMouse();
	void ProcessInput();

private:

前三个私有成员变量是 Direct Input、键盘设备和鼠标设备的接口。

	IDirectInput8* m_directInput;
	IDirectInputDevice8* m_keyboard;
	IDirectInputDevice8* m_mouse;

接下来的两个私有成员变量用于记录键盘和鼠标设备的当前状态。

	unsigned char m_keyboardState[256];
	DIMOUSESTATE m_mouseState;

	int m_screenWidth, m_screenHeight;
	int m_mouseX, m_mouseY;
};

#endif

Inputclass.cpp

[edit | edit source]
////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "inputclass.h"

类构造函数将 Direct Input 接口变量初始化为 null。

InputClass::InputClass()
{
	m_directInput = 0;
	m_keyboard = 0;
	m_mouse = 0;
}


InputClass::InputClass(const InputClass& other)
{
}


InputClass::~InputClass()
{
}


bool InputClass::Initialize(HINSTANCE hinstance, HWND hwnd, int screenWidth, int screenHeight)
{
	HRESULT result;


	// Store the screen size which will be used for positioning the mouse cursor.
	m_screenWidth = screenWidth;
	m_screenHeight = screenHeight;

	// Initialize the location of the mouse on the screen.
	m_mouseX = 0;
	m_mouseY = 0;

此函数调用将初始化 Direct Input 的接口。一旦您拥有一个 Direct Input 对象,就可以初始化其他输入设备。

	// Initialize the main direct input interface.
	result = DirectInput8Create(hinstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_directInput, NULL);
	if(FAILED(result))
	{
		return false;
	}

我们将初始化的第一个输入设备是键盘。

	// Initialize the direct input interface for the keyboard.
	result = m_directInput->CreateDevice(GUID_SysKeyboard, &m_keyboard, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Set the data format.  In this case since it is a keyboard we can use the predefined data format.
	result = m_keyboard->SetDataFormat(&c_dfDIKeyboard);
	if(FAILED(result))
	{
		return false;
	}

设置键盘的协作级别在它的作用和您从那时起如何使用设备方面都很重要。在这种情况下,我们将把它设置为不与其他程序共享(DISCL_EXCLUSIVE)。这样,如果您按下某个键,只有您的应用程序可以看到该输入,而其他应用程序将无法访问它。但是,如果您希望其他应用程序在您的程序运行时访问键盘输入,您可以将其设置为非排他性(DISCL_NONEXCLUSIVE)。现在,打印屏幕键再次有效,其他正在运行的应用程序可以使用键盘进行控制等等。请记住,如果它是非排他性的,并且您在窗口模式下运行,那么您需要检查设备何时失去焦点以及何时重新获得焦点,以便它可以重新获取设备以供再次使用。这种焦点丢失通常发生在其他窗口成为您的窗口的主要焦点或您的窗口被最小化时。

	// Set the cooperative level of the keyboard to not share with other programs.
	result = m_keyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
	if(FAILED(result))
	{
		return false;
	}

键盘设置完成后,我们调用 Acquire 最终获得键盘的访问权限,以便从现在开始使用它。

	// Now acquire the keyboard.
	result = m_keyboard->Acquire();
	if(FAILED(result))
	{
		return false;
	}

接下来我们设置的输入设备是鼠标。

	// Initialize the direct input interface for the mouse.
	result = m_directInput->CreateDevice(GUID_SysMouse, &m_mouse, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Set the data format for the mouse using the pre-defined mouse data format.
	result = m_mouse->SetDataFormat(&c_dfDIMouse);
	if(FAILED(result))
	{
		return false;
	}

我们对鼠标使用非排他性协作设置。我们将不得不检查它何时进出焦点,并在每次进出焦点时重新获取它。

	// Set the cooperative level of the mouse to share with other programs.
	result = m_mouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
	if(FAILED(result))
	{
		return false;
	}

鼠标设置完成后,我们获取它,以便我们可以开始使用它。

	// Acquire the mouse.
	result = m_mouse->Acquire();
	if(FAILED(result))
	{
		return false;
	}

	return true;
}

Shutdown 函数释放这两个设备和 Direct Input 的接口。请注意,设备始终先取消获取,然后释放。

void InputClass::Shutdown()
{
	// Release the mouse.
	if(m_mouse)
	{
		m_mouse->Unacquire();
		m_mouse->Release();
		m_mouse = 0;
	}

	// Release the keyboard.
	if(m_keyboard)
	{
		m_keyboard->Unacquire();
		m_keyboard->Release();
		m_keyboard = 0;
	}

	// Release the main interface to direct input.
	if(m_directInput)
	{
		m_directInput->Release();
		m_directInput = 0;
	}

	return;
}

InputClass 的 Frame 函数将读取设备的当前状态到我们设置的状态缓冲区。读取每个设备的状态后,它会处理这些更改。

bool InputClass::Frame()
{
	bool result;


	// Read the current state of the keyboard.
	result = ReadKeyboard();
	if(!result)
	{
		return false;
	}

	// Read the current state of the mouse.
	result = ReadMouse();
	if(!result)
	{
		return false;
	}

	// Process the changes in the mouse and keyboard.
	ProcessInput();

	return true;
}

ReadKeyboard 将读取键盘的状态到 m_keyboardState 变量中。该状态将显示当前按下的或未按下的任何键。如果它读取键盘失败,可能是以下五种原因之一。我们想要恢复的只有两种情况:焦点丢失或取消获取。如果是这种情况,我们每帧调用一次 acquire,直到我们重新获得控制权。窗口可能被最小化,在这种情况下 Acquire 会失败,但一旦窗口再次进入前台,Acquire 就会成功,我们就可以读取键盘状态。另外三种错误类型在本教程中我不想恢复。

bool InputClass::ReadKeyboard()
{
	HRESULT result;


	// Read the keyboard device.
	result = m_keyboard->GetDeviceState(sizeof(m_keyboardState), (LPVOID)&m_keyboardState);
	if(FAILED(result))
	{
		// If the keyboard lost focus or was not acquired then try to get control back.
		if((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
		{
			m_keyboard->Acquire();
		}
		else
		{
			return false;
		}
	}
		
	return true;
}

ReadMouse 将读取鼠标的状态,类似于 ReadKeyboard 读取键盘的状态。但是,鼠标的状态只是从上一帧到当前帧位置的改变。例如,更新看起来像是鼠标向右移动了 5 个单位,但它不会给出鼠标在屏幕上的实际位置。此增量信息非常有用,我们可以自己维护鼠标在屏幕上的位置。

bool InputClass::ReadMouse()
{
	HRESULT result;


	// Read the mouse device.
	result = m_mouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);
	if(FAILED(result))
	{
		// If the mouse lost focus or was not acquired then try to get control back.
		if((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
		{
			m_mouse->Acquire();
		}
		else
		{
			return false;
		}
	}

	return true;
}

ProcessInput 函数是我们在上一帧后处理输入设备中发生的更改的地方。在本教程中,我们将只进行一个简单的鼠标位置更新,类似于 Windows 如何跟踪鼠标光标的位置。为此,我们使用 m_mouseX 和 m_mouseY 变量(它们被初始化为零),然后简单地将鼠标位置的更改添加到这两个变量中。这将根据用户在周围移动鼠标来维护鼠标的位置。

请注意,我们确实检查了鼠标位置是否从未超出屏幕。即使用户不断将鼠标向左移动,我们也会将光标保持在零位置,直到他们再次开始向右移动。

void InputClass::ProcessInput()
{
	// Update the location of the mouse cursor based on the change of the mouse location during the frame.
	m_mouseX += m_mouseState.lX;
	m_mouseY += m_mouseState.lY;

	// Ensure the mouse location doesn't exceed the screen width or height.
	if(m_mouseX  m_screenWidth)  { m_mouseX = m_screenWidth; }
	if(m_mouseY > m_screenHeight) { m_mouseY = m_screenHeight; }
	
	return;
}

我在 InputClass 中添加了一个名为 IsEscapePressed 的函数。这展示了如何利用键盘来检查特定按键是否当前被按下。您可以编写其他函数来检查对您的应用程序感兴趣的任何其他按键。

bool InputClass::IsEscapePressed()
{
	// Do a bitwise and on the keyboard state to check if the escape key is currently being pressed.
	if(m_keyboardState[DIK_ESCAPE] & 0x80)
	{
		return true;
	}

	return false;
}

GetMouseLocation 是我编写的一个辅助函数,它返回鼠标的位置。GraphicsClass 可以获取此信息,然后使用 TextClass 将鼠标 X 和 Y 位置渲染到屏幕上。

void InputClass::GetMouseLocation(int& mouseX, int& mouseY)
{
	mouseX = m_mouseX;
	mouseY = m_mouseY;
	return;
}

Systemclass.cpp

[edit | edit source]

我将只介绍在删除 Windows 输入系统并添加 DirectX 输入系统后发生更改的函数。

////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"


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;
	}

Input 对象的初始化现在有所不同,因为它需要窗口、实例和屏幕尺寸变量的句柄。它还返回一个布尔值来指示它在启动 Direct Input 时是否成功。

	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// 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;
}


void SystemClass::Shutdown()
{
	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}

释放 Input 对象现在需要在删除对象之前进行 Shutdown 调用。

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}


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.  If frame processing fails then exit.
			result = Frame();
			if(!result)
			{
				MessageBox(m_hwnd, L"Frame Processing Failed", L"Error", MB_OK);
				done = true;
			}
		}

Run 函数中对 Esc 键的检查现在有所不同,它通过检查 InputClass 中辅助函数的返回值来完成。

		// Check if the user pressed escape and wants to quit.
		if(m_Input->IsEscapePressed() == true)
		{
			done = true;
		}
	}

	return;
}


bool SystemClass::Frame()
{
	bool result;
	int mouseX, mouseY;

在 Frame 函数中,我们调用 Input 对象自己的 Frame 函数来更新键盘和鼠标的状态。此调用可能会失败,因此我们需要检查返回值。

	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}

在读取输入设备更新后,我们使用鼠标的位置更新 GraphicsClass,以便它可以将其以文本形式渲染到屏幕上。

	// Get the location of the mouse from the input object,
	m_Input->GetMouseLocation(mouseX, mouseY);

	// Do the frame processing for the graphics object.
	result = m_Graphics->Frame(mouseX, mouseY);
	if(!result)
	{
		return false;
	}

	// Finally render the graphics to the screen.
	result = m_Graphics->Render();
	if(!result)
	{
		return false;
	}

	return true;
}

我们已从 MessageHandler 函数中删除了 Windows 键盘读取。Direct Input 现在为我们处理了所有这些。

LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
	return DefWindowProc(hwnd, umsg, wparam, lparam);
}

Graphicsclass.h

[edit | edit source]
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();

Frame 函数现在接受两个整数,用于每帧的鼠标位置更新。

	bool Frame(int, int);
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextClass* m_Text;
};

#endif

Graphicsclass.cpp

[edit | edit source]

我将只介绍自上一教程以来发生更改的函数。

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

Frame 函数现在接受鼠标 X 和 Y 位置,然后让 TextClass 对象更新将位置写入屏幕的文本字符串。

bool GraphicsClass::Frame(int mouseX, int mouseY)
{
	bool result;


	// Set the location of the mouse.
	result = m_Text->SetMousePosition(mouseX, mouseY, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	return true;
}

Textclass.h

[edit | edit source]
////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:
	struct SentenceType
	{
		ID3D11Buffer *vertexBuffer, *indexBuffer;
		int vertexCount, indexCount, maxLength;
		float red, green, blue;
	};

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

public:
	TextClass();
	TextClass(const TextClass&);
	~TextClass();

	bool Initialize(ID3D11Device*, ID3D11DeviceContext*, HWND, int, int, D3DXMATRIX);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX);

TextClass 现在有一个新的函数,用于设置鼠标的位置。

	bool SetMousePosition(int, int, ID3D11DeviceContext*);

private:
	bool InitializeSentence(SentenceType**, int, ID3D11Device*);
	bool UpdateSentence(SentenceType*, char*, int, int, float, float, float, ID3D11DeviceContext*);
	void ReleaseSentence(SentenceType**);
	bool RenderSentence(ID3D11DeviceContext*, SentenceType*, D3DXMATRIX, D3DXMATRIX);

private:
	FontClass* m_Font;
	FontShaderClass* m_FontShader;
	int m_screenWidth, m_screenHeight;
	D3DXMATRIX m_baseViewMatrix;
	SentenceType* m_sentence1;
	SentenceType* m_sentence2;
};

#endif

Textclass.cpp

[edit | edit source]

我将只介绍自上一教程以来发生更改的函数。

///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"

我们现在在 TextClass 中有一个新的函数,它将鼠标 X 和 Y 位置转换为两个字符串,然后更新两个句子,以便可以将鼠标的位置渲染到屏幕上。

bool TextClass::SetMousePosition(int mouseX, int mouseY, ID3D11DeviceContext* deviceContext)
{
	char tempString[16];
	char mouseString[16];
	bool result;


	// Convert the mouseX integer to string format.
	_itoa_s(mouseX, tempString, 10);

	// Setup the mouseX string.
	strcpy_s(mouseString, "Mouse X: ");
	strcat_s(mouseString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence1, mouseString, 20, 20, 1.0f, 1.0f, 1.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	// Convert the mouseY integer to string format.
	_itoa_s(mouseY, tempString, 10);

	// Setup the mouseY string.
	strcpy_s(mouseString, "Mouse Y: ");
	strcat_s(mouseString, tempString);

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence2, mouseString, 20, 40, 1.0f, 1.0f, 1.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	return true;
}

总结

[edit | edit source]

如您所见,在 DirectX 11 中设置 Direct Input 非常简单,它为我们提供了对输入设备信息的快速访问。

待办事项

[编辑 | 编辑源代码]

1. 重新编译并运行程序。移动鼠标,观察文本位置更新。

2. 使用 2D 渲染教程中的信息,并将其与本教程结合,创建自己的鼠标光标,使其随着鼠标移动而移动。

3. 实现一个函数,读取键盘缓冲区,并在屏幕上显示您输入的内容。

华夏公益教科书