游戏手柄(JoyStick)编程学习笔记(1)

最近我开发的一个项目中需要用手柄控制一个二维移动平台的运动,找了些工业用的操作杆,感觉都挺丑的。后来想到打游戏时用的游戏手柄就挺漂亮,就决定试试用游戏手柄作为控制手柄来用。

上网查了查,在 WINDOWS 下编程控制游戏手柄比较主流的技术是用 DirectInput。如果不需要复杂的控制功能,也可以直接使用 WIN API (multimedia joystick API)。这篇学习笔记就采用 WIN API 的方式。

游戏手柄有点像是鼠标和键盘的混合体,既可以用来改变位置信息,也有一系列的按键。在 windows 系统中,是通过一个系统服务来实时监控游戏手柄的状态的。这个系统服务最多可以同时监控两个游戏手柄,每个游戏手柄最多可以有四个控制键。我们在程序中既可以直接读取游戏手柄的位置信息和按键状态,也可以通过监控相应的系统消息的方式来获得游戏手柄的信息。多数时候我们在程序中都采用接收游戏手柄消息的方式,因为这种方式不需要定时轮询游戏手柄的状态。

游戏手柄相关的函数都封装在 Winmm.dll 中。相应的头文件是 Mmsystem.h, 链接时需要链接 Winmm.lib。

查询游戏手柄信息

查询游戏手柄信息主要有三个函数。在介绍这三个函数之前需要先介绍 2 个 结构体:JOYINFO 和 JOYINFOEX。

typedef struct {UINT wXpos;UINT wYpos;UINT wZpos;UINT wButtons;
} JOYINFO;

JOYINFO 比较简单, wXpos、yXpos、zXpos 分别返回当前的坐标位置。wButtons 返回当前是否有按键按下了。 JOYINFO 结构体支持四个按键。分别是下面这四个:

  1. JOY_BUTTON1 表示按下了第一个按键。
  2. JOY_BUTTON2 表示按下了第二个按键。
  3. JOY_BUTTON3 表示按下了第三个按键。
  4. JOY_BUTTON4 表示按下了第四个按键。

我试了下,其实 JOYINFO 也是支持 32 个按键的。

如果有多个按键被按下了,那么 wButtons 的值就是这几个按键的值的或运算。

typedef struct joyinfoex_tag {DWORD dwSize;DWORD dwFlags;DWORD dwXpos;DWORD dwYpos;DWORD dwZpos;DWORD dwRpos;DWORD dwUpos;DWORD dwVpos;DWORD dwButtons;DWORD dwButtonNumber;DWORD dwPOV;DWORD dwReserved1;DWORD dwReserved2;
} JOYINFOEX;

这个结构体扩充了 JOYINFO。支持最多 6 个轴的位置信息和最多 32 个按键。
dwSize 为这个结构体的字节数,调用 joyGetPosEx 函数时需提前设置这个值。
dwFlags 为不同的值时 joyGetPosEx 函数通过这个结构体返回不同的信息。

  1. JOY_RETURNX dwXpos 返回 X 轴的位置信息。
  2. JOY_RETURNY dwXpos 返回 Y 轴的位置信息。
  3. JOY_RETURNZ dwXpos 返回 Z 轴的位置信息。
  4. JOY_RETURNR dwXpos 返回 R 轴的位置信息。
  5. JOY_RETURNU dwXpos 返回 U 轴的位置信息。
  6. JOY_RETURNV dwXpos 返回 V 轴的位置信息。
  7. JOY_RETURNBUTTONS dwButtons 返回按键信息。
  8. JOY_RETURNPOV dwPOV 返回 POV 信息。
  9. JOY_RETURNPOVCTS dwPOV 返回 POV 信息, 100 表示 1 度。
  10. JOY_RETURNALL 返回所有信息。
  11. JOY_RETURNRAWDATA 返回所有信息(游戏手柄的原始信息)。

理解了这两个结构体后就可以学习这个函数了。

/*** \brief 获取当前计算机中有多少游戏手柄。* \return 返回当前计算机中有多少游戏手柄。*/
UINT joyGetNumDevs(void); /***  \brief 获取指定的游戏手柄的状态信息。最多只支持三轴游戏手柄,每个游戏手柄支持 4 个按键。*  \param [in] uJoyID 游戏手柄的 ID,如果只有一个游戏手柄就是 JOYSTICKID1*  \param [inout] pji 指向一个 JOYINFO 的指针,通过它返回游戏手柄的位置信息和按键信息*  \return JOYERR_NOERROR 表示成功返回。 MMSYSERR_NODRIVER 表示没有加载游戏手柄驱动程序。MMSYSERR_INVALPARAM 表示传入的参数有误。JOYERR_UNPLUGGED 表示指定的游戏手柄没有接入系统。*  */
MMRESULT joyGetPos(UINT uJoyID, LPJOYINFO pji);/***  \brief 获取指定的游戏手柄的状态信息。*  \param [in] uJoyID 游戏手柄的 ID,如果只有一个游戏手柄就是 JOYSTICKID1*  \param [inout] pji 指向一个 JOYINFOEX 的指针,通过它返回游戏手柄的位置信息和按键信息,传入时需提前填好 dwSize 和 dwFlags 的值,否则函数调用会失败。*  \return JOYERR_NOERROR 表示成功返回。 MMSYSERR_NODRIVER 表示没有加载游戏手柄驱动程序。MMSYSERR_INVALPARAM 表示传入的参数有误。MMSYSERR_BADDEVICEID 表示传入的 uJoyID 不合法。JOYERR_UNPLUGGED 表示指定的游戏手柄没有接入系统。*  */
MMRESULT joyGetPosEx(UINT uJoyID, LPJOYINFOEX pji); 

