DirectInput手柄Windows环境下震动实现
- 背景
- 1.direcrInput手柄震动控制
- 1.1运行环境
- 1.2代码实现
- 2 模拟XInput设备,通过XInput实现
- 2.1 x360ce设置
- 2.2 XInput 代码实现
- 2.3 x360ce分析
背景
近期项目里面有个需求,需要在控制终端上安装摇杆,且需要通过震动反馈设备的某些重要运行状态。自以为机智的自己在万能的某宝买了一个飞行摇杆,带震动反馈,DInput接口,结果是踩坑的开始。
Windows下手柄分为两类,XInput和DirectInput,XInput主要是Xbox系列手柄,比较贵,亲儿子;市面上大多杂牌的手柄都只支持DirectInput。查阅官方文档:xinput and directinput 心凉了半截:The vibration effects will not be available,意思就是directinput不再支持手柄震动了。不过directInput有一个directInputEffect,从文档看是力反馈,论坛说也能拿来做震动,因此先用directInput做一下尝试。
1.direcrInput手柄震动控制
1.1运行环境
环境搭建就不再赘述,主要就是选一个win10的tool kit,已经集成了directx。Qt是项目中用于界面搭建的环境。
(1)windows 10 1903
(2)Qt 5.9
1.2代码实现
github 上有比较完整的 QGameController,能够实现手柄的识别、各参数的读取,封装比较完整,但是没有手柄震动的驱动。本着不重复造轮子的原则,从这个开源项目开始进行代码实现。
DWORD dwAxisX = DIJOFS_X; //一个震动电机LONG lDirecX = 0;DIPERIODIC diPeriodic; ZeroMemory(&diPeriodic, sizeof(DIPERIODIC));DICONSTANTFORCE diConstantForce;ZeroMemory(&diConstantForce, sizeof(DICONSTANTFORCE));DIEFFECT diEffect; // general parameters// set up the effect structure itselfdiEffect.dwSize = sizeof(DIEFFECT);diEffect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;diEffect.dwDuration = (DWORD) INFINITE; diEffect.dwStartDelay = 0;// set up details of effectdiEffect.dwSamplePeriod = 0; diEffect.dwGain = lVibraStrength; diEffect.dwTriggerButton = DIEB_NOTRIGGER;// connect effect to trigger buttondiEffect.dwTriggerRepeatInterval = 0;diEffect.cAxes = 1;diEffect.rgdwAxes = &dwAxisX;diEffect.rglDirection = &lDirecX;diEffect.lpEnvelope = 0;diConstantForce.lMagnitude = DI_FFNOMINALMAX;diEffect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);diEffect.lpvTypeSpecificParams = &diConstantForce;// create the effect and get the interface to ithr = g_pJoystick->CreateEffect(GUID_ConstantForce, // standard GUID&diEffect, // where the data is&lpdieffect, // where to put interface pointerNULL); // no aggregationif(FAILED(hr)){qDebug()<< "create effect failed";}g_pJoystick->SendForceFeedbackCommand(DISFFC_RESET);//g_pJoystick->SendForceFeedbackCommand(DISFFC_SETACTUATORSON);if( FAILED(g_pJoystick->SendForceFeedbackCommand(DISFFC_SETACTUATORSON)))qDebug() << "start failed";hr = lpdieffect->Download();if(FAILED(hr))qDebug("download failed, the error NO is %X",hr);hr = lpdieffect->Start(INFINITE,DIES_SOLO);if(FAILED(hr))qDebug("start failed, the error NO is %X",hr);
代码有些冗长,参照了策随心和code从业员两位大佬的一些参数设置,虽然用的语言不一样,但是道理都是一样的。
代码运行并没有那么顺利,手柄在download和start以后,并没有反应,整个世界都是清净的。查找错误代码是0x80040205 DIERR_NOTEXCLUSIVEACQUIRED,注释是:The operation cannot be performed unless the device is acquired in DISCL_EXCLUSIVE mode。意思是这个操作在独占模式下才可以实现。因此我在create前面加了:
if( FAILED(hr = g_pJoystick->SetCooperativeLevel(windID,DISCL_BACKGROUND|DISCL_EXCLUSIVE)))qDebug("error set coopreative, error NO is %X",hr);
运行,世界还是一片寂静。这个bug直到现在都没调好,有大佬知道为啥,请告诉我!
2 模拟XInput设备,通过XInput实现
deadline越来越近,不能在一条路上撞死。逛论坛找大神的时候,发现DInput的设备可以通过软件模拟,变成XInput设备。在微软提供的XInput库中,有函数直接可以实现vibration。尝试了多个软件以后,找到了开源的x360ce,程序和源代码都能在官网上找到。
2.1 x360ce设置
在完成相关设置后,可以一键autosetting。上面一排标签可以看到Force Feedback,在这个选项卡中,拖动test的进度条后,手柄震动起来了,第一步完成。
2.2 XInput 代码实现
在qt中搭一个简单界面,按键做震动开关。把下面的代码块放到按键的槽函数中。
XINPUT_VIBRATION vibration;ZeroMemory( &vibration, sizeof(XINPUT_VIBRATION) );vibration.wLeftMotorSpeed = LeftMotorSpeed; vibration.wRightMotorSpeed = RightMotorSpeed; XInputSetState( uID, &vibration );
把x360ce中生成的xinput1_3.dll放到程序的运行目录下面,点击运行,动了!果然是亲儿子,这么小的代码量就能解决上面一片的所有问题。
2.3 x360ce分析
看了一下开源的代码,这个软件主要分为两大部分,一部分是生成dll文件,C++;一部分是界面,C#。
x360ce_dll工程里,代码量不大,主要是通过DirectInput控制手柄,再给XInput提供接口。震动的实时是调用XInputSetState,而不是directInputEffect.start,所以没有出现上面的独占问题。