概述
最近突然對如何程式設計讀取遊戲搖桿輸入比較感興趣。是以上網找了找相關的資料,發現沒有什麼簡單明了的教程,是以在此将收集到跟joystick遊戲杆程式設計相關資料整理一下,友善大家參考使用。
JoyStick簡介
先給出JoyStick的維基百科介紹 維基百科詞條:JoyStick
按照維基百科中的介紹,JoyStick事實上就是電子輸入裝置,可以輸入按鍵,方向等資料。可以用來控制電子遊戲,也可以用來控制飛行器、汽車或者其他裝置等。事實上,一般的JoyStick遊戲搖桿或者搖杆的輸入都有遵循的标準,這樣我們就可以使用統一的joystick接口來讀取對應的輸入資料。
在我的認識中,至少我們玩飛行模拟遊戲所使用的飛行搖杆,以及我們玩PS4/Xbox等遊戲所使用的USB遊戲搖桿都屬于joystick裝置的。我們可以使用統一的程式設計接口來讀取輸入。
本文的目标
本文的目的是簡單介紹遊戲搖桿輸入的讀取方法,并給出一些簡單快捷的joystick程式設計方法。
JoyStick程式設計方法
1. 基于底層操作直接操作遊戲搖桿
有一篇文章Windows下對遊戲杆程式設計也列出列了幾個遊戲杆的使用方法,其中第一個和第二個就是通過驅動開發接口DDK或者通過讀取USB裝置直接通路。隻能說這樣做的難度不小,而且未必能夠達到我們想要的目标。喜歡探索或者程式設計大觸可以嘗試一下。
2. 使用Windows API
在Windows系統下,使用VS寫讀取joystick的C/C++代碼是非常容易的,此處給出一篇參考文章:JoyStick程式設計學習筆記。
給出一段測試讀取遊戲搖桿輸入的代碼如下:
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#include<Windows.h>
//添加joystick操作api的支援庫
#include<MMSystem.h>
#pragma comment(lib, "Winmm.lib")
int main(int argc, char* argv[])
{
JOYINFO joyinfo;//定義joystick資訊結構體
JOYINFOEX joyinfoex;
joyinfoex.dwSize = sizeof(JOYINFOEX);
joyinfoex.dwFlags = JOY_RETURNALL;
while(1)
{
//讀取搖桿資訊
UINT joyNums;
joyNums = joyGetNumDevs();
// printf("目前搖桿數量:%d \n",joyNums);
if (joyNums>=1)
{
MMRESULT joyreturn = joyGetPosEx(JOYSTICKID1, &joyinfoex);
if(joyreturn == JOYERR_NOERROR)
{
printf("0x%09d ", joyinfoex.dwXpos);
printf("0x%09d ", joyinfoex.dwYpos);
//printf("0x%09X ", joyinfoex.dwZpos);
//printf("0x%09X ", joyinfoex.dwPOV);
//printf("0x%09X ", joyinfoex.dwButtons);
printf("\n");
}else
{
switch(joyreturn)
{
case JOYERR_PARMS :
printf("bad parameters\n");
break;
case JOYERR_NOCANDO :
printf("request not completed\n");
break;
case JOYERR_UNPLUGGED :
printf("joystick is unplugged\n");
break;
default:
printf("未知錯誤\n");
break;
}
}
}
if(kbhit()) break;
Sleep(300);
}
return 0;
}
上述代碼實作的效果是在指令行視窗中循環讀取遊戲搖桿裝置輸入 ,并将讀取到到資料列印出來。如果有遊戲搖桿插入,并按下對應的按鍵,則對應的列印資料就會發生變化。
測試時用的是我之前買的一個老舊的雜牌遊戲搖桿。在測試代碼是否有效時遇到的非常多的問題:
- 首先,不管我插不插遊戲搖桿,joyGetNumDevs的傳回值始終是16,後來查了些資料:在C++ Builder中使用遊戲操縱杆說如果電腦有遊戲端口,那麼joyGetNumDevs 傳回值通常為16。。。但是也沒告訴我怎樣判斷遊戲搖桿是不是插入了,是以隻能通過joyGetPosEx的傳回值來進行判斷了。
- 第二個問題是使用上述代碼編譯出來的exe運作時,在不同的電腦和系統上讀取遊戲搖桿輸入的效果時靈時不靈。測試環境就是win10,win7和xp,分别測試了使用vc6.0以及vs2010編譯運作,總之測試結果達不到編譯一個exe然後到其他平台使用的效果。
3. 使用Directlnput或者XInput技術
DirectInput是微軟提供的一個輸入裝置的API,用于結合鍵盤、滑鼠、搖杆,或其它的遊戲控制器。如果是想要在Windows平台下使用搖杆的,可以參考Directlnput和XInput這兩篇文章。
如果是遊戲開發,可能對操縱杆或者輸入裝置的操作比較複雜,而且對相容性要求較高,而DirectInput和XInput提供的接口比較全面,而且和direct X的技術結合緊密。是以這個技術應該是開發Windows平台遊戲的不二選擇了。
4. 使用joystick接口庫
以前曾經用過一個windwos平台上的JoyStick庫,使用這個庫操作joystick很是友善。可是忘記了這個庫叫什麼。不過我在github上找了找,還真找到了一些joystick庫,先給出兩個結果:
- SDL-mirror/SDL
- Tasssadar/libenjoy
SDL全稱是Simple DirectMedia Layer,是一個很全面的跨平台媒體/遊戲開發庫,但是我沒精力折騰這些,是以轉向了libenjoy。這是一個簡單的JoyStick操作接口庫,使用C語言實作,可以與任何C/C++應用程式一起使用,而且是跨平台的,可以說非常友善。在此給出libenjoy工程中的測試例程和libenjoy庫的源代碼與測試工程:
//測試例程
#include <stdio.h>
#ifdef __linux
#include <unistd.h>
#else
#include <windows.h>
#endif
//包含libenjoy庫的頭檔案
#include "../src/libenjoy.h"
// This tels msvc to link agains winmm.lib. Pretty nasty though.
// 導入winmm.lib庫
#pragma comment(lib, "winmm.lib")
int main()
{
// libenjoy初始化
libenjoy_context *ctx = libenjoy_init(); // initialize the library
libenjoy_joy_info_list *info;
// Updates internal list of joysticks. If you want auto-reconnect
// after re-plugging the joystick, you should call this every 1s or so
// 更新joystick可用清單,如果想要實作熱插拔效果,則需要每隔1秒調用一次這個函數
libenjoy_enumerate(ctx);
// get list with available joysticks. structs are
// 獲得joystick可用的清單
// typedef struct libenjoy_joy_info_list {
// uint32_t count;
// libenjoy_joy_info **list;
// } libenjoy_joy_info_list;
//
// typedef struct libenjoy_joy_info {
// char *name;
// uint32_t id;
// char opened;
// } libenjoy_joy_info;
//
// id is not linear (eg. you should not use vector or array),
// and if you disconnect joystick and then plug it in again,
// it should have the same ID
info = libenjoy_get_info_list(ctx);
if(info->count != 0) // just get the first joystick
{
libenjoy_joystick *joy;
printf("Opening joystick %s...", info->list[0]->name);
joy = libenjoy_open_joystick(ctx, info->list[0]->id);//獲得第一個遊戲杆的資訊
if(joy)
{
int counter = 0;
libenjoy_event ev;
printf("Success!\n");
printf("Axes: %d btns: %d\n", libenjoy_get_axes_num(joy),libenjoy_get_buttons_num(joy));
while(1)
{
// Value data are not stored in library. if you want to use
// them, you have to store them
// That's right, only polling available
// 調用libenjoy_poll函數監聽joystick按鍵事件
while(libenjoy_poll(ctx, &ev))
{
switch(ev.type)
{
case LIBENJOY_EV_AXIS:
printf("%u: axis %d val %d\n", ev.joy_id, ev.part_id, ev.data);
break;
case LIBENJOY_EV_BUTTON:
printf("%u: button %d val %d\n", ev.joy_id, ev.part_id, ev.data);
break;
case LIBENJOY_EV_CONNECTED:
printf("%u: status changed: %d\n", ev.joy_id, ev.data);
break;
}
}
#ifdef __linux
usleep(50000);
#else
Sleep(50);
#endif
counter += 50;
// 如果joystick被拔出了,則每隔1秒調用一次libenjoy_enumerate函數
// 來監控joystick的連接配接狀态
if(counter >= 1000)
{
libenjoy_enumerate(ctx);
counter = 0;
}
}
// Joystick is really closed in libenjoy_poll or libenjoy_close,
// because closing it while libenjoy_poll is in process in another thread
// could cause crash. Be sure to call libenjoy_poll(ctx, NULL); (yes,
// you can use NULL as event) if you will not poll nor libenjoy_close
// anytime soon.
// 關閉joystick庫
libenjoy_close_joystick(joy);
}
else
printf("Failed!\n");
}
else
printf("No joystick available\n");
// Frees memory allocated by that joystick list. Do not forget it!
// 清除記憶體
libenjoy_free_info_list(info);
// deallocates all memory used by lib. Do not forget this!
// libenjoy_poll must not be called during or after this call
// 關閉libenjoy庫,在關閉之後,就不可以再調用libenjoy_poll函數了
libenjoy_close(ctx);
return 0;
}
總結
本文介紹了joystick遊戲杆程式設計的基本概念,并給出了幾種讀取joystick遊戲杆輸入的方法。這幾種方法種,我最青睐的還是第四種使用joystick接口庫。雖然使用Windows平台微軟提供的庫進行joystick程式設計也很友善,但是我在使用時還是遇到了許多相容性問題。使用joystick接口庫則是拿來了一個造好并且調試好的輪子,直接使用很友善。
為了更加友善大家使用,在此将libenjoy庫編譯成為了靜态庫和動态庫:libenjoy動态連結庫(win32vc6.0)。Linux版的暫時沒有需求,是以就沒有做,有需要的可以自己來。
引用:
- 維基百科詞條:JoyStick
- Windows下對遊戲杆程式設計
- JoyStick程式設計學習筆記
- 在C++ Builder中使用遊戲操縱杆
- Directlnput
- XInput
- SDL-mirror/SDL
- Tasssadar/libenjoy
資源:
- libenjoy_master源碼和測試工程
- libenjoy動态連結庫(win32vc6.0)