提供鍵鼠輸入能夠說是一個遊戲的必備要素。在這裏,咱們不使用DirectInput
,而是使用Windows消息處理機制中的Raw Input
,不過要從頭開始實現會讓事情變得很複雜。DXTK提供了鼠標輸入的Mouse.h
和鍵盤輸入的Keyboard.h
(如今已經單獨抽離出來使用),對消息處理機制進行了封裝,使用Mouse
類和Keyboard
類可讓咱們的開發效率事半功倍。html
對Raw Input
有興趣的同窗,你能夠看鍵鼠類的內部實現,也能夠看MSDN文檔: Raw Inputgit
Mouse
類和Keyboard
類都在名稱空間DirectX
內。github
DirectX11 With Windows SDK完整目錄windows
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。app
Mouse
類是一個單例類,咱們能夠經過Mouse::Get()
靜態方法來獲取該實例:函數
static Mouse& Mouse::Get();
也能夠在應用程序類中直接做爲成員使用,由於它提供了默認構造函數。不過爲了異常安全,建議使用智能指針:3d
std::unique_ptr<DirectX::Mouse> m_pMouse; m_pMouse = std::make_unique<DirectX::Mouse>();
要想使用鼠標類,咱們還須要完成三件事情:指針
1.在初始化階段給鼠標類設置要綁定的窗口句柄,使用Mouse::SetWindow
方法:code
void Mouse::SetWindow(HWND window);
2.設置鼠標模式,須要使用Mouse::SetMode
方法:
void Mouse::SetMode(Mouse::Mode mode);
鼠標模式有下面兩種:
enum Mode { MODE_ABSOLUTE = 0, // 絕對座標模式,每次狀態更新xy值爲屏幕像素座標,且鼠標可見 MODE_RELATIVE, // 相對運動模式,每次狀態更新xy值爲每一幀之間的像素位移量,且鼠標不可見 };
能夠加在GameApp::Init
方法上:
bool GameApp::Init() { m_pMouse = std::make_unique<DirectX::Mouse>(); m_pKeyboard = std::make_unique<DirectX::Keyboard>(); if (!D3DApp::Init()) return false; if (!InitEffect()) return false; if (!InitResource()) return false; // 初始化鼠標,鍵盤不須要 m_pMouse->SetWindow(mhMainWnd); m_pMouse->SetMode(DirectX::Mouse::MODE_ABSOLUTE); 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; // 忽略
注意: 這裏要區分
State
和ButtonState
類型的五個同名成員,含義不一樣。
枚舉量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);
而後就可使用它的IsKeyPressed
或IsKeyReleased
方法來進行判斷鍵盤按鍵是否剛按下,或者剛釋放了,一樣須要接受Keyboard::Keys
枚舉量
因而咱們能夠嘗試讓前面的立方體經過鍵盤或鼠標動起來。
這裏展現了消息處理部分的變化:
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.cpp
和Keyboard.cpp
查看源碼的時候,大機率會遇到下面的報錯:
你能夠直接無視該錯誤繼續編譯,即使到VS2019這個狀況依然存在。
如今來看看當前章節對應的項目。該程序使用鍵盤的WSAD四個鍵控制魔方旋轉,或者鼠標拖動旋轉。
DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。