Qt Gamepad – Implémentation Windows (première partie)

Un grand classique dans les questions des débutants Qt (qui veulent tous créer des jeux vidéos… je leur jette pas la pierre, je suis pareil :D) : comment utiliser un gamepad avec Qt. La réponse en général est qu’on ne peut pas. Il faut utiliser une bibliothèque externe pour cela, par exemple la SFML.

L’objectif va donc être d’ajouter ces fonctionnalités dans QWindow en utilisant le QPA (Qt Platform Abstraction), de créer un élément QML pour gérer le gamepad dans Qt Quick, de créer un élément graphique pour proposer un gamepad virtuel sur écran tactile (mobile, tablette). Je vais rédiger plusieurs articles :

  1. implémentation pour Windows (premier, deuxième et troisième parties)
  2. implémentation des éléments QML
  3. implémentation pour Linux
  4. implémentation pour Android (OUYA)
  5. implémentation de la version QPA
  6. performances

Introduction

Pour commencer, je dois avouer une chose : je n’avais jamais utiliser un gamepad en C++ avant de commencer cette série d’articles. Donc il me fallait une base de travail. Comme je suis actuellement sous Windows, je vais faire simple, je vais m’intéresser dans un premier temps à l’implémentation sur cette plateforme, on verra pour Linux et Android ensuite (je n’ai pas de Mac OS X, j’aurais besoin d’un coup de main pour le portage dessus). Donc la chose la plus simple à faire est de demander en Microsoft : MSDN – Dev Center – Multimedia Input – Joysticks.

Le second point important est comment implémenter cela dans Qt. Plusieurs approches sont possibles :

  • créer un QObject et utiliser les signaux et slots pour obtenir des informations sur le gamepad. Simple et efficace, je ferais mes tests avec cette approche. Cela permettra également aux utilisateurs des anciennes versions de Qt de pouvoir utiliser le gamepad.
  • ajouter la gestion du gamepad directement dans la boucle d’événements en utilisant le QPA. Il faudra donc créer une classe QGamepadEvent et ajouter la fonction gamepadUpdateEvent dans QWindow.
class QWindow { 
protected:
    virtual void gamepadUpdateEvent(QGamepadEvent* event);
};

Peut être faire comme pour la souris et créer plusieurs événements : gamepadPressEvent et gamepadReleaseEvent (pour les boutons du gamepad) et gamepadMoveEvent (pour le déplacement des sticks) ?

Gestion du gamepad sous Windows

Pour gérer les gamepad, il faut :

  • récupérer la liste des gamepad et leurs fonctionnalités (capacities) disponibles.
  • s’informer régulièrement des l’états des boutons.
  • ou ajouter des fonctions callback pour que le gamepad informe l’application lorsqu’il  a des mises à jour des valeurs.

Comme l’objectif est d’émettre un signal ou un événement lors d’une action de l’utilisateur sur le gamepad, les fonctions callback semblent être la meilleure approche a priori. Cependant, ces fonctions callback ne sont disponibles que pour les fonctions de base du gamepad (josGetPos). Et malheureusement, la gestion d’un gamepad complet (plusieurs sticks numériques, plusieurs boutons) n’est possible qu’avec les fonctions étendues (josGetPosEx).

Donc, il va falloir opérer différemment. Il faut créer un timer qui lit l’état du gamepad à interval régulier et le compare à l’état précédent. Si une valeur à changée, on émet un signal ou un event.

Détecter les gamepads connectés

Il est possible de gérer jusqu’à 16 gamepads en même temps avec les pilotes Windows. Il faut inclure le fichier d’en-tête window.h et la bibliothèque Winmm.dll. Chaque gamepad physique est identifié par un ID de type unsigned int. Il faut également gérer les gamepads virtuels, donc on va réserver les valeurs d’ID inférieur à 256 pour les gamepads physiques.

Pour connaître le nombre de gamepads physiques possibles, il faut utiliser la fonction joyGetNumDevs :

unsigned int maxGamepadId = joyGetNumDevs();

Pour tester si un gamepad est connecté, il faut utiliser la fonction joyGetPosEx avec l’identifiant en paramètre. Cette fonction retourne la valeur JOYERR_UNPLUGGED si le gamepad n’est pas connecté.

JOYINFOEX info;
for (unsigned int id = 0; id < maxGamepadId; ++id) {
    MMRESULT result = joyGetPosEx(id, &info);
    bool isConnected = result == JOYERR_NOERROR ;
    cout << "gamepad #" << id <<
        (isConnected ? " connected" : " not connected") << endl;
}

Pour commencer, on vas tester la connexion de tous les gamepads à chaque update du timer. A terme, il faudra tester le coût de ces tests en continue et voir s’il faut optimiser (faire un test des connexions moins souvent que le test des changements de valeur).

Récupérer les informations d’un gamepad

