IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

XNA/Monogame Discussion :

Etat des touches enfoncé vérifié trop rapidement


Sujet :

XNA/Monogame

  1. #1
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2009
    Messages : 163
    Points : 62
    Points
    62
    Par défaut Etat des touches enfoncé vérifié trop rapidement
    Bonjour,

    J'ai dans mon application un petit tank qui possède un canon. Je voudrais faire en sorte que quand j'appuie sur la barre d'espace le canon tire un boulet. Chose que j'ai réussi à faire, j'ai mis dans la méthode update un test qui vérifie si la touche espace est "down". Si le test est vrai, un objet de type classe "Boulet" est crée, ajouté à une liste et est mis à jours (update,draw,..) quand on lui demande. Mon problème se trouve au moment où l'on appuie sur la barre d'espace. Quand je clique dessus, même de manière très rapide, il y a au moins 3 boulet qui parte d'un coup, la boucle passe très rapidement en moins d'une seconde. Or il faudrait pouvoir tirer un boulet toutes les 1 seconde par exemple.

    J'avait pensé, pour palier au problème, garder en mémoire le temps qui s'écoule entre chaque frame, par exemple à chaque fois que l'on passe dans la boucle j'additionne le temps écoulé en milliseconde, et tant que ce temps ne dépasse pas les 1 secondes je n'autorise pas à tirer d'autre boulet. Et quand les 1 secondes sont passé on autorise d'envoyer un boulet, et je redémarre ce compteur à zéro.

    Est-ce une solution correcte? Sinon quels sont les techniques qui sont dans la plupart du temps employer pour résoudre un tel problème?

    Je vous remercie d'avance pour votre aide.

  2. #2
    Membre à l'essai
    Profil pro
    Inscrit en
    Août 2010
    Messages
    36
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2010
    Messages : 36
    Points : 20
    Points
    20
    Par défaut
    Tu peux y allez de deux façons, la première, ne tirez qu'un coup à tout les fois que le spacebar est enfoncé. Pour ça, tu dois ajouté une variable qui va avoir la valeur du dernier keyboardstate.

    Exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    KeyboardState previousKeyboardState;
    if(Keyboard.GetState().IsKeyDown(Keys.Spacebar) && previousKeyboardState.IsKeyUp(Keys.Spacebar))
    {
    TireBoulet();
    }
    previousKeyboadState = Keyboard.GetState();
    Ou si tu veux mettre un temps limite entre chaque tir, tu crée une variable disons float currentTime et tu met ton test à l'intérieur de ton test SpaceBar pressé ou pas.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    currentTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
    If(currentTime >= 1)
    {
    TireBoulet();
    currentTime = 0;
    }
    J'espère que j'ai pu aidé un peu.

  3. #3
    Membre confirmé
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2008
    Messages
    187
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo

    Informations forums :
    Inscription : Octobre 2008
    Messages : 187
    Points : 451
    Points
    451
    Par défaut
    C'est une solution très correcte et plutôt élégante. L'utilisation de Timers est souvent utilisée pour ce genre de problème.

    Cependant, il est d'usage (à ma modeste connaissance) de regrouper la gestion des Timers et des touches dans une fonction à part. Par exemple, en pseudo-code, au lieu de faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Update( time )
        timer = timer + time
        si on appuie sur X et si timer > 1 seconde
            tirer un boulet de canon
            timer = timer - 1 seconde
        finsi
    fin
    tu peut faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Update( time )
        UpdateActions( time )
        si Action == tirer_un_boulet_de_canon
            tirer un boulet de canon
        finsi
    fin
     
    UpdateActions( time )
        Action = ne_rien_faire
        timer = timer + time
        si on appuie sur X et si timer > 1 seconde
            Action = tirer_un_boulet_de_canon
            timer = timer - 1 seconde
        finsi
    fin
    L'avantage est ici de séparer la logique de la classe Tank de la gestion des touches.

    Au passage, plutôt que de remettre le compteur à zéro après avoir tiré un boulet de canon, il est préférable de lui soustraire 1 seconde, pour être sûr que le tir se fait bien à intervalle régulier.

  4. #4
    Membre averti Avatar de yodaime
    Profil pro
    Développeur informatique
    Inscrit en
    Avril 2006
    Messages
    282
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Avril 2006
    Messages : 282
    Points : 340
    Points
    340
    Par défaut
    Ce que dis dancingmad est tout à fait vrai.

    Pour ma part j'ai toujours vu le premier code de Frenchwolf, avec la gestion de l'état précédent de la touche.

    Après tout dépend de ce que tu veux faire.

    Un tir continu, c'est à dire on laisse la touche espace appuyer et un boulet est tiré toutes les X ms, alors c'est la solutions de dancingmad .

    Un tir a chaque fois qu'on appuie sur la touche espace alors c'est la solutions 1ere solution de Frenchwolf.

    Je me demande si il n'y a pas moyen de mixer les deux techniques

  5. #5
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2009
    Messages : 163
    Points : 62
    Points
    62
    Par défaut
    Bonsoir,

    J'ai pris quelques minutes pour faire ce que je souhaitais. Alors voici d'abord le code que j'ai pondu:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    public override void Update(GameTime gameTime)
            {
                this.UpdateShootState(gameTime);
     
                if (this._CanShoot)
                    this.FiredCannonBall();
     
                foreach (CannonBall ball in this._CannonBallList)
                    ball.Update(gameTime);
     
                this.RotateCannon(gameTime);
                this._PreviousKeyboardState = Keyboard.GetState();
     
                base.Update(gameTime);
            }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     
    private void UpdateShootState(GameTime Time)
            {
                this._ShootTimer = Math.Min(this._ShootTimer + Time.ElapsedGameTime.TotalMilliseconds, TankCannon._ShootInterval);
                this._CanShoot = false;
     
                if (Keyboard.GetState().IsKeyUp(Keys.Space))
                {
                    if (this._PreviousKeyboardState.IsKeyDown(Keys.Space) && this._ShootTimer >= TankCannon._ShootInterval)
                    {
                        this._CanShoot = true;
                        this._ShootTimer = 0;
                        this._ShootKeyHoldDown = 0;
                    }
                }
                else if(this._PreviousKeyboardState.IsKeyDown(Keys.Space))
                {
                    if (this._PreviousKeyboardState.IsKeyDown(Keys.Space))
                        this._ShootKeyHoldDown = Math.Min(this._ShootKeyHoldDown + (Time.ElapsedGameTime.TotalMilliseconds * TankCannon._PowerMultiplicator), TankCannon._PowerLimit);
     
                    if (this._ShootKeyHoldDown > TankCannon._PowerStart)
                        this._Power = this._ShootKeyHoldDown;
                }
            }
    Je vais tenté d’expliquer ce que j'ai fait et les choix que j'ai effectué.
    Dans la méthode UpdateShootState, tout d'abord je retiens le temps qui c'est écoulé depuis le tir précédent (tout ce qui commence par "TankCannon." sont des const static que j'ai défini, ici par exemple c'est l’intervalle entre chaque tir). La variable "_CanShoot" permet de savoir si dans la méthode "Update" on autorise de tirer. Ensuite si je commence par le contenu du else if, je teste si la touche espace est enfoncé. Si oui j’exécute quelques petites instructions. Petite chose avant de continuer, j'ai voulu tenter de faire deux modes de tir. Un mode normal, on appuie puis lache et le boulet part, et un mode "Power", on reste enfoncé plus longtemps sur la touche et quand on relâche le boulet par plus vite (pour mieux détruire sa cible ). Donc pour en revenir à mon else if, si la touche espace est enfoncée, on vérifie si à la frame précédente la touche était aussi enfoncé (ce qui signifierais que l'utilisateur est resté appuyé), si c'est le cas je garde en mémoire le temps qui c'est écoulé en millisecondes multiplié par un coefficient que j'ai défini en const, je garde ainsi dans une variable (ici ShootKeyHoldDown) le temps pendant lequel l'utilisateur est resté appuyé. Ensuite je compare ce temps à une const que j'ai défini (encore ), cette const me permet de dire à partir de combien de temps le mode "Power" s'active. Dans mon cas je l'ai reglé à 1300ms. Donc si l'utilisateur reste appuyé moins de 1.3s c'est un tir normal, sinon un tir Power, dans ce cas j'assigne à ma variable "Power" la valeur correspondant à la variable qui garde le temps pendant lequel la touche est resté enfoncé. De base ma variable Power dispose d'une certaine valeur (par exemple 2000 dans le cas d'un tir normal, mais si c'est un tir Power j'ai fait en sorte que sa valeur soit au dessus de 2000, ensuite quand je crée mon boulet de canon, je lui donne cette variable divisé par 1000, je me retrouve avec un seul chiffre, par exemple 2 qui servira alors de spécifier la vitesse du boulet). Ces explications était dans le cas où ma touche était enfoncé.

    Maintenant si la touche est relâché, je test si à la précédente frame la touche était enfoncé, et si le tir précédent c'est effectué après l'intervalle. Si c'est toujours ok, j'autorise d'effectuer un tir, et réinitialise les différents timer. Dancingmad me conseillé de retirer par exemple 1s au timer si je souhaitais avoir un intervalle de 1s entre chaque tir et garder une précision correcte. Mais quand j'ai tenté de cette façon, comme mon timer s'additionnais avec le temps écoulé à chaque tour de boucle, si je restait sans tirer pendant plusieurs secondes je pouvais enchaîner les tirs sans intervalles. Par exemple si au bout de 5 secondes je n'avais pas tiré, quand je tentais un tir je vérifiais si le timer était supérieur à 1 seconde puis je retirais 1. Mais là il restait 4s au timer, donc si je retentais un autre tir juste derrière ça passais de suite. Tandis qu'en remettant le timer à zéro, je suis assuré de bien avoir attendu le temps désiré. Après je me suis probablement mal pris dans mon algo.

    Premièrement j'aimerais avoir votre avis. Ce qui est à améliorer, enlever ou garder.

    Deuxièmement, si je reprends mon passage où je vérifie si la touche est toujours enfoncé pour savoir si je fait un tir Power. Pour ne pas trop me prendre la tête, la valeur que j'assigne à ma variable "Power" correspond au temps qui c'est écoulé pendant que la touche est resté enfoncé. J'ai fixé une limite tout de même, car sinon on reste appuyé 10 secondes, et la vitesse serait de 10. Dans mon cas j'ai par exemple mis 3000. Si on regarde mon code de plus près, on peut voir que lorsque j'additionne le temps écoulé totale avec le temps écoulé entre la frame précédente, ce dernier je le multiplie à une variable "TankCannon._PowerMultiplicator". Ce que je souhaitais c'était éviter que la charge dure trop longtemps. Si je ne l'avais pas mis, dans mon cas l'utilisateur aurait du attendre 3s pour avoir la vitesse maximum (la valeur limite fixé à 3000), donc si je voulais rehausser cette valeur, il aurait fallu attendre plus longtemps. Tout ça pour en venir à mon problème. Je pense qu'il est plus mathématique qu'autre chose, mais je souhaiterais pouvoir faire en sorte que la valeur maximale soit atteinte tout le temps au bout de 3s par exemple. Si demain finalement je voudrais passer ma valeur limite de 3000 à 15000 parce que la vitesse doit être 5x plus élevé quand on est au max, mais toujours dans le même intervalle de temps, comment puis-je faire? Ici dans ce que j'avais fait, si on commençais à activer le mode Power mais qu'on allais pas jusqu'au bout on avait tout de même une vitesse plus élevé que la normal mais pas full, et je voudrais pouvoir garder cette contrainte.

    Voila. Je viens de me relire, c'est vraiment indigeste. Donc je vous remercie beaucoup d'avance pour les conseils et aide que vous pourrez m'apporter.

  6. #6
    Membre expérimenté

    Profil pro
    Programmeur
    Inscrit en
    Août 2002
    Messages
    1 091
    Détails du profil
    Informations personnelles :
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : Programmeur

    Informations forums :
    Inscription : Août 2002
    Messages : 1 091
    Points : 1 679
    Points
    1 679
    Par défaut
    Tu as traditionnellement deux types d'input :
    l'immediat et le différé

    En mode immédiat tu as deux états : touche enfoncée et touche non enfoncée.
    Cela peut servir par exemple pour les déplacements quand tenir la touche "avant" enfoncée va faire avancer le personnage pendant un intervalle de temps.

    En mode différé tu as deux "signaux" : un signal "touche est passée de non enfoncée à enfoncée" et un signal "touche est passée de enfoncée à non enfoncée".
    Cela peut servir par exemple pour taper un texte, tu veux qu'une lettre apparaisse uniquement quand la touche a été préssée la première fois et ne pas avoir de signal répété. Ou quand tu veux une action simple comme actionner un bouton dans un jeu/une interface.

    Bien entendu ces deux versions sont les complémentaires l'une de l'autre. La version immédiate peut-être déduite de la version différée en gardant une trace précise des signaux rencontrés et la version différée peut-être déduite de la version immédiate en vérifiant à intervalle réguliers l'état courant et simuler le signal lors de changement de cet état. Bien entendu, en pratique cela dépend fortement de la résolution à laquelle les états sont rapportés et la fréquence à laquelle tu vérifies l'état courant.

    C'est pour ça qu'il vaut mieux travailler d'emblée avec la version différée du signal. (avec une possible exception lorsque tu n'as besoin que d'un nombre limité d'états courants sans historique et qu'en contrepartie un trop grand nombre de signaux pourraient être envoyés qui causeraient un problème de performance dans leur traitement en temps réel).

    Pour ton canon, tu veux probablement envoyer un boulet lorsque le signal "passe de non enfoncée à enfoncé" est détecté.

    Mais tu peux en plus conserver l'état courant (enfoncé/non enfoncé) pour toutes les secondes ou deux secondes ou le temps que tu veux, réenvoyer un boulet pour simuler l'"autofire". (ça fonctionne aussi comme ça quand tu tapes un texte au clavier : une lettre est imprimé lors du premier signal mais si tu laisses ton doigt sur la touche au lieu de la relever alors une succession de caractères est imprimée plus ou moins rapidement selon les settings de ton OS).

    LeGreg

  7. #7
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2009
    Messages : 163
    Points : 62
    Points
    62
    Par défaut
    Bonjour,

    Merci pour ce complément d'informations. J'essais de situer ce que j'ai fait par rapport à ce que tu as expliqué, c'est pas évident. Mon canon tire un boulet quand la touche (dans mon cas espace) est relâché. Bien entendu cela supposais que l'était précédent était enfoncé sinon ça partirais même sans rien faire. J'ai fait ce choix de ne tirer que quand la touche passe de enfoncé à relever pour permettre d'avoir des niveaux de puissances. Si l'utilisateur appuie rapidement (ou du moins de façon "normal" sur la touche), le boulet part avec une vitesse "normal". Tandis que si il est resté au moins 1s et des poussières enfoncé, quand il relâcheras le boulet partira plus vite, et plus il reste appuyé longtemps, plus ça partira vite.

    J'ai l'impression que dans le code que j'ai écrit j'ai fait un petit mélange entre les deux types d'input que tu as décrit. Je vais peut être me tromper dans ce que je vais dire mais je me lance.

    Quand je fais mon test pour savoir si la touche est enfoncé, je cherche à savoir si elle l'était avant. Je pense que cela s'apparente au mode immédiat que tu as décrit. Tu donnais par exemple le fait de rester appuyer sur la touche pour faire avancer un personnage, je fais de même mais pour charger la puissance d'un tir.

    Tandis que quand je vérifie si la touche est relevé, je vérifie si auparavant elle était au contraire enfoncé. Donc cela peut s'apparenter au mode différé, ici je regarde si la touche est passé de enfoncé à relevé. Ce qui me permet de savoir si le boulet doit partir ou non.

    Je sais pas si c'est correct, tu me corrigeras dans mes dires. Sinon ce que je voulais savoir aussi c'est si ce que j'ai écrit est correct? D'un point de vue fonctionnel ça fonctionne, j'ai ce que j'attends. Mais d'un point de vue écriture du code, est-ce que c'est dégueu? Ou bien à peu près correct?

    Merci d'avance.

  8. #8
    Membre du Club Avatar de Takumi
    Homme Profil pro
    Développeur .NET
    Inscrit en
    Juin 2009
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juin 2009
    Messages : 163
    Points : 62
    Points
    62
    Par défaut
    Bonjour,

    J'ai finalement résolu mon dernier petit problème. Je met la discussion en résolue. Par contre si vous avez des liens vers des articles ou tutoriel décrivant les différentes approches sur la gestion des input dans un jeux vidéo et pourquoi pas XNA en particulier, je suis preneur.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 5
    Dernier message: 29/02/2016, 13h56
  2. Detection des touches enfoncées par API
    Par Ingham dans le forum VB 6 et antérieur
    Réponses: 31
    Dernier message: 20/02/2006, 12h30
  3. [Forms6i]touche enfoncée
    Par asnf dans le forum Forms
    Réponses: 6
    Dernier message: 02/01/2004, 16h40
  4. [DirectInput] Gestion des touches en mode fenêtré
    Par Harry_polin dans le forum DirectX
    Réponses: 8
    Dernier message: 19/03/2003, 17h50
  5. Cherche Nom des touches du clavier
    Par juan64 dans le forum C++Builder
    Réponses: 8
    Dernier message: 23/07/2002, 19h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo