SDL2 : Configurer le délai d'une répétition d'une touche

a marqué ce sujet comme résolu.

Plop !

Je me suis mis très récemment à SDL (plus par curiosité qu'autre chose) et j'ai un petit souci dont je ne parviens pas à trouver la solution (avec SDL 1.2, ça fonctionnait sans problème, j'imagine que c'est faisable avec SDL2).

En gros, j'en suis au stade de faire bouger une image en appuyant sur des touches. Apparemment la répétition est gérée « par défaut » sous SDL2, mais j'ai un petit problème : avant que la répétition ne s'enclenche, il y a un petit délai (environ une demi seconde) : résultat, lorsque que j'appuie sur la touche, mon personnage bouge une fois, puis vient ce délai d'une demi seconde et enfin la répétition s'active.

Ce que je voudrais, c'est que la répétition s'active dès l'appui de la touche (ou en tout cas, que ça ne soit pas perceptible parce que là, ça l'est).

Avec SDL 1.2, SDL_EnableKeyRepeat permettait cela mais cette fonction n'existe apparemment plus dans SDL 2.

Bref, si vous savez comment résoudre ça, ça serait franchement sympa. :)

Merci d'avance.

Voilà. ;)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <SDL2/SDL.h>

#define SCREEN_WIDTH    640
#define SCREEN_HEIGHT   480

int main(int argc, char **argv)
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Surface *sprite;
    SDL_Texture *texture;
    SDL_Rect player;
    SDL_Event event;
    int spriteWidth, spriteHeight;
    int cont = 1;

    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);

    window = SDL_CreateWindow("Test du deplacement d'un personnage", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    sprite = SDL_LoadBMP("player.bmp");
    SDL_SetColorKey(sprite, SDL_TRUE, SDL_MapRGB(sprite->format, 255, 0, 255));

    texture = SDL_CreateTextureFromSurface(renderer, sprite);

    SDL_QueryTexture(texture, NULL, NULL, &spriteWidth, &spriteHeight);

    player.x = (SCREEN_WIDTH / 2) - (spriteWidth);
    player.y = (SCREEN_HEIGHT / 2) - (spriteHeight);
    player.w = spriteWidth;
    player.h = spriteHeight;

    SDL_RenderCopy(renderer, texture, NULL, &player);

    SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);

    while(cont == 1)
    {
        if(SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                    cont = 0;
                break;
                case SDL_KEYDOWN:

                    std::cout << "Toutche enfoncee." << std::endl;

                    switch(event.key.keysym.sym)
                    {
                        case SDLK_ESCAPE:
                            cont = 0;
                        break;
                        case SDLK_DOWN:
                            player.y += 1;
                            SDL_RenderCopy(renderer, texture, NULL, &player);
                        break;
                    }

                break;
            }

            SDL_RenderPresent(renderer);
        }
    }

    SDL_RenderPresent(renderer);

    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
} 

Salut.

À la place de confondre la gestion des évènements et la logique du jeu, tu devrais faire un tableau associant une touche à son état (pressée ou non). Ta boucle se décomposera alors en deux partie : la gestion des évènements, pour mettre à jour le tableau, et le jeu en lui même, où les actions seront effectuées en fonction de l'état des touches.

+2 -0

Ben le souci, c'est ce que tu as souligné :

avant que la répétition ne s'enclenche, il y a un petit délai (environ une demi seconde) : résultat, lorsque que j'appuie sur la touche, mon personnage bouge une fois, puis vient ce délai d'une demi seconde et enfin la répétition s'active.

Que20

SDL_EnableKeyRepeat a effectivement été supprimé avec SDL2, tu dois donc utiliser une autre méthode. Soit ce que j'ai proposé plus haut, soit en utilisant cette fonction, qui a néanmoins l'inconvénient de « manger » tous les évènements d'un coup ; il est donc possible de louper un appui de touche si celle-ci est relâchée assez vite.

+1 -0