因为 joyGetPos 函数能支持 32 个按键,所以一般是用不到 joyGetPosEx 函数的。

下面是个简单的代码片段:

    JOYINFO joyinfo; UINT wNumDevs, wDeviceID; BOOL bDev1Attached, bDev2Attached; if((wNumDevs = joyGetNumDevs()) == 0) return ERR_NODRIVER; bDev1Attached = joyGetPos(JOYSTICKID1, &joyinfo) != JOYERR_UNPLUGGED; bDev2Attached = wNumDevs == 2 && joyGetPos(JOYSTICKID2,&joyinfo) != JOYERR_UNPLUGGED; if(bDev1Attached || bDev2Attached)   // decide which joystick to use wDeviceID = bDev1Attached ? JOYSTICKID1 : JOYSTICKID2; else return ERR_NODEVICE;

这个例子很简单,先用 joyGetNumDevs() 函数判断有多少个游戏手柄。之后获得游戏手柄的当前状态。

确定游戏手柄一切正常之后就可以开始监听游戏手柄的消息了。默认情况下,WINDOWS 系统是不监控游戏手柄的,所以需要调用 joySetCapture 函数通知相应的系统服务将游戏手柄的状态改变以消息的形式传给我们的应用程序。接收消息的窗口的 HWND 需要作为第一个参数传给 joySetCapture 函数,最后一个参数如果为 FALSE 时就会不停的发送消息,即使游戏手柄的状态没有变化。

下面是代码片段:

    if(wNumDevs > 0){joySetThreshold(JOYSTICKID1, 10000);joySetCapture((HWND)this->winId(), JOYSTICKID1, NULL, TRUE);}

剩下就是消息处理了。只有三个相应的消息类型:MM_JOY1MOVE, MM_JOY1BUTTONDOWN 和 MM_JOY1BUTTONUP。这里有两点需要注意:

  1. MM_JOY1BUTTONDOWN 和 MM_JOY1BUTTONUP 消息只能对应游戏手柄的前四个按键,其他的按键按下是不会发消息的。但是如果按着其他的按键的同时也按下了这四个按键之一,那么我们是可以读出哪些按键被一起按下了的。
  2. 在我的电脑上所有的消息都会连着发两遍。不知道在其他的电脑上会怎么样。

下面简单介绍一下这三个消息:

MM_JOY1MOVE:对应的是摇杆的位置发生改变。

    fwButtons = wParam; xPos = LOWORD(lParam); yPos = HIWORD(lParam); 

其中 fwButtons 可以为 JOY_BUTTON1、JOY_BUTTON2、JOY_BUTTON3、JOY_BUTTON4 这四个的组合。表示在发出这个消息时这些按键是被按下的。xPos 和 yPos 就是当前的位置。我用的手柄比较初级,位置只能是 0、32767 和 65535,分别对应负位置、零位和正位置。

MM_JOY1BUTTONDOWN 对应有按键按下。

    fwButtons = wParam; xPos = LOWORD(lParam); yPos = HIWORD(lParam); 

其中 fwButtons 可以为 JOY_BUTTON1CHG 、JOY_BUTTON2CHG、JOY_BUTTON3CHG、JOY_BUTTON4CHG ,也可以是 JOY_BUTTON1、JOY_BUTTON2、JOY_BUTTON3、JOY_BUTTON4。xPos 和 yPos 就是当前的位置。

MM_JOY1BUTTONUP 表示一个按键被释放了。

    fwButtons = wParam; xPos = LOWORD(lParam); yPos = HIWORD(lParam); 

其中 fwButtons 可以为 JOY_BUTTON1CHG 、JOY_BUTTON2CHG、JOY_BUTTON3CHG、JOY_BUTTON4CHG ,也可以是 JOY_BUTTON1、JOY_BUTTON2、JOY_BUTTON3、JOY_BUTTON4。xPos 和 yPos 就是当前的位置。

