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;
}
上述代码实现的效果是在命令行窗口中循环读取游戏手柄设备输入 ,并将读取到到数据打印出来。如果有游戏手柄插入,并按下对应的按键,则对应的打印数据就会发生变化。
测试时用的是我之前买的一个老旧的杂牌游戏手柄。在测试代码是否有效时遇到的非常多的问题:
- 首先,不管我插不插游戏手柄,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)