Je dois avouer que j'ai un peu de mal à me représenter ta solution et en quoi ça va régler le souci.

Et question idiote mais… pourquoi ce délai ? Bug de SDL ou c'est « volontairement » imposé ? Je peux comprendre le problème inverse : admettons qu'on ai un menu, si en appuyant juste un peu trop on descend de 4 choix au lieu d'un seul ben ça serait tout aussi chiant. Mais bon, je trouve que ça devrait être « paramètrable » comme dans SDL 1.2 (mais si j'ai bien compris : sans ruser, ce n'est pas possible :/ ).

Je plussoie Praetonus. Si tu décides de ne pas utiliser SDL_GetKeyboardState, tu peux créer un tableau de taille SDL_NUM_SCANCODES et une fonction qui remplit ce tableau à chaque tour de boucle. Ta fonction se charge alors de mettre à jour tes évènements. Le mieux est alors de créer une structure qui se charge de gérer tes évènements. Par exemple :

1
2
3
4
5
struct Input
{
   SDL_bool key[SDL_NUM_SCANCODES];
   SDL_bool quit;
}

Et la fonction qui la mettrait à jour serait du genre :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void UpdateEvent(struct Input *in)
{
   SDL_Event e;
   while(SDL_PollEvent(&e))
   {
      if(e.type == SDL_QUIT)
         in->quit = SDL_TRUE;
      else if(e.type == SDL_KEYDOWN)
         in->key[e.key.keysym.scancode] = SDL_TRUE;
      else if(e.type == SDL_KEYUP)
         in->key[e.key.keysym.scancode] = SDL_FALSE;
   }
}  

Je t’aurais bien renvoyé sur le tutoriel que je suis en train d’écrire sur la SDL2, mais je n’ai même pas encore commencé à rédiger la partie sur les évènements. ^^ En gros, cela te permet de séparer les différentes composantes de ton programme. Gestion des évènements d’un côté, gestion de l’affichage de l’autre… Avec ce que je viens de proposer, ta boucle principale devient (j’ai supprimé l’affichage en cas de touche appuyée, mais on peut le remettre)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Initialisations, création de in...

while(!in.quit && !in.key[SDL_SCANCODE_ESCAPE])
{     
   UpdateEvent(&in);
   if(in.key[SDL_SCANCODE_DOWN])
      player.y += 1;

   SDL_RenderCopy(renderer, texture, NULL, &player);
   SDL_RenderPresent(renderer);
   SDL_Delay(20);
}

Ici, j’affiche à chaque tour de boucle, mais j’attends également 20 ms avant de passer à l’image suivante (par contre, je ne sais pas si c’est voulu, mais tu n’effaces pas avant de redessiner).

PS : je vois que tu codes en C++, alors ce que je propose n’est sûrement pas top niveau conception. J’imagine qu’il faudrait créer une classe pour gérer les évènements.Je laisse les pros du C++ en parler.

EDIT :

Et question idiote mais… pourquoi ce délai ? Bug de SDL ou c'est « volontairement » imposé ? Je peux comprendre le problème inverse : admettons qu'on ai un menu, si en appuyant juste un peu trop on descend de 4 choix au lieu d'un seul ben ça serait tout aussi chiant. Mais bon, je trouve que ça devrait être « paramètrable » comme dans SDL 1.2 (mais si j'ai bien compris : sans ruser, ce n'est pas possible :/ ).

Même en SDL 1.2 c’était mieux de s’en occuper soi-même en fait. Avec la méthode que j’ai à peu près implémenté, tu gères vraiment tes évènements comme tu veux. Tu peux décider que tu veux de la répétition de touche pour certaines touches seulement. Par exemple, dans un jeu, tu pourrais vouloir que tant que le joueur appuie sur les touches directionnelles, il avance, mais qu’il doive relâcher la touche de tir et la represser pour faire un nouveau tir.

+0 -0