知道这些就可以编程控制游戏手柄了。由于按下游戏手柄的其他按键时不会产生消息,所以用起来不是很方便。所以建议不用这种消息机制。而是自己在程序中建立一个独立的线程,这个线程轮询游戏手柄的状态,根据自己的需要,发送各种自定义消息。

游戏手柄(JoyStick)编程学习笔记(1)相关推荐

  1. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记--async和await(一) 接上文 多线程编程学习笔记--async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  2. 多线程编程学习笔记——任务并行库(二)

    接上文 多线程编程学习笔记--任务并行库(一) 三.   组合任务 本示例是学习如何设置相互依赖的任务.我们学习如何创建一个任务的子任务,这个子任务必须在父任务执行结束之后,再执行. 1,示例代码如下 ...

  3. 多线程编程学习笔记——任务并行库(三)

    接上文 多线程编程学习笔记--任务并行库(一) 接上文 多线程编程学习笔记--任务并行库(二) 六.   实现取消选项 本示例学习如何实现基于Task的异步操作进行取消流程,以及在任务真正运行前如何知 ...

  4. Linux与C++11多线程编程(学习笔记)

    多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...

  5. Cocoa编程学习笔记一

    Cocoa编程学习笔记一 一.Cocoa的起源 Mac OS X的窗口服务器与UNIX中的X窗口服务器具有相同的功能:从用户那里接受事件,并将时间转发给应用程序,将应用程序发过来的数据显示在屏幕上.N ...

  6. 多线程编程学习笔记——使用并发集合(三)

    接上文 多线程编程学习笔记--使用并发集合(一) 接上文 多线程编程学习笔记--使用并发集合(二) 四.   使用ConcurrentBag创建一个可扩展的爬虫 本示例在多个独立的即可生产任务又可消费 ...

  7. Java 8 函数式编程学习笔记

    Java 8 函数式编程学习笔记 @(JAVASE)[java8, 函数式编程, lambda] Java 8 函数式编程学习笔记 参考内容 Java 8中重要的函数接口 扩展函数接口 常用的流操作 ...

  8. java 网络编程学习笔记

    java 网络编程学习笔记 C/S模式:客户端和服务器 客户端创建流程 1 1.建立Socket端点 2 3 Socket s = new Socket(绑定地址, 绑定端口); 2.确认源数据方式和 ...

  9. 多线程编程学习笔记——线程池(二)

    接上文 多线程编程学习笔记--线程池(一) 三.线程池与并行度 此示例是学习如何应用线程池实现大量的操作,及与创建大量线程进行工作的区别. 1. 代码如下 using System; using Sy ...

  10. WCF服务编程 学习笔记(1)

    你或许可以使用某一技术实现某些功能,可以按着指定的要求,完成特定的功能,实现某一想要的效果,这表示你可以使用该技术,会使用该技术,但是我们不能停留在使用的层次上,还要了解它们的运行机制,可能有点深了, ...

最新文章

  1. 7个最佳的学习Python编程的开源库
  2. Unity技术专家:游戏最重要的是好玩而不是高画质...
  3. ASP.NET Core 中间件(Middleware)详解
  4. calender获取日期前几月_java获取当前时间和前一天日期(实现代码)
  5. LeetCode--26. 删除排序数组中的重复项(双指针,暴力)
  6. Mac使用ssh公钥免密登录服务器
  7. sed系列:行或者模式匹配删除特定行
  8. FATFS Copy Files
  9. 4.4使用@Enable*注释去切换配置
  10. NOI.AC NOIP模拟赛 第四场 补记
  11. 机器学习数据预处理之字符串转数字
  12. 复现贪吃蛇程序——吃食物增加长度(最后一篇)
  13. php分解质因数,JavaScript趣题:分解质因数
  14. mysql数据库设计教材_mySQL教程 第1章 数据库设计
  15. 我想和这个世界说说2
  16. burntest Linux参数,限拷机软件IntelBurnTest 2.0
  17. Java程序中如何判断一个数是否为素数
  18. python 画图marker标记汇总(matplotlib.pyplot)
  19. R语言广义加性模型GAMs分析温度、臭氧环境数据绘制偏回归图与偏残差图
  20. 开闭原则 by Robert Martin

热门文章

  1. 北京易思汇商务服务有限公司 留学缴费
  2. python编程考试_Python编程练习(一)
  3. 默写人体的方法有哪些?如何默写好画人体?
  4. DDR中的一些知识点说明(ODT,ZQ校准,OCT,TDQS)【转载】
  5. 【win10】win10值不值得升级?
  6. 深度信念网络(Deep Belief Network)
  7. ETOKEN 身份认证 电子证书
  8. java对excel加密_随笔:Java 对Excel等文件进行加密、解密
  9. 郑捷《机器学习算法原理与编程实践》学习笔记(第二章 中文文本分类(一))...
  10. 新手教学,如何快速地画一个PCB板子