天天看點

JoyStick遊戲杆程式設計實踐

概述

最近突然對如何程式設計讀取遊戲搖桿輸入比較感興趣。是以上網找了找相關的資料,發現沒有什麼簡單明了的教程,是以在此将收集到跟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;
}
           

上述代碼實作的效果是在指令行視窗中循環讀取遊戲搖桿裝置輸入 ,并将讀取到到資料列印出來。如果有遊戲搖桿插入,并按下對應的按鍵,則對應的列印資料就會發生變化。

測試時用的是我之前買的一個老舊的雜牌遊戲搖桿。在測試代碼是否有效時遇到的非常多的問題:

  1. 首先,不管我插不插遊戲搖桿,joyGetNumDevs的傳回值始終是16,後來查了些資料:在C++ Builder中使用遊戲操縱杆說如果電腦有遊戲端口,那麼joyGetNumDevs 傳回值通常為16。。。但是也沒告訴我怎樣判斷遊戲搖桿是不是插入了,是以隻能通過joyGetPosEx的傳回值來進行判斷了。
  2. 第二個問題是使用上述代碼編譯出來的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庫,先給出兩個結果:

  1. SDL-mirror/SDL
  2. 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版的暫時沒有需求,是以就沒有做,有需要的可以自己來。

引用:

  1. 維基百科詞條:JoyStick
  2. Windows下對遊戲杆程式設計
  3. JoyStick程式設計學習筆記
  4. 在C++ Builder中使用遊戲操縱杆
  5. Directlnput
  6. XInput
  7. SDL-mirror/SDL
  8. Tasssadar/libenjoy

資源:

  1. libenjoy_master源碼和測試工程
  2. libenjoy動态連結庫(win32vc6.0)