Et question idiote mais… pourquoi ce délai ? Bug de SDL ou c'est « volontairement » imposé ?

Vu que tu ne spécifies rien de particulier dans ton programme, le délai avant répétition et la vitesse de répétition est entièrement gérée par le système d'exploitation. C'est surtout windows qui a introduit ce concept il y a bien longtemps.

Mais comme les autres l'ont déjà dit, tu ferais mieux de désactiver la répétition de touches complètement au niveau SDL, et la gérer toi-même avec une structure qui mémorise l'état appuyé ou non de chaque touche. ET bien sûr de découpler la boucle des évènements de celle du jeu car ça c'est assez primordial.

En gardant les choses ainsi, tu risque de te frotter à des bugs bizarroïdes qu'on a tous eu en étant débutants dans la conception de jeux vidéo: un déplacement irrégulier comme celui que tu décris, des FPS pas vraiment constants, ou bien le personnage qui arrive à traverser les murs plus ou moins aléatoirement quand bien même on croit avoir fait toutes les vérifications, par exemple.

+0 -0

Plop !

Après avoir un peu recherché je pense avoir trouvé une manière d'implémenter cela.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
#include <iostream>
#include <SDL2/SDL.h>

#define SCREEN_WIDTH    640
#define SCREEN_HEIGHT   480

using namespace std;

int main(int argc, char **argv)
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Surface *sprite;
    SDL_Texture *texture;
    SDL_Rect player;
    SDL_Event event;
    int spriteWidth, spriteHeight;
    int cont = 0;
    bool keys[322] = {false};

    SDL_Init(SDL_INIT_VIDEO);

    window = SDL_CreateWindow("Test du deplacement d'un personnage", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    sprite = SDL_LoadBMP("player.bmp");
    SDL_SetColorKey(sprite, SDL_TRUE, SDL_MapRGB(sprite->format, 255, 0, 255));

    texture = SDL_CreateTextureFromSurface(renderer, sprite);

    SDL_QueryTexture(texture, NULL, NULL, &spriteWidth, &spriteHeight);

    player.x = (SCREEN_WIDTH / 2) - (spriteWidth);
    player.y = (SCREEN_HEIGHT / 2) - (spriteHeight);
    player.w = spriteWidth;
    player.h = spriteHeight;

    SDL_RenderCopy(renderer, texture, NULL, &player);

    SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);

    SDL_RenderPresent(renderer);

    while(cont == 0)
    {
        if(SDL_PollEvent(&event))
        {
            switch(event.type)
            {
                case SDL_QUIT:
                    cont = 1;
                break;
                case SDL_KEYDOWN:
                    keys[event.key.keysym.scancode] = true;
                break;
                case SDL_KEYUP:
                    keys[event.key.keysym.scancode] = false;
                break;
            }
        }

        if(keys[SDL_SCANCODE_ESCAPE])
        {
            cont = 1;
        }

        if(keys[SDL_SCANCODE_DOWN])
        {
            player.y += 4;
        }

        if(keys[SDL_SCANCODE_UP])
        {
            player.y -= 4;
        }

        if(keys[SDL_SCANCODE_LEFT])
        {
            player.x -= 4;
        }

        if(keys[SDL_SCANCODE_RIGHT])
        {
            player.x += 4;
        }

        SDL_Delay(16);

        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, texture, NULL, &player);
        SDL_RenderPresent(renderer);
    }

    SDL_RenderPresent(renderer);

    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
} 

Et ça fonctionne ! ^^

La suite de if à la fin, c'est ce qu'il y a de mieux ou bien il y a des façons plus propres de le faire ? L'avantage, c'est que je peux me déplacer en diagonale (si je comprends bien, il exécute un if et puis un autre, ce qui donne le déplacement en diagonale) alors que quand j'ai essayé un switch, vu qu'un seul cas est géré et bien je ne peux me déplacer que dans une seule direction.

Ah, c'est cool si un tutoriel est en cours de rédaction ! ^^

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte