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

游戏手柄(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 就是当前的位置。

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

你可能感兴趣的:(游戏手柄)