天天看點

遊戲搖桿(JoyStick)程式設計學習筆記(1)遊戲搖桿(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()) == ) 
        return ERR_NODRIVER; 
    bDev1Attached = joyGetPos(JOYSTICKID1, &joyinfo) != JOYERR_UNPLUGGED; 
    bDev2Attached = wNumDevs ==  && 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 > )
    {
        joySetThreshold(JOYSTICKID1, );
        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 就是目前的位置。

知道這些就可以程式設計控制遊戲搖桿了。由于按下遊戲搖桿的其他按鍵時不會産生消息,是以用起來不是很友善。是以建議不用這種消息機制。而是自己在程式中建立一個獨立的線程,這個線程輪詢遊戲搖桿的狀态,根據自己的需要,發送各種自定義消息。