背景
使用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文件夾下就可以,gamepads和platforms同級,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有事件衝突