Pong
LeSolitaire Il y a 6 mois Premium Pro - Adhésion à vie0

Bon et bien nous y sommes, il faut bien faire preuve d’un peu sociabilité de temps en temps. Comme j’arrive enfin à comprendre les concepts de base de la programmation et à les faire cohabiter, c’est le moment de mettre tout ça en application en créant un petit jeu. Je suis parti sur un pong, ça me semble plus simple qu’un casse-briques, par exemple.

Et jusque-là, j’ai en effet rencontré assez peu de problèmes. J’ai commencé par afficher les deux raquettes et la balle. J’ai ensuite mis la balle en mouvement en lui appliquant une vitesse en x et en y, et géré les collisions avec les bords de l’écran (oui, les quatre bords dans un premier temps) simplement en inversant la vitesse y si la balle touche le bord haut ou le bord bas de l’écran, ou la vitesse x si la balle touche le bord gauche ou le bord droit.

Après la balle, les raquettes. Pour celle du joueur, rien de compliqué. On vérifie les flèches haut ou bas dans l’update, et si l’une est enfoncée on met à jour la position y de la raquette en conséquence. Concernant la balle, c’est une autre paire de manche. N’ayant pas envie de jouer seul avec mes deux mains, j’ai opté pour intégrer une (toute) petite IA dans le jeu. A ce stade du projet et à mon niveau de compétences, pas besoin de machine learning. Je calque simplement la position y de la raquette adverse sur la position y de la balle et ça fait le travail. Ça a cependant l’inconvénient de rendre le jeu invincible, puisque l’adversaire suit mécaniquement la balle, mais ça n’a pas d’importance pour le moment.

Tout ce beau monde étant maintenant en mouvement, il me faut à présent gérer les collisions entre la balle et les raquettes. Pas de problème, j’utilise les fonction CheckCollision à laquelle David fait régulièrement appel dans la formation et j’inverse la vitesse x de la balle si une collision est détectée entre elle et une des deux raquette. Mais telle quelle, cette solution pose quelques soucis.

Tout d’abord, les raquettes ne sont pas collées sur les bords de l’écran. Pour l’instant, je gère toujours les collisions avec les bords gauche et droit de la fenêtre. Du coup, dans le cas de figure -certes rare, mais qui s’est quand même produit durant mes tests- où la balle réussi à passer entre le bord de l’écran et le dos de la raquette, elle rebondi presque indéfiniment entre les deux. Autre problème, lorsque la collision est détectée, selon le temps qui s’est écoulé entre deux frames, la balle être légèrement « dans » la raquette, ce qui aimante en quelque sorte les deux entités. La balle glisse alors le long de la raquette avant de repartir dans une direction relativement aléatoire. Dernière chose, comme je n’inverse que la vitesse x de la balle peu importe où a lieu le contact entre elle et la raquette, si jamais la collision a lieu sur le dessus ou le dessous de la raquette, elle repart en sens inverse, ce rend assez… bizarre, je ne saurais pas l’expliquer autrement.

Pour régler le premier problème, je ne m’occupe pas des collisions mais du gameplay. Je vérifie la position de la balle et si elle est complètement sortie à gauche ou à droite de l’écran, je la fais réapparaitre à son point de spawn original, c’est-à-dire en haut et au lieu de l’écran. De plus elle conserve sa vitesse. Dans tous les cas elle va vers le bas. En effet, soit, au moment où la balle sort de l’écran, sa vitesse y était positive donc quand elle repop la vitesse y est toujours positive donc la balle « descend », soit la vitesse y était négative -la balle « remontait »- mais comme je fais apparaitre la balle juste sous le bord haut de l’écran, une collision est aussitôt détectée et la vitesse y est inversée, donc la balle « descend ».

Reste maintenant à s’occuper des « vrais » bugs de collisions. La raquette du joueur est située sur la droite de l’écran, à une trentaine de pixel à gauche du bord. Dans le cas où une collision est détectée entre la balle et la raquette, je regarde si le milieu de la balle a dépassé la position x de la raquette. Si oui, j’en déduis que la balle est trop « enfoncée » dans la raquette pour que la collision ait lieu sur la face verticale de la raquette. Le contact a donc forcément lieu sur le dessus ou le dessous, pour savoir avec lequel des deux, je regarde la valeur de la vitesse y de la balle au moment du contact. Si elle est positive, ça signifie que la balle descend, donc que le contact a lieu sur le dessus de la raquette, à l’inverse, si la valeur de la vitesse est négative, la balle est en train de remonter, ce qui veut dire que le contact a lieu en dessous de la raquette.

Pour éviter le problème d’aimantation entre la raquette et la balle, je la repositionne pile sur le dessus ou le dessous de la raquette selon la situation et j’inverse la vitesse y. Ainsi, au lieu de voir repartir la balle vers là d’où elle venait, si la balle tape le dessus ou le dessous de la raquette, vous marquez d’une certaine façon un but contre votre camp. Il ne reste plus qu’un dernier cas à vérifier, lorsque la balle frappe bien la face verticale de la raquette. Il suffit alors de replacer la balle pile sur la face gauche de la raquette et d’inverser la vitesse x.

Il y a un petit piège: l’origine des entités est située en haut et à gauche. Donc quand on replace la balle par rapport à la raquette, il faut prendre cela en compte. Pour le dessus, par exemple, la balle devra être repositionnée à la valeur en y de la raquette moins la hauteur de la balle.

Il faut à présent répéter l’opération pour la raquette contrôlée par l’ordinateur. C’est pratiquement la même opération, à un détail près. Comme cette raquette est située sur la gauche de l’écran et que la balle arrive depuis la droite, au moment de vérifier la position du milieu de la balle par rapport à la position en x de la raquette, il faut en plus prendre en compte la largeur de la raquette. Sinon la balle va traverser la raquette et entrer en collision avec le vide situé juste derrière.

