WebGL pas à pas

Séance 5 · Travaux pratiques

TP 5 · Sprites animés

Une petite scène de jeu de plateforme : un fond, un sol carrelé, des caisses, des pièces qui tournent et un personnage animé que l'on dirige pour ramasser les pièces. Tout est fait avec un seul rectangle et des textures.

Durée 1 h 30Dossier tp5/depart/Cours Textures et sprites

Télécharger le code de départtp5-depart.zip · 13 Ko · à décompresser, puis ouvrir le dossier tp5/depart/ dans VS Code

Objectifs

  • Ajouter des coordonnées UV et lire une texture dans le fragment shader.
  • Créer une texture à partir de données, puis d'une image chargée de façon asynchrone.
  • Utiliser la répétition, la transparence et les planches de sprites.
tp5/depart/ ├── main.js votre code ├── m3.js la bibliothèque du TP 4 (complète) ├── img/caisse.png 128 × 128 ├── img/dalle.png 64 × 64, se répète sans raccord visible ├── img/slime.png planche de 4 images de 64 × 64, fond transparent ├── img/piece.png planche de 8 images de 64 × 64, fond transparent └── lib/gl-utils.js + loadImage(url)

Les positions sont en pixels, avec (0, 0) en haut à gauche (matrice de projection du TP 4). La fonction sprite(tex, x, y, largeur, hauteur) dessine le rectangle unité mis à l'échelle et placé. Au départ, un carré blanc s'affiche en haut à gauche.

Une texture calculée

  • 1.1 et 1.2 Ajoutez l'attribut a_uv, le varying v_uv, l'uniform sampler2D u_image, et lisez la texture : texture(u_image, v_uv) * u_teinte.
  • 1.3 Décrivez a_uv : 2 flottants, stride de 4 flottants, offset de 2 flottants.
  • 1.4 Dans creerTexture, réglez le filtrage et la répétition avec gl.texParameteri (quatre appels).
  • 1.5 Remplissez le damier : le texel (x, y) commence à l'indice (y * N + x) * 4 et occupe 4 octets R, G, B, A (de 0 à 255).
Vérification

Un damier de 8 × 8 cases bien nettes remplace le carré blanc.

Question 1

Passez le filtrage à gl.LINEAR. Que voit-on ? Pourquoi ce rendu ne convient-il pas au pixel art ? Que se passe-t-il si vous oubliez complètement de régler TEXTURE_MIN_FILTER ? (Indice : la valeur par défaut utilise des mipmaps que nous n'avons pas créées.)

Une image

  • 2.1 Complétez creerTexture pour une image : gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source).
  • 2.2 Chargez la caisse : const imgCaisse = await loadImage("img/caisse.png"); puis const texCaisse = creerTexture(imgCaisse);. Le mot-clé await est autorisé car on est dans la fonction async main().
  • 2.3 Dessinez la caisse en 96 × 96 à droite, posée sur le haut du sol : y = SOL - 96.
Vérification

La caisse s'affiche à l'endroit (la diagonale descend du coin haut gauche au coin bas droit).

Question 2

Le cours recommandait UNPACK_FLIP_Y_WEBGL. Ajoutez-le avant texImage2D : l'image se retourne ! Pourquoi n'en a-t-on pas besoin ici ? (Comparez l'orientation de notre repère en pixels, des UV de nos sommets, et l'ordre des lignes dans un fichier image.) Retirez-le ensuite.

Répéter : le sol et le fond

Pour carreler un grand rectangle avec une petite image, on étire les UV au-delà de 1 et la répétition REPEAT fait le reste.

  • 3.1 Ajoutez les uniforms u_uvEchelle et u_uvDecalage au vertex shader et calculez v_uv = a_uv * u_uvEchelle + u_uvDecalage;
  • 3.2 et 3.3 Chargez la dalle (en gl.REPEAT) et envoyez les deux uniforms dans sprite.
  • 3.4 Dessinez le sol de (0, SOL), de 800 × 128 pixels, avec une dalle tous les 64 pixels. Que doit valoir uvEchelle ?
  • Étirez le damier sur tout le fond (de 0 à SOL), avec des cases de 40 pixels.
Vérification

Un sol de 2 rangées de 12,5 dalles, des dalles carrées (non étirées), et un fond en damier sombre.

La transparence

  • 4.1 Chargez texSlime et dessinez-la en 64 × 64 à la position du slime (etat.slime).
  • Constat : on voit la planche entière, écrasée, avec un fond noir autour des personnages.
  • 4.2 Avant de dessiner les objets transparents (après le décor) : gl.enable(gl.BLEND) et gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA). Désactivez le mélange à la fin du rendu.
Question 3

Dessinez le slime avant le sol, à un endroit où ils se chevauchent. Que se passe-t-il ? Pourquoi l'ordre de dessin compte-t-il avec le mélange ?

L'animation

La planche du slime contient 4 images côte à côte : l'image i occupe les u de i/4 à (i+1)/4.

  • 5.2 Écrivez image(i, n) qui renvoie { uvEchelle: [1 / n, 1], uvDecalage: [i / n, 0] }.
  • 5.3 Dessinez le slime avec sprite(texSlime, x, y, 64, 64, image(i, 4)), où i = Math.floor(etat.temps * 8) % 4 (8 images par seconde).
  • 5.4 Chargez texPiece et dessinez chaque pièce de etat.pieces en 48 × 48, animée à 12 images par seconde. Utilisez p.phase pour que les pièces ne tournent pas toutes en même temps.
Vérification

Le slime rebondit sur place et six pièces tournent, chacune à son propre rythme.

Jouer

  • 6.1 Dans update, déplacez le slime avec les flèches (vitesse en pixels par seconde), sans sortir du canvas, et retenez sa direction dans versGauche.
  • 6.2 Le slime doit regarder dans la direction où il va. Ajoutez le paramètre miroir à image : une échelle négative en u (-1 / n) lit l'image de droite à gauche. Quel décalage faut-il alors ?
  • 6.3 Si le slime touche une pièce, elle disparaît et le score augmente (etat.pieces.filter(...) est pratique). Quand toutes sont ramassées, placerPieces().
Vérification

Le slime se déplace en regardant dans la bonne direction et ramasse les pièces ; le compteur augmente.

Bonus

  • A. Saut. Une vitesse verticale vy, une gravité (en pixels/s², par exemple 1400) ajoutée à chaque mise à jour, et une impulsion vers le haut avec Espace quand le slime est au sol. Les pièces hautes deviennent accessibles.
  • B. Teinte. Le slime clignote en jaune pendant 0,3 s quand il ramasse une pièce (paramètre teinte de sprite).
  • C. Défilement. Faites défiler lentement le fond en animant uvDecalage avec le temps.