Séance 3 · Travaux pratiques
TP 3 · Esquive
Un petit jeu complet : vous dirigez un carré bleu au clavier et devez éviter des balles orange qui rebondissent et se multiplient. Le but n'est pas le jeu lui-même mais sa structure : état, mise à jour, rendu, boucle.
Télécharger le code de départtp3-depart.zip · 5 Ko · à décompresser, puis ouvrir le dossier tp3/depart/ dans VS Code
Objectifs
- Dessiner plusieurs objets avec une seule géométrie et des uniforms.
- Écrire une boucle
requestAnimationFrameavec delta time. - Séparer
updateetrender, gérer le clavier. - Passer à un pas de temps fixe et mesurer FPS et UPS.
Lancez la page : un grand carré bleu recouvre tout. C'est le joueur, qui n'est ni réduit ni placé. On commence par là. Lisez d'abord tout main.js : l'objet etat contient tout ce qui décrit le jeu à un instant donné.
Placer et dimensionner avec des uniforms
Le buffer contient un seul carré, de −1 à 1. Pour dessiner le joueur et les balles, on réutilise ce carré en le réduisant et en le déplaçant dans le vertex shader.
- 1.1 Ajoutez les uniforms
u_deplacement(vec2) etu_taille(float), et calculeza_position * u_taille + u_deplacement. - 1.2 Récupérez leurs emplacements.
- 1.3 Complétez
dessinerCarreavecgl.uniform2fetgl.uniform1f.
Un petit carré bleu en bas et un petit carré orange en haut, immobiles.
Pourquoi ne pas modifier les sommets dans le buffer pour déplacer un objet ? Dans quel ordre sont appliquées l'échelle et la translation ? Que se passerait-il en écrivant (a_position + u_deplacement) * u_taille ?
La boucle et le mouvement
- 2.1 Écrivez la fonction
image(t): calculezdten secondes à partir detet du temps précédent, appelezupdate(dt)puisrender(), et redemandez l'image suivante. Lancez la boucle à la fin du fichier avecrequestAnimationFrame(image). - Comptez les images et affichez une fois par seconde le nombre de FPS dans
hud.textContent. - 2.2 Dans
update, déplacez chaque balle :b.x += b.vx * dt, idem en y. Si la balle dépasse un bord (en tenant compte de sa taille), replacez-la contre le bord et inversez la composante de vitesse correspondante.
La balle rebondit sur les quatre bords sans jamais en sortir ni s'y « coller ». Le HUD affiche environ 60 FPS (ou la fréquence de votre écran).
Pourquoi faut-il replacer la balle contre le bord avant d'inverser sa vitesse ? (Indice : imaginez un dt très grand.)
Je suis bloqué sur dt
let tPrecedent = performance.now();
function image(t) {
const dt = (t - tPrecedent) / 1000;
tPrecedent = t;
// ...
requestAnimationFrame(image);
}
Le clavier
- 3.1 Créez
const touches = new Set();. Surkeydown, ajouteze.code; surkeyup, retirez-le. Appeleze.preventDefault()pour les flèches et l'espace, sinon la page défile. - 3.2 Dans
update, calculez une directiondx, dyà partir des touches ("ArrowLeft","ArrowRight"…, et"KeyA","KeyD","KeyW","KeyS"qui correspondent à Q, D, Z, S sur un clavier AZERTY). Déplacez le joueur dedx * vitesse * dtet empêchez-le de sortir du cadre avecMath.max/Math.min.
Le joueur se déplace en douceur, y compris en diagonale avec deux touches à la fois, et reste dans le cadre.
En diagonale, le joueur va-t-il plus vite qu'en ligne droite ? De combien ? Comment corriger ?
Collisions et fin de partie
- 4.1 Écrivez
collision(a, b)(voir le cours, section 6). - 4.2 Si le joueur touche une balle,
etat.enJeu = false. Quand la partie est finie,updatene fait plus bouger le jeu, sauf si la touche Espace est enfoncée : elle appellereinitialiser(). Pendant le jeu, faites avanceretat.temps. - 4.3 Ajoutez une nouvelle balle toutes les 5 secondes (un compte à rebours dans
etat). - Dans
render, changez la couleur de fond quand la partie est perdue, et affichez le temps de survie dans le HUD.
Le jeu est jouable : les balles s'accumulent, un contact arrête tout, Espace relance une partie.
Pas de temps fixe
Cochez « Simuler un ordinateur lent » : chaque rendu prend alors 40 ms de plus, et le jeu ne tourne plus qu'à environ 20 FPS.
Avec votre boucle actuelle, la vitesse des balles change-t-elle quand on coche la case ? Le jeu est-il pour autant identique ? Pensez aux collisions avec une balle très rapide.
- Transformez
image(t)en boucle à pas de temps fixe, avecconst DT = 1 / 60et une réserve de temps (voir le cours, section 3). N'oubliez pas de plafonner le temps ajouté à la réserve. - Comptez aussi les mises à jour et affichez les UPS à côté des FPS.
Sans la case : environ 60 FPS et 60 UPS. Avec la case : environ 20 FPS mais toujours 60 UPS. Le jeu va à la même vitesse dans les deux cas, plus saccadé dans le second.
Bonus
- A. Pause avec la touche P :
updaten'est plus appelée, mais le rendu continue. - B. Clignotement : ajoutez un uniform
u_tempsau fragment shader et faites pulser la couleur des balles avecsin(u_temps * 6.0). - C. Meilleur score conservé dans
localStorage. - D. Écran non carré : passez le canvas en 900 × 600. Le joueur devient un rectangle. Corrigez-le avec un uniform
u_aspect(largeur / hauteur) qui divise x dans le vertex shader. Pensez aux bords et aux collisions. La séance suivante règlera ce problème de façon générale.