操作杆(包括不同厂商的操作杆、方向盘、阀门、手柄等)只要遵循主流的协议都可实现与用户应用程序的交互。其交互方式大致分为两种:

1.轮询式–DirectX读取

通过DirectX中的DirectInput识别操作杆外设,调用Windows系统自带的dinput8.libWinmm.lib亦可)库中的LPJOYINFO实现对操作杆状态的查询。具体实现示例如下:

#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>

#pragma comment(lib, "Winmm.lib")

int main()
{
    UINT DeviceNumbers = joyGetNumDevs();
    printf("%d\n", DeviceNumbers); // always be 16 ?

    while (true)
    {
        UINT JoystickID = 0;
        LPJOYINFO pJoystickInfo = new joyinfo_tag;
        joyGetPos(JoystickID, pJoystickInfo);

        printf("X: %d\n", pJoystickInfo->wXpos); // value from 0 to 65535 (2^6)
        printf("Z: %d\n", pJoystickInfo->wYpos);
        printf("Y: %d\n", pJoystickInfo->wZpos);
        printf("Buttons: %d\n", pJoystickInfo->wButtons);
        
        sleep(1000);
    }

    return 0;
}

每个操作杆设备轴的变化最多支持6个维度,按键最多支持32个键,基本能够覆盖全面了。

2.响应式–映射为键盘事件

TARGET GUI提供了图形化和脚本编写两种方式将操作杆设备的输入(可以区分Pulse、Hold、Press、Release)转换为不同的键盘事件(可以为复杂的组合键方式),随后在自己的应用程序中可以通过Qt响应键盘事件的方式获取操作杆传入信息,进而实现自己的应用场景。TARGET GUI同时还提供了事件测试器,方便测试定义的键盘事件键值和精度是否正确。实现示例如下:

bool JoyStickInputEventManager::Filter( QObject * receiver, QEvent * event  )
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);

        bool IsThrottleEvent = false;
        if (keyEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier))
        {
            IsThrottleEvent = true;
            if (keyEvent->key() == Qt::Key_1)
            {
                emit Power_On_RDRNRM(true);
            }
            else
            {
                IsThrottleEvent = false;
            }

            return IsThrottleEvent;
        }
        if (keyEvent->modifiers() == (Qt::ControlModifier | Qt::AltModifier))
        {
            IsThrottleEvent = true;
            if (keyEvent->key() == Qt::Key_Up)
            {
                emit Throttle_Control(1);
            }
            else if (keyEvent->key() == Qt::Key_Down)
            {
                emit Throttle_Control(-1);
            }
            else
            {
                IsThrottleEvent = false;
            }

            return IsThrottleEvent;
        }
    }

    return false;
}

推荐DirectX读取的方式,这也是游戏引擎主流的使用方式,其中应该考虑了读取速度和不与键盘操作冲突的场景,一般映射为键盘事件是为了接入已有的系统内,通过操作杆间接控制键盘。

因为我在这里遇到了一个大坑…

该项目用的是Thrustmaster的摇杆和阀门因为需要精细化的操作识别,所以通过TARGET GUI定义了很多组合键,其中包括CtrlAltShiftF*(可以从F1到F30,虽然普通键盘上没有列出这些功能键)。 Qt绘制过程中会出现paintEvent刷新不干净的bug,几经debug认为应该是操作杆定义的键盘事件过于复杂,可能和Qt保留键冲突导致Qt事件队列异常从而无法更新QWidget中绘制的图形,但是其他的绘制可以正常更新,只是会界面始终会保留一个没有刷新掉的图形。

后将TARGET GUI中定义的组合键去去掉,程序改为DirectInput轮询式读取后搞定。注:这种方式同样需要运行TARGET GUI,但无需编辑按键脚本。