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

前言

提供鍵鼠輸入能夠說是一個遊戲的必備要素。在這裏,咱們不使用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

Github項目源碼安全

歡迎加入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;       // 忽略

注意: 這裏要區分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,以及有什麼問題也能夠在這裏彙報。

相關文章
相關標籤/搜索