Gestion du système d'entrée du clavier

13

Remarque: je dois interroger plutôt que de faire des rappels en raison des limitations de l'API (SFML). Je m'excuse également pour l'absence d'un titre «décent».

Je pense que j'ai deux questions ici; comment enregistrer l'entrée que je reçois et quoi en faire.

Gestion de l'entrée

Je parle après le fait que vous avez enregistré que la touche «A» a été enfoncée, par exemple, et comment le faire à partir de là.

J'ai vu un tableau de l'ensemble du clavier, quelque chose comme:

bool keyboard[256]; //And each input loop check the state of every key on the keyboard

Mais cela semble inefficace. Non seulement vous couplez la clé «A» au «joueur se déplaçant vers la gauche», par exemple, mais il vérifie chaque clé, 30 à 60 fois par seconde.

J'ai ensuite essayé un autre système qui cherchait juste les clés qu'il voulait.

std::map< unsigned char, Key> keyMap; //Key stores the keycode, and whether it's been pressed. Then, I declare a load of const unsigned char called 'Quit' or 'PlayerLeft'.
input->BindKey(Keys::PlayerLeft, KeyCode::A); //so now you can check if PlayerLeft, rather than if A.

Cependant, le problème avec ceci est que je ne peux pas maintenant taper un nom, par exemple, sans avoir à lier chaque clé.

Ensuite, j'ai le deuxième problème, que je ne peux pas vraiment penser à une bonne solution pour:

Envoi d'entrée

Je sais maintenant que la touche A a été enfoncée ou que playerLeft est vrai. Mais comment je vais partir d'ici?

J'ai pensé à vérifier simplement que if(input->IsKeyDown(Key::PlayerLeft) { player.MoveLeft(); }
ceci couple grandement l'entrée aux entités, et je la trouve plutôt désordonnée. Je préférerais que le joueur gère son propre mouvement lorsqu'il est mis à jour. Je pensais qu'une sorte de système d'événements pourrait fonctionner, mais je ne sais pas comment y aller. (J'ai entendu que les signaux et les créneaux horaires étaient bons pour ce genre de travail, mais c'est apparemment très lent et je ne vois pas comment cela irait).

Merci.

Le canard communiste
la source

Réponses:

7

Ce que je ferais, c'est utiliser le modèle d'observateur et avoir une classe d'entrée qui conserve une liste de rappels ou d'objets de gestion des entrées. D'autres objets peuvent s'enregistrer auprès du système d'entrée pour être avertis lorsque certaines choses se produisent. Vous pouvez enregistrer différents types de rappels, en fonction du type d'événements d'entrée que les observateurs souhaitent recevoir. Cela peut être aussi bas que «la touche x est en bas / en haut» ou plus spécifique au jeu comme «un événement de saut / tir / mouvement s'est produit».

Généralement, les objets de jeu ont un composant de gestion d'entrée qui est responsable de s'inscrire auprès de la classe d'entrée et de fournir des méthodes pour gérer les événements qui l'intéressent. Pour le mouvement, j'ai un composant de moteur d'entrée qui a une méthode de déplacement (x, y). Il s'enregistre auprès du système d'entrée et est notifié des événements de mouvement (où x et y seraient -1 et 0 pour signifier un déplacement vers la gauche), le composant de déplacement modifie ensuite ses coordonnées d'objet de jeu parent en fonction de la direction du mouvement et de la vitesse de l'objet.

Je ne sais pas si c'est une bonne solution mais cela fonctionne pour moi jusqu'à présent. En particulier, cela me permet de fournir différents types de système d'entrée qui correspondent aux mêmes événements de jeu logiques afin que le bouton de la manette de jeu x et l'espace du clavier génèrent tous deux un événement de saut par exemple (c'est un peu plus compliqué que cela, permettant à l'utilisateur de réaffecter la clé mappages).

