跳轉到內容

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. 實現一個函式,讀取鍵盤緩衝區,並在螢幕上顯示您輸入的內容。

華夏公益教科書