A ce point du développement, j’ai une raquette contrôlée par le joueur, une autre par l’ordinateur, une balle qui bouge, qui change de direction quand elle entre en collision avec le haut de l’écran, le bas, ou avec une raquette et qui réapparait  quand elle sort de l’écran par la gauche ou par la droite. Il me faut à présent rendre la machine faillible sinon la partie est potentiellement infinie, compter les points et instaurer une condition de victoire (ou de défaite) après un certain nombre de points marqués, et j’aurais une boucle de gameplay assez complète.

Mais avant de m’atteler à ces tâches, je me suis permis une fantaisie. Quand la balle sort de l’écran, je sais d’avance ce qui va se passer à sa réapparition. Je trouve ça un peu ennuyeux. J’ai donc défini trois points de spawn. Ils sont tous les trois situés au milieu de l’écran, mais à des hauteurs différentes: à 50 pixels du haut, au milieu et à 50 pixels du bas. Là dessus, j’ai un premier générateur de nombre aléatoire, de 1 à 3 qui défini sur quel point la balle va apparaitre. Ensuite il y a des conditions: si la balle apparait en haut, elle ne peut aller que vers le bas, mais aléatoirement vers la gauche ou vers la droite. Sur le point du bas, c’est sensiblement la même chose, sauf que la balle ne peut aller que vers le haut. Par contre sur le point du milieu, c’est open bar: la balle peut aller aléatoirement vers le haut ou vers le bas, vers la gauche ou vers la droite. Ce n’est pas grand chose mais ça apporte de la variété.

Ceci fait, mon but à présent est de rendre le jeu possible à battre. Une recherche rapide sur le net m’apporte une première solution assez simple. Il faut d’abord générer un nombre aléatoire. Ensuite, lorsque la raquette doit changer de direction (après un rebond de la balle), on regarde la valeur du nombre aléatoire et si ça correspond à un nombre qu’on aura choisi au préalable (par exemple 1), on autorise le changement de direction. Comme le jeu est mis à jour 60 fois par seconde, la raquette adverse ne reste jamais vraiment fixe mais le petit temps de latence que cela lui donne suffit pour permettre à la balle de passer. Il reste un défaut, quand la balle réapparait la position de la raquette n’est pas réinitialisée et si elle est un peu loin de la balle ou si la raquette doit changer de direction en cours de route, l’IA a du mal à suivre et on peut se retrouver à marquer deux ou trois points sans avoir rien fait. Je corrigerais ce problème plus tard, pour l’instant j’ai ce que je cherchais: un jeu que l’on peut battre.

Pour compter les points, rien de plus simple. Une variable initialisée à 0 pour les points du joueur, une autre pour les points de la machine et on incrémente la bonne variable de 1 selon le bord par lequel est sortie la balle.

Il ne reste plus à présent qu’à définir la condition de victoire. Pour l’instant je l’ai fixé à 5 points. Au début j’ai simplement affiché « victoire » ou « défaite » dans un print. C’est peu pratique à suivre et ça oblige en plus le joueur à quitter manuellement le jeu quand la console indique « victoire » ou « défaite ».

Pour finir cette première version du jeu, j’ai donc codé un gestionnaire de scènes. C’est peut-être jusqu’ici ce qui m’a donné le plus de fil à retordre, j’ai dû pas mal bidouiller pour rendre le truc fonctionnel. Temps que j’étais sur des états fixes, tout fonctionnait très bien, mais une fois le jeu mis en place dedans, une fois dans la scène de jeu, impossible de changer. En effet, la scène de jeu étant la seule rafraichie, quand un changement d’état devait se produire, il ne durait qu’une fraction de seconde et la scène de jeu reprenait le dessus. J’ai donc crée un booléen par état (j’en ai cinq pour le moment: MENU, OPTIONS, JEU, VICTOIRE, DEFAITE) et dans chaque état je défini le booléen correspondant à true et tous les autres à false. Ensuite, pour gérer les changements d’états et l’état courant j’ai une fonction que j’appelle dans l’update. Ainsi temps que je suis dans la scène x, le booléen x est à true, tous les autres sont sur false et le jeu reste sur la scène courante x. Quand je passe à la scène y, le booléen x devient false, le booléen y devient true et ainsi de suite.

Voilà où j’en suis arrivé. Le jeu est plutôt ennuyeux à jouer, la vitesse étant assez lente; en plus l’IA est régulièrement aux fraises et la présentation du tout est des plus soviétique. Mais j’ai un jeu complet, et qui fonctionne correctement (on peut même quitter proprement depuis le menu).

J’ai plusieurs axes de travail pour améliorer le jeu:

  • L’IA. C’est très probablement ce que je vais chercher à améliorer en premier. Après quelques recherches, je pense avoir trouvé un algorithme un peu plus sophistiqué qui n’a pas l’air ardu à implémenter.
  • Peaufiner le gameplay. Le jeu est très lent, le contrôle de la raquette un poil lourd et il n’y a aucun feedback.
  • Ajouter de vraies options dans le menu des options, pour le moment c’est un simple placeholder. Dans le désordre, j’ai pensé à pouvoir passer le jeu en vertical au lieu d’horizontal, un mode deux joueurs, des niveaux de difficultés, des parties qui se jouent au temps au lieu du nombre de points, pouvoir modifier / couper le volume des sons et de la musique quand j’en aurai ajouté. Peut-être quelques statistiques aussi.
  • Comme dit juste au-dessus, ajouter des sons et de la musique pour égayer un peu le tout.
  • Donner une vraie UI au jeu pour le rendre chaleureux

Quand j’aurai fait tout ça, ce sera déjà pas mal. A la prochaine!

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.