Les informations relatives au gamepad sont contenues dans le paramètre de type JOYINFOEX de la fonction joyGetPosEx. Il faut conserver cette information et la comparer à chaque update.

JOYINFOEX info, lastInfo;
if (info.dwXpos != lastInfo.dwXpos)
    cout << "X position modified";

Les différents sticks et boutons sont identifiés avec des lettres et des chiffres. Il n’est pas possible de nommer explicitement ces éléments puisqu’ils peuvent changer d’une manette à l’autre. L’image suivante présente la correspondance des identifiants sur une manette XBox. Je détaillerais ça dans un prochain article.

Liste des contrôles sur une manettes XBox.

Classes Qt de gestion de gamepad

Pour commencer, l’objectif sera d’ajouter un fonction gamepadUpdateEvent dans la classe QWindow. En attendant, l’utilisateur devra créer lui-même cette fonction, en surchargeant la fonction event pour récupérer les événements du gamepad.

Modifications dans la classe QWindow

Par exemple, pour créer cette fonction dans une classe MyWindow :

class MyWindow : public QWindow {
    Q_OBJECT
protected:
    bool event(QEvent* event) override;
    virtual bool gamepadUpdateEvent(GamepadEvent* event);
};

Dans la fonction event, il faut tester le type d’événement. Si celui-ci correspond à un événement du gamepad, on appelle la fonction gamepadUpdateEvent. Sinon on lance le traitement standard des événements en appelant la fonction QWindow::event.

bool GamepadWidget::event(QEvent* event)
{
    if (event->type() == static_cast<QEvent::Type>(
            GamepadEvent::GamepadUpdate))
        return gamepadUpdateEvent(static_cast<GamepadEvent*>(
            event));
    else
        return QWidget::event(event);
}

bool GamepadWidget::gamepadUpdateEvent(GamepadEvent* event)
{
    qDebug() << "receive gamepadUpdateEvent";
}

La classe GamepadEvent

La classe GamepadEvent dérive de QEvent et définie un nouveau type d’événement. La définition des fonctionnalités de base de cette classe est la suivante :

class GamepadEvent : public QEvent
{
public:
    static const QEvent::Type GamepadUpdate;

    explicit GamepadEvent(QEvent::Type type);
    virtual ~GamepadEvent();
};

Le type GamepadUpdate est un identifiant qui est initialisé avec la fonction statique registerEventType. Pour le constructeur, il faut passer le type d’événement en paramètre de QEvent.

const QEvent::Type GamepadEvent::GamepadUpdate = 
    static_cast<QEvent::Type>(QEvent::registerEventType());

GamepadEvent::GamepadEvent(QEvent::Type type) :
    QEvent(type)
{
}
GamepadEvent::~GamepadEvent()
{
}

Cette classe doit conserver les informations sur le gamepad. Suivant les règles de codage de Qt, ces informations seront dans une classe private en utilisant l’idiome Pimpl (pointeurs D et Q). Ces informations sont les suivantes :

unsigned int id() const;
float joystickX() const;
float joystickY() const;
float joystickZ() const;
float joystickR() const;
float joystickU() const;
float joystickV() const;
float joystickPov() const;
unsigned int pressedButtons() const;

Id correspond à l’identifiant du gamepad, les valeurs réelles sont comprise entre -1 et 1 et correspondent aux boutons analogiques du gamepad (sticks, gachettes), sauf la valeur POV (point-of-view) qui est comprise entre 0 et 360 degrés. Les boutons correspondante à une combinaison de valeurs qu’il faut tester avec l’opérateur binaire AND :

bool isButtonAPressed = 
    Gamepad::Button1 & event->pressedButtons();

La classe GamepadEventPrivate contient les valeurs qui sont lues par ces fonctions :

class GamepadEventPrivate
{
};

Le pointeur D est déclaré dans la classe GamepadEvent :

class GamepadEventPrivate; // déclaration anticipée
class GamepadEvent : public QEvent
{
private:
    class GamepadEventPrivate* d;
};

Ce pointeur doit être instancié dans le constructeur. Il pourra ensuite être utilisé par les fonctions de GamepadEvent.

GamepadEvent::GamepadEvent(QEvent::Type type) :
    QEvent(type)
{
    d = new GamepadEventPrivate;
}

GamepadEvent::~GamepadEvent()
{
    delete d;
}

Conclusion

J’aborderais la prochaine fois l’implémentation de la classe GamepadManager, qui permet de surveiller l’état des gamepads, détecter un changement d’état et émettre les événements si nécessaire.

N’hésitez pas si vous avez des remarques, en particulier sur l’API ou les fonctionnalités, n’hésitez pas à en parler sur le forum de QtFr.org.

Publicités

Un commentaire sur « Qt Gamepad – Implémentation Windows (première partie) »

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s