nbrignol.fr Ceci est ma contribution au World Wide Web.

Actor Stdio : étape 2 Le héros se déplace en accélérant vers la droite.

Catégories : jeu vidéo, développement, game design, SDL, C++.
Publié en 2015.

Principe

L'idée est de modifier la position x de l'acteur et de le faire réafficher à l'écran à chaque fois.

Boucle de jeu simple

Pour commencer, je vais ajouter une boucle while qui va procéder au changement de la position x, et rappeller les méthodes showActor et waitForExit. Au passage il faut modifier cette derniere méthode pour savoir si l'évènement "exit" a eu lieu et surtout rendre la main s'il n'y a pas eu d'évènement.

Ca marche mais ce n'est pas hyper précis. A noter que l'affichage fonctionne bien car la méthode showActor redessine la fenêtre en entier avant de dessiner l'acteur. Essayez d'enlever le SDL_FillRect dans showActor, vous verrez la trace de l'acteur !

Problème : on ne contrôle pas bien la vitesse de déplacement. Il faut trouver un moyen de chronométrer et de faire le déplacement 30 fois par secondes, par exemple.

Controlleur

Avant de m'attaquer sérieusement au chronométrage, je rassemble tout ça dans un controlleur que je nomme MainController. Cela me permettra d'alléger un peu le fichier main.cpp.

Ce controller sera simple : la méthode run qui sera appellée une seule fois par le fichier main.cpp et la méthode step qui sera appellée à chaque tour de ma boucle.

SDL Timer

L'idée de base serait de battre un rythme arbitraire (30 fois par seconde, par exemple) et de procéder au déplacements et à l'affichage à chaque "temps".

Si on pouvait appeller la méthode step 30 fois par secondes, ce serait un bon début.

Il existe plein de moyens pour déclencher une fonction à intervale régulier, et la librairie SDL2 en propose un avec la fonction :

Uint32 SDL_AddTimer(Uint32 interval, void * functionToCall, void * params)

Cela sonne bien, mais après avoir testé, il se trouve que le timer execute la fonction de callback (donc notre step) dans un thread séparé. Malheureusement, SDL (ainsi que beaucoup d'autres libs) nous oblige à appeller les fonctions d'affichage depuis le thread principal seulement. Du coup pas moyen de dessiner avec cette méthode !

Il est problablement possible de contourner ce problème en appellant seulement le calcul de déplacement avec le timer, et s'occuper de l'affichage dans le main thread, mais ca me semble compliqué pour le moment. A voir plus tard.

Dors si c'est pas l'heure

Je change donc de principe. L'idée maintenant est de rester dans ma boucle while (et le main thread), et d'executer la méthode step seulement si ca fait plus de 1/30s qu'elle a été éxécuté.

Dans la boucle while, il faut donc compter le nombre de Ms écoulés, et d'attendre pour appeller ou pas ma méthode.

Je commence par chronométrer avec la fonction clock(), qui permet de savoir le nombre de millisecodes écoulée depuis le début de l'execution en divisant cette valeur par la constante CLOCKS_PER_SEC.

A noter que clock() ne peut pas mesurer des durée de plus de 35 minutes avant de recommencer à 0 (pour des raisons de dépassement de taille d'int). Comme je mesure des intervales courts ca devrait aller, mais cela reste à confirmer lors d'un test.

Ca marche, mais mon programme utilise le processeur en permanance (90% du CPU sur mon mac !).

Sleep

Lors qu'on ne fait rien, il vaut mieux faire dormir le processus, cela permet de libérer le processeur. J'utilise donc la fonction usleep qui permet de rendre la main au système pendant quelques microsecondes.

C'est ce que je fais dès que la dernière execution à eu lieu, je fais un peu dormir le processus. Attention par contre, car clock() ne compatibilise pas le temps de sommeil ! Il faut donc penser à faire l'addition pour notre calcul du temps d'attente.

F.P.S. (Frames per seconds)

Un autre soucis est que si je change le FPS, mon hero ne se déplace pas à la même vitesse... Chaque tour de boucle, il se déplace de 1px, que le tour dure 1/30s ou 1/5s...

J'utilise donc le temps effectif depuis le dernier calcul de position pour calculer la nouvelle position. Pour cela je calcule simplement :

deplacement effectif = velocite px par seconde * temps passe depuis dernier deplacement

Premier exemple : Avec une velocité de 100 px/s, et avec 30 FPS, je me déplacerai de 100 * (1/30) = 100 * 0.033 = 3.33 px par tour de boucle. Il y aura 30 tours de boucle en une seconde, pour un déplacement total de 3.33 * 30 soit environ 99.9px

Autre exemple : Avec une velocité de 100 px/s, et avec 10 FPS, je me déplacerai de 100 * (1/10) = 100 * 0.1 = 10 px par tour de boucle. Il y aura 10 tours de boucle en une seconde, pour un déplacement de 10 * 10 soit... 100px

Du coup, je peux changer le FPS, le hero se déplace toujours à la même vitesse à l'écran (mais par contre, il est plus ou moins fluide).

Attention, ce n'est pas la technique idéale (voir liens plus bas) mais cela me semble un très bon compromis pour le moment.

Il faudra trouver (plus tard) comment régler les problemes d'arrondis, car en réalité je me déplacerai de 3 px par tour, et non pas 3.33. De plus, avec des FPS très élevés, l'acteur risque de ne pas se déplacer car un déplacement de 0.8px sera arrondis à zéro.

Refactoring

Avant d'aller plus loin, il est temps de refactorer : d'abord je rajoute la propriété velocity à la classe Actor, puis j'ajoute une methode move(float elapsed) sur la même classe, pour que l'acteur gère lui même son déplacement, en lui passant le nombre de millisecondes écoulées depuis le dernier mouvement.

J'encapsule aussi la partie "timer" dans une classe dédié : Timer.

Accélération

Il reste à gérer l'accélération. Il faut pour cela simplement faire évoluer la vélocité dans le temps. J'ajoute simplement un multiplicateur pour la vélocité pour simuler l'accélération lors du calcul du déplacement, et voila !

Et voila !

Mission accomplie (j'ai désactivé le dessin du fond pour la capture) !

Notes pour plus tard
Voir pour différencier le timer pour le calcul du déplacement et un autre pour l'affichage.
S'assurer que la fonction clock() ne pose pas de probleme lors du dépassement de 35 minutes.
Gérer correctement l'arrondi des déplacements.