Firas Assaad
la source
Cela m'intéresse définitivement; le composant d'entrée s'enregistrerait-il pour certains événements (quelque chose comme playerInput s'inscrit-il à gauche, à droite, etc.) ou est-ce que chaque composant d'entrée enregistré recevrait tous les messages?
The Communist Duck
Cela dépend vraiment de vos besoins et de la façon dont vous souhaitez les mettre en œuvre. Dans mon cas, les écouteurs s'enregistrent pour des événements spécifiques et implémentent l'interface de gestionnaire d'entrée appropriée. Faire en sorte que chaque composant d'entrée reçoive tous les messages devrait fonctionner, mais le format du message doit être plus générique que ce que j'ai.
Firas Assaad du
6

Je pense que la discussion du PO rassemble un tas de choses ensemble, et que le problème peut être considérablement simplifié en le décomposant un peu.

Ma suggestion:

Conservez votre tableau d'états de clés. N'y a-t-il pas un appel d'API pour obtenir l'état du clavier entier à la fois? (La plupart de mon travail se fait sur des consoles, et même sur Windows pour un contrôleur connecté, vous obtenez tout l'état du contrôleur à la fois.) Vos états de touches sont une carte "dure" sur les touches du clavier. Non traduit est le meilleur, mais vous obtenez le plus facilement les API.

Gardez ensuite quelques tableaux parallèles qui indiquent si la clé a remonté cette trame, un autre indiquant qu'elle est descendue et un troisième pour indiquer simplement que la clé est "baissée". Ajoutez peut-être une minuterie pour que vous puissiez savoir depuis combien de temps la clé est dans son état actuel.

Créez ensuite une méthode pour créer un mappage des clés des actions. Vous voudrez le faire plusieurs fois. Une fois pour les choses de l'interface utilisateur (et veillez à interroger les mappages Windows car vous ne pouvez pas supposer que le scancode lorsque vous appuyez sur 'A' va donner un A sur l'ordinateur de l'utilisateur). Un autre pour chaque "mode" du jeu. Ce sont les mappages des codes clés aux actions. Vous pouvez le faire de plusieurs façons, l'une serait de conserver une énumération utilisée par le système récepteur, une autre serait un pointeur de fonction indiquant la fonction à appeler lors d'un changement d'état. Peut-être même un hybride des deux, selon vos objectifs et vos besoins. Avec ces "modes", vous pouvez les pousser et les faire apparaître sur une pile de mode contrôleur, avec des drapeaux qui permettent aux entrées ignorées de continuer à descendre dans la pile ou autre.

Enfin, gérez ces actions clés d'une manière ou d'une autre. Pour le mouvement, vous voudrez peut-être faire quelque chose de délicat, traduisez «W» vers le bas pour signifier «aller de l'avant», mais cela n'a pas besoin d'être une solution binaire; vous pouvez avoir "W est en baisse" signifie "augmenter la vitesse de X jusqu'à un maximum de Y" et "W est en hausse" pour être "diminuer la vitesse de Z jusqu'à ce qu'il atteigne zéro." De manière générale, vous voulez que votre "interface de gestion des contrôleurs dans les systèmes de jeu" soit assez étroite; effectuez toutes les traductions clés en un seul endroit, puis utilisez les résultats partout ailleurs. Cela contraste avec la vérification directe pour voir si la barre d'espace est pressée n'importe où au hasard dans le code, car si elle se trouve à un endroit aléatoire, elle sera probablement frappée à un moment aléatoire lorsque vous ne le voulez pas, et vous ne le faites pas '' Je ne veux pas y faire face ...

Je déteste vraiment concevoir des modèles et je pense que les composants apportent plus de coûts de développement que de bien, c'est pourquoi je n'ai mentionné ni l'un ni l'autre. Des schémas émergeront s'ils sont destinés à, comme le feront les composants, mais le fait de faire l'une ou l'autre dès le départ ne fera que compliquer votre vie.

dash-tom-bang
la source
Ne pensez-vous pas que c'est un peu sale de lier des événements à des cadres graphiques? Je pense que cela devrait être séparé.
Jo So
Je veux dire, je comprends que dans presque tous les cas, le lecteur sera plus lent à entrer des boutons que la carte graphique émettra des images, mais en réalité, elles devraient être indépendantes, et en fait elles sont destinées à d'autres types d'événements, comme ceux collectés sur le réseau .
Jo So
En effet; il n'y a aucune raison pour que l'interrogation du clavier (ou d'un autre périphérique d'entrée) ne puisse pas être exécutée à une vitesse différente de celle de l'écran. En effet, si vous interrogez des entrées à 100 Hz, vous pouvez fournir une expérience de contrôle utilisateur vraiment cohérente quelle que soit la fréquence de mise à jour du matériel vidéo. Vous devrez être un peu plus intelligent sur la façon dont vous gérez vos données (concernant le verrouillage, etc.), mais cela rendra les choses comme les gestes beaucoup plus faciles à rendre cohérentes.
dash-tom-bang
3

Dans SFML, vous avez les touches dans l'ordre dans lequel elles ont été enfoncées avec le type d'événement KeyPressed. Vous traitez chaque clé dans un certain temps (getNextEvent ()).

Donc, si la touche enfoncée se trouve dans votre carte Key-> Action, exécutez l'action correspondante, et si ce n'est pas le cas, transférez-la à n'importe quel widget / élément qui pourrait en avoir besoin.

En ce qui concerne votre deuxième question, je vous recommande de le garder comme ça car il est plus facile de modifier votre gameplay. Si vous utilisez des signaux ou autres, vous devrez créer un emplacement pour le "RunKeyDown" et un autre pour le "JumpKeySlot". Mais que se passe-t-il si dans votre jeu, une action spéciale est exécutée lorsque les deux sont pressés?

Si vous souhaitez décorréler l'entrée et les entités, vous pouvez faire de votre entrée un état (RunKey = true, FireKey = false, ...) et l'envoyer au joueur comme vous envoyez tout autre événement à vos IA.

Calvin1602
la source
Je ne gère pas les événements clavier avec sf :: Event, mais plutôt sf :: Input, car il s'agit de la détection en temps réel IIRC. J'ai essayé de le garder aussi indépendant de l'API que possible. : P (La question, c'est-à-dire)
The Communist Duck
1
Mais je pense que l'argument de Calvin1602 est que votre affirmation originale dans la question "Je dois interroger, plutôt que de faire des rappels à cause des limitations de l'API (SFML)" n'est pas vraie; vous avez des événements, vous pouvez donc émettre un rappel lorsque vous gérez un événement.
Kylotan
3

