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平台下使用摇杆的,可以参考DirectlnputXInput这两篇文章。
如果是游戏开发,可能对操纵杆或者输入设备的操作比较复杂,而且对兼容性要求较高,而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)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值