DirectX11 With Windows SDK--06 鍵盤和鼠標輸入


前言

提供鍵鼠輸入可以說是一個游戲的必備要素。在這里,我們不使用DirectInput,而是使用Windows消息處理機制中的Raw Input,不過要從頭開始實現會讓事情變得很復雜。DXTK提供了鼠標輸入的Mouse.h和鍵盤輸入的Keyboard.h現在已經單獨抽離出來使用),對消息處理機制進行了封裝,使用Mouse類和Keyboard類可以讓我們的開發效率事半功倍。

Raw Input有興趣的同學,你可以看鍵鼠類的內部實現,也可以看MSDN文檔: Raw Input

Mouse類和Keyboard類都在名稱空間DirectX內。

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。

鼠標輸入

Mouse類是一個單例類,我們可以通過Mouse::Get()靜態方法來獲取該實例:

static Mouse& Mouse::Get();

也可以在應用程序類中直接作為成員使用,因為它提供了默認構造函數。不過為了異常安全,建議使用智能指針:

std::unique_ptr<DirectX::Mouse> m_pMouse;
m_pMouse = std::make_unique<DirectX::Mouse>();

要想使用鼠標類,我們還需要完成三件事情:

1.在初始化階段給鼠標類設置要綁定的窗口句柄,使用Mouse::SetWindow方法:

void Mouse::SetWindow(HWND window);

2.設置鼠標模式,需要使用Mouse::SetMode方法:

void Mouse::SetMode(Mouse::Mode mode);

鼠標模式有下面兩種:

enum Mode
{
    MODE_ABSOLUTE = 0,  // 絕對坐標模式,每次狀態更新xy值為屏幕像素坐標,且鼠標可見
    MODE_RELATIVE,      // 相對運動模式,每次狀態更新xy值為每一幀之間的像素位移量,且鼠標不可見
};

可以加在D3DApp::Init方法上:

bool D3DApp::Init()
{
      m_pMouse = std::make_unique<DirectX::Mouse>();
      m_pKeyboard = std::make_unique<DirectX::Keyboard>();

      if (!InitMainWindow())
            return false;

      if (!InitDirect3D())
            return false;

      return true;
}

3.在消息處理的回調函數上進行修改,在接收到下面這些消息后,需要調用Mouse::ProcessMessage方法:

WM_ACTIVATEAPP WM_INPUT
WM_LBUTTONDOWN WM_MBUTTONDOWN WM_RBUTTONDOWN WM_XBUTTONDOWN
WM_LBUTTONUP WM_MBUTTONUP WM_RBUTTONUP WM_XBUTTONUP	
WM_MOUSEWHEEL WM_MOUSEHOVER WM_MOUSEMOVE

具體的修改的部分在最后會展示。

完成這些操作后,我們的程序就可以開始使用鼠標了。

對於每一幀,我們可以通過Mouse::GetState方法獲取當前幀下鼠標的運動狀態:

Mouse::State Mouse::GetState() const;

Mouse::State包含如下成員:

struct State
{
    bool    leftButton;         // 鼠標左鍵被按下
    bool    middleButton;       // 鼠標滾輪鍵被按下
    bool    rightButton;        // 鼠標右鍵被按下
    bool    xButton1;           // 忽略
    bool    xButton2;           // 忽略
    int     x;                  // 絕對坐標x或相對偏移量
    int     y;                  // 絕對坐標y或相對偏移量
    int     scrollWheelValue;   // 滾輪滾動累積值
    Mode    positionMode;       // 鼠標模式
};

對於剩下的方法:

Mouse::ResetScrollWheelValue方法可以清空滾輪的滾動累積值

Mouse::IsConnected方法則可以檢驗鼠標是否連接

Mouse::SetVisible方法設置鼠標是否可見

Mouse::IsVisible方法可以檢驗鼠標是否可見

鼠標狀態追蹤

Mouse::ButtonStateTracker類提供了更高級的功能,通過根據上一次的鼠標事件和當前鼠標事件的對比,來判斷鼠標的狀態。它有兩個重要方法:

void Mouse::ButtonStateTracker::Update( const Mouse::State& state );
// 在每一幀的時候應提供Mouse的當前狀態去更新它

State Mouse::ButtonStateTracker::GetLastState() const;
// 獲取上一幀的鼠標事件,應當在Update之前使用,否則變為獲取當前幀的狀態

然后還有5個重要公共成員:

ButtonState leftButton;     // 鼠標左鍵狀態
ButtonState middleButton;   // 鼠標滾輪按鍵狀態
ButtonState rightButton;    // 鼠標右鍵狀態
ButtonState xButton1;       // 忽略
ButtonState xButton2;       // 忽略

注意: 這里要區分StateButtonState類型的五個同名成員,含義不同。

枚舉量ButtonState含義:

enum ButtonState
{
    UP = 0,         // 按鈕未被按下
    HELD = 1,       // 按鈕長按中
    RELEASED = 2,   // 按鈕剛被放開
    PRESSED = 3,    // 按鈕剛被按下
};

由於鼠標狀態追蹤類不是單例,而且它存有上一幀的鼠標狀態,不應該作為一個臨時變量,而是也應該像鼠標類一樣在整個程序生命周期內都存在。

在絕對模式下,我們也可以獲取兩幀之間的鼠標相對位移量:

Mouse::State mouseState = m_pMouse->GetState();
Mouse::State lastMouseState = mMouseTracker.GetLastState();
int dx = mouseState.x - lastMouseState.x, dy = mouseState.y - lastMouseState.y;