La bonne solution est souvent une combinaison des deux méthodes dont vous discutez:

Pour la fonctionnalité de jeu avec un jeu de clés prédéfini, l'interrogation de chaque image est tout à fait appropriée et vous ne devez interroger que les clés qui sont réellement liées à quelque chose. Ceci est en fait analogue à la façon dont vous interrogeriez les entrées de contrôleur dans un jeu de console, cela va être entièrement polluant, en particulier en ce qui concerne les périphériques d'entrée analogiques. Pendant le jeu général, vous voudrez probablement ignorer les événements de pression de touche et utiliser uniquement l'interrogation.

Quand il s'agit de taper des entrées comme des noms, vous devez basculer le jeu dans un mode de gestion des entrées différent. Par exemple, dès qu'une invite "entrez votre nom" apparaît, vous pouvez modifier ce que fait votre code d'entrée. Il peut écouter les événements de pression de touche via une interface de rappel, ou vous pouvez commencer à interroger chaque touche si nécessaire. Il s'avère que généralement lors de la frappe d'événements, vous ne vous souciez pas autant des performances de toute façon, donc l'interrogation ne sera pas si mauvaise.

Par conséquent, vous souhaitez utiliser l'interrogation sélective pour l'entrée de gameplay et la gestion des événements pour les entrées de saisie de nom (ou toutes les interrogations au clavier si la gestion des événements n'est pas disponible).

Ben Zeigler
la source