前言

提供键鼠输入可以说是一个游戏的必备要素。在这里,我们不使用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四个键控制立方体旋转,或者鼠标拖动旋转。

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11 With Windows SDK--05 键盘和鼠标输入的更多相关文章

  1. 粒子系统与雨的效果 (DirectX11 with Windows SDK)

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  2. DX11 Without DirectX SDK--05 键盘和鼠标输入

    回到 DirectX11--使用Windows SDK来进行开发 提供键鼠输入可以说是一个游戏的必备要素.在这里,我们不使用DirectInput,因为Windws SDK本身就不提供该头文件.这里我 ...

  3. 【windows】使用键盘代替鼠标的快捷键

    键盘代替鼠标右键 使用上述键可以代替右键

  4. unity3d的键盘和鼠标输入

    一.键盘的输入 •GetKey,GetKeyDown,GetKeyUp三个方法分别获取用户键盘按键的输入 1. GetKey:用户长按按键有效: bool down = Input.GetKeyDow ...

  5. TForm.ShowModal只是接管消息循环,禁止外部键盘和鼠标输入到别的窗口,但并不封锁其它窗口继续获取消息(比如WM_TIMER消息仍可被发送到别的窗口上)

    窗体上放一个TTimer,然后双击输入: procedure TForm1.Timer1Timer(Sender: TObject); var cvs: TCanvas; Rect: TRect; S ...

  6. DirectX11 With Windows SDK--21 鼠标拾取

    前言 拾取是一项非常重要的技术,不论是电脑上用鼠标操作,还是手机的触屏操作,只要涉及到UI控件的选取则必然要用到该项技术.除此之外,一些类似魔兽争霸3.星际争霸2这样的3D即时战略游戏也需要通过拾取技 ...

  7. DX11 Without DirectX SDK--使用Windows SDK来进行开发

    在看龙书(Introduction to 3D Game Programming with Directx 11)的时候,里面所使用的开发工具包为Microsoft DirectX SDK(June ...

  8. DirectX11 With Windows SDK--00 目录

    前言 (更新于 2019/4/10) 从第一次接触DirectX 11到现在已经有将近两年的时间了.还记得前年暑假被要求学习DirectX 11,在用龙书的源码配置项目运行环境的时候都花了好几天的时间 ...

  9. DirectX11 With Windows SDK--04 使用DirectX Tool Kit帮助开发

    前言(2018/11/4) DXTK库现在已经不随Github项目提供,因为只用到了其中的键鼠类,已经过提取加入到后续的项目中 但是如果你需要配置DirectXTK到自己的项目当中,可以参考这篇博客进 ...

随机推荐

  1. miller——rabin判断素数

    我们首先看这样一个很简单的问题:判定正整数\(n\)是否为素数 最简单的做法就是枚举\(2\)到\(n\)的所有数,看是否有数是\(n\)的因数,时间复杂度\(O(n)\) 稍微优化一下发现只要枚举\ ...

  2. day5 笔记

    笔记 字符格式化输出: 占位符%s s=string 字符型%d d=dight 整数型%f f=float 浮点数 约等于小数 通过格式:%(str1,str2,str3)一一对应 数据运算 数据类 ...

  3. 【HDU - 4345 】Permutation(DP)

    BUPT2017 wintertraining(15) #8F 题意 1到n的排列,经过几次置换(也是一个排列)回到原来的排列,就是循环了. 现在给n(<=1000),求循环周期的所有可能数. ...

  4. python3 集合set

    set是一种集合的数据类型,使用{}表示 集合中元素是无序的,并且不可重复,集合最重要的作用就是可以去重 set是不可哈希的,set中的元素必须是可哈希的 可以切片,可以迭代 交集.并集.差集.对称差 ...

  5. Nifi InvokeHttp processor

    Authorization: Bearer <access-token> Content_type: application/json       NIFI 中国社区 QQ群:595034 ...

  6. 「SCOI2014」方伯伯运椰子 解题报告

    「SCOI2014」方伯伯运椰子 可以看出是分数规划 然后我们可以看出其实只需要改变1的流量就可以了,因为每次改变要保证流量守恒,必须流成一个环,在正负性确定的情况下,变几次是无所谓的. 然后按照套路 ...

  7. 2019 校内赛 RPG的天赋分支(贪心)

    Problem Description 很多游戏都有天赋树的概念,天赋树的不同分支具有不同的属性加成,那么合理选择分支就非常重要了.Luke最近沉迷一款RPG游戏,它的天赋树机制如下:角色具有n个可选 ...

  8. javascript的性能优化tips

    谈到javascript的性能优化,有好多点,比如把script放到离body闭合标签附近,合并多个script标签等等,还有一些代码的性能,for的性能不如while的性能好,用while模拟for ...

  9. Docker自动补全容器名

    Zsh Place the completion script in your /path/to/zsh/completion (typically ~/.zsh/completion/): 下载自动 ...

  10. A1140. Look-and-say Sequence

    Look-and-say sequence is a sequence of integers as the following: D, D1, D111, D113, D11231, D112213 ...