雖然這樣子第一幀的時候,鼠標狀態追蹤類並沒有開始記錄,而鼠標已經記錄下了第一幀的狀態,但由於開始運行的第一幀通常都很快,等到我們第一次使用鼠標的時候已經經過了一段時間,所以並不會產生什么問題。

然后在更新了跟蹤器狀態后,就可以判斷鼠標狀態做進一步操作了。以渲染立方體的項目為例,這里打算通過鼠標拖動產生旋轉,如:

// 更新鼠標按鈕狀態跟蹤器,僅當鼠標按住的情況下才進行移動
mMouseTracker.Update(state);
if (mouseState.leftButton == true && mMouseTracker.leftButton == mMouseTracker.HELD)
{
    // 旋轉立方體
	cubeTheta -= (mouseState.x - lastMouseState.x) * 0.01f;
	cubePhi -= (mouseState.y - lastMouseState.y) * 0.01f;
}

mCBuffer.world = XMMatrixRotationY(cubeTheta) * XMMatrixRotationX(cubePhi);

鍵盤輸入

Keyboard類也是一個單例類,我們可以通過Keyboard::Get()靜態方法來獲取該實例:

static Keyboard& Keyboard::Get();

也可以在應用程序類中直接作為成員使用,因為它提供了默認構造函數。不過為了異常安全,建議使用智能指針:

std::unique_ptr<DirectX::Keyboard> m_pKeyboard;
m_pKeyboard = std::make_unique<DirectX::Keyboard>();

要想使用鍵盤類,我們還需要完成一件或兩件事情:

1.如果Keyboard類內有SetWindow方法,則需要調用以初始化,否則不需要:

void Keyboard::SetWindow(ABI::Windows::UI::Core::ICoreWindow* window);

2.在消息處理的回調函數上進行修改,在接收到下面這些消息后,需要調用Keyboard::ProcessMessage方法:

WM_ACTIVATEAPP
WM_KEYDOWN WM_SYSKEYDOWN WM_KEYUP WM_SYSKEYUP

具體的修改的部分在最后會展示。

完成上述操作我們就可以使用鍵盤了。

對於每一幀,我們可以通過Keyboard::GetState方法獲取當前幀下鍵盤所有按鍵的狀態:

Keyboard::State Keyboard::GetState() const;

Keyboard::State結構體記錄了按鍵信息,而Keyboard::Keys枚舉量定義了有哪些按鍵。獲取了鍵盤按鍵狀態后,我們要關注的是Keyboard::State內的方法:

Keyboard::State::IsKeyDown方法判斷按鍵是否被按下

bool Keyboard::State::IsKeyDown(Keyboard::Keys key) const;

Keyboard::State::IsKeyUp方法判斷按鍵是否沒有按下

bool Keyboard::State::IsKeyUp(Keyboard::Keys key) const;

下面演示的是鍵盤連續操作,其中dt是兩幀之間的時間間隔

if (keyState.IsKeyDown(Keyboard::W))
	cubePhi += dt * 2;
if (keyState.IsKeyDown(Keyboard::S))
	cubePhi -= dt * 2;
if (keyState.IsKeyDown(Keyboard::A))
	cubeTheta += dt * 2;
if (keyState.IsKeyDown(Keyboard::D))
	cubeTheta -= dt * 2;

mCBuffer.world = XMMatrixRotationY(cubeTheta) * XMMatrixRotationX(cubePhi);

注意:對於鼠標和鍵盤的拖動距離,推薦鼠標用偏移量,而推薦鍵盤用按壓持續時間

鍵盤狀態追蹤

如果要判斷按鍵是剛按下還是剛放開,則需要Keyboard::KeyboardStateTracker類幫助

Keyboard::KeyboardStateTracker::Update方法需要接受當前幀的State以進行更新:

void Keyboard::KeyboardStateTracker::Update(const Keyboard::State& state);

然后就可以使用它的IsKeyPressedIsKeyReleased方法來進行判斷鍵盤按鍵是否剛按下,或者剛釋放了,同樣需要接受Keyboard::Keys枚舉量

於是我們可以嘗試讓前面的立方體通過鍵盤或鼠標動起來。

D3DApp::MsgProc方法的變化

這里展示了消息處理部分的變化:

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
		// 省略原有的部分...
        
		// 監測這些鍵盤/鼠標事件
	case WM_INPUT:

	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
	case WM_XBUTTONDOWN:

	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
	case WM_XBUTTONUP:

	case WM_MOUSEWHEEL:
	case WM_MOUSEHOVER:
	case WM_MOUSEMOVE:
		m_pMouse->ProcessMessage(msg, wParam, lParam);
		return 0;

	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
	case WM_KEYUP:
	case WM_SYSKEYUP:
		m_pKeyboard->ProcessMessage(msg, wParam, lParam);
		return 0;

	case WM_ACTIVATEAPP:
		m_pMouse->ProcessMessage(msg, wParam, lParam);
		m_pKeyboard->ProcessMessage(msg, wParam, lParam);
		return 0;
	}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}

補充說明:當你打開Mouse.cppKeyboard.cpp查看源碼的時候,大概率會遇到下面的報錯:

你可以直接無視該錯誤繼續編譯,即便到VS2019這個情況依然存在。

現在來看看當前章節對應的項目。該程序使用鍵盤的WSAD四個鍵控制立方體旋轉,或者鼠標拖動旋轉。

image

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM