博客 / 詳情

返回

Qt5支持手柄

背景

使用Qt5的Gamepad模塊支持手柄UI顯示和操作。Qt使用的版本是5.15.8。採用的是SDL支持的插件。因為想要支持PS5手柄,但是使用默認的xinputgamepad.dll好像對PS5手柄的支持不太好。

編譯和構建

SDL2選用github上官方SDL2項目的最新release包即可

  • 需要工作在VS命令行工具。
  • 需要Qt源碼

找到qtsoucecodepath\qtgamepad\src\plugins\gamepads\sdl2目錄,該目錄下有對應的源碼

nmake.exe" -f Makefile.Debug all
nmake 
nmake install

Release也行,改一下就好
產物在plugins\gamepads目錄下

使用

使用時也放在可執行文件目錄的plugins\gamepads文件夾下就可以,gamepadsplatforms同級,SDL2庫文件放在可執行文件目錄就好。

如果gamepads目錄下有xinputgamepad.dll文件可能會優先選擇,但是如果gamepads目錄下只有sdl2gamepad.dll的話,就只能選擇sdl支持了。

踩坑

Qt5 Gamepad模塊無法拿到手柄的詳細信息,比如手柄的類型(可以通過vendor id做簡單類型區分,也可以通過product id做具體手柄區分)。業務場景是要求區分各個廠家的手柄並做對應的按鍵UI。所以僅靠Qt5 Gamepad是不夠的。所以還需要依靠SDL的能力,要將Gamepad和SDL的GameController連接起來。(原來我以為Qt Gamepad的device id和SDL GameController intex不是同一個值,採用了一種特別彆扭的映射方法)

通過閲讀SDL插件和Qt Gamepad部分的代碼,發現其實SDL GameController的index其實是通過信號傳遞給了Qt5的Gamepad.

void QSdlGamepadBackend::addController(int index)
{
    char GUID[100];
    SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(index), GUID, 100);
    if (!SDL_IsGameController(index))
        return;

    SDL_GameController *controller = SDL_GameControllerOpen(index);
    if (controller) {
        m_indexForController.insert(index, controller);

        SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);

        int instanceID = SDL_JoystickInstanceID(joystick);
        m_instanceIdForIndex.insert(instanceID, index);

        const char *name = SDL_JoystickName(joystick);

        //qDebug() << "Controller " << index << " added with instanceId: " << instanceID;
        emit gamepadAdded(index);

        if (name)
            emit gamepadNamed(index, QString::fromUtf8(name));
    }
}

gamepadAdded信號把index丟給了Qt上層
Qt則是用這個index當做devciceId了,那説明Qt Gamepad和SDL GameController其實是可以通過這個deviceId鏈接起來的,那麼問題就簡單多了。

void QGamepadManagerPrivate::_q_forwardGamepadConnected(int deviceId)
{
    Q_Q(QGamepadManager);
    connectedGamepads.insert(deviceId, QString());
    emit q->gamepadConnected(deviceId);
    emit q->connectedGamepadsChanged();
}

只需要維護一個Qt Gamepad到手柄類型的Map映射即可,當Qt的QGamepadManager收到connectedGamepadsChanged信號時,構造一個QGamepad對象,然後通過Gamepad的deviceId去SDL拿到vendor id。

    QGamepadManager* manager = QGamepadManager::instance();
    QList<int> gamepadList = manager->connectedGamepads();
    SPDLOG_INFO("Connected gamepad count: {}", gamepadList.size());
    
    for (const auto& device_id : gamepadList) {
        SPDLOG_INFO("Connected gamepad deviceId: {}", device_id);
        QGamepad* gamepad = new QGamepad(device_id, this);
        if (gamepad != nullptr) {
            Uint16 vendor = SDL_JoystickGetDeviceVendor(device_id);
            // handle gamepad reflect
        } else {
            continue;
        }
    }

關於Qt6

Gamepad好像目前還沒有移植到Qt6,需要找一下其他的替代方案了,可以考慮直接引入SDL,但是不知道會不會和Qt有事件衝突

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.