Retour au parcours HTML5

Gérez les images et les animations de vos jeux HTML5 comme un pro

Dans cet atelier apprenez les techniques des pros pour gérer le chargement et l’affichage de vos images dans un jeu Web codé en HTML5.

Tout d’abord nous allons coder un « Image Loader » pour pouvoir charger les images de votre jeu en une seule fois et afficher une barre de progression. Vous contournez ainsi le problème classique des images en HTML5 : qu’elles ne se chargent pas immédiatement et provoquent un bug dans votre jeu !

 

Ensuite nous construirons une classe « Sprite » super pratique, capable d’afficher des animations image par image, issues d’une « sprite sheet ». Ainsi, vous pourrez regroupez vos images au sein d’une seule image, comme le font les pros, et construisez puis affichez n’importe quelle séquence d’images afin de créer des animations.

Cette structure sera réutilisable dans vos propres jeux vidéo et elle est parfaitement évolutive pour coller à vos propres besoins spécifiques.

Adhérez aujourd'hui

Gamecodeur c’est à partir de 8 € / mois (facturé annuellement)

Comments (20)

Excellent tuto !

(pour infos, tu peux taper directement dans ton invité de commande « cmd » pour executer un nouveau shell Dos ou « powershell » pour un nouveau shell powershell, ça va ouvrir une instance, au lieu de chercher partout l’un ou l’autre.
Exit va fermer ces instances).

Merci

Bonjour David,

Super Atelier que tu as fais là , tes conseils sont précieux et je gagne du temps en suivant tes cours.
J’ai progressé mais j’ai encore beaucoup de choses à apprendre en JavaScript / HTML 5.
Dans le chapitre chargement des images (avec les cartes)
j’ai eu cette erreur :
Uncaught DOMException: Failed to execute ‘drawImage’ on ‘CanvasRenderingContext2D’: The HTMLImageElement provided is in the ‘broken’ state.
at Sprite.draw (http://127.0.0.1:5500/sprite.js:10:14)
at http://127.0.0.1:5500/game.js:195:16
at Array.forEach ()
at draw (http://127.0.0.1:5500/game.js:194:16)
at run (http://127.0.0.1:5500/main.js:14:5)

dans le fichier game.js :
for (let image of Object.values(imageLoader.getListImages())) {
let mySprite = new Sprite(image);
mySprite.x = Math.random() * 800;
mySprite.y = Math.random() * 600;
lstSprites.push(mySprite);
}

la classe Sprite ne prend pas pas en 1er parametre une reference d’objet mais plutôt un path non ?
j’ai donc remplacé ceci :
let mySprite = new Sprite(image);
par
let mySprite = new Sprite(image.src);

ou bien on peut aussi changer le début de la boucle for :
for (let image of imageLoader.getLstPaths()) {

en rajoutant cette methode dans la classe imageLoader:
getLstPaths() {
return this.lstPaths;
}

Si cela peut aider d’autres élèves qui ont eu ce problème..

en fait je crois que c’est parceque le constructor de la class Sprite change légèrement en cours de route, j’ai dû manquer l’information mais en retombant dessus un peu plus loin j’ai remarqué le changement (vidéo/ Nettoyage du projet :3min20)

– supprimer la ligne this.img.src = pSrc
– et remplacer this.img = new Image() // par this.img = pSrc

c’était mon erreur en tout cas

Vous êtes des boss ;), moi aussi j’ai loupé l’information et grâce au differente solution que vous avez apportées, j’ai chercher le lien et ça confirme ce que dit « piopio65 » « la classe Sprite ne prend pas en 1er parametre une reference d’objet mais plutôt un path ».

Rappel: path = chemin.

Merci à vous.

Petite astuce pour éviter de copier/coller 50 fois la partie imageLoader.add(" :
Avec Visual Studio Code il suffit de faire un clic molette avant le début de la première image puis de descendre jusqu’en bas tout en restant avant le début du nom de l’image puis de coller avec CTRL+V.

Le même principe s’applique pour coller à la fin la partie ")

Si jamais quelqu’un veux un truc un tout petit peut plus propre.

Copiez le nom des images comme David la fait mettez tout dans un tableau et après faites une
bloucle for of avec un string interpolation

exemple :

const imagesArray = [ tout les noms des "images.png" ]

for (let element of imagesArray) {
imageLoader.add(`images/${element}`);
}

Bonjour David merci pour le cours, cependant pour les images au lieu de faire comme tu fais j’ai préféré utiliser le fichier liste.txt qui est généré pour charger les images.

Si ça intéresse certain, j’ai procédé comme ça :

Ajout d’une méthode addFolder et je fais un request du fichier

async addFolder(pPath, pFileList = "list.txt"){
//use html request for read file, require('fs') doesn't work
let rawLines = await this.#requestFile(pPath, pFileList);
let lines = rawLines.split(/\r?\n/);
lines.forEach(line => {
if (line.endsWith(".png") || line.endsWith(".jpg"))
this.add(pPath + "/" + line);
});
}

#requestFile(pPath, pFile){
return new Promise((resolve, reject) => {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(s, e) {
if(xmlhttp.status == 200 && xmlhttp.readyState == 4){
resolve(xmlhttp.responseText);
}
};
xmlhttp.open("GET",pPath + "/" + pFile, true);
xmlhttp.send();
});
}

Et ensuite dans le game.js je fais :

async function load(){
await imageLoader.addFolder("images/card");
imageLoader.start(startGame);
}

Comme ça même si j’ajoute des images au dossier, vu que je charge tout le contenu, j’ai juste besoin de généré un nouveau liste.txt

Super tuto!!! J’avais déja remarque ce problem de chargement en paralelle mais ce ImageLoader est vraiment une solution efficace merci beaucoup.
Pour le loading des images je comprends qu’il faille le faire en manuel mais n’y aurait-il pas une solution sur le server un script PHP qui récupérer tous les images et genère un fichier js par example?

Pour les animations il y a t’il une raison particulière pour avoir utilisé la taille en pixel d’une frame du spritesheet?
Je voudrais utiliser l’inverse setTileSheet.. setTileSheet(NbColmn, NbRow) de la j’aurai déduit la taille en pixel this.width/NbColmn this.height/NbRow…
Est-ce possible ? Non recommandé?

Tu fais comme tu veux. Perso je trouve plus naturel de donner la taille des tiles, vu que c’est la base pour moi (peu importe l’image qui les contient). Mais ton idée est pas plus mauvaise que la mienne, l’important est que ça colle à ton raisonnement.

Bonjour, Je suis bloquée à la dernier vidéo et je comprend vraiment pas ou j’ai raté (Et sa fait 1h que je repasse mon code et celui de la vidéo en boucle pour voir une différence °w°)

Dans la console lorsque j’inspecte sur google chrome il me marque cette erreur:

sprite.js:40 Uncaught TypeError: Cannot read properties of undefined (reading ‘0’)
at sprite.js:40:65
at Array.forEach ()
at Sprite.startAnimation (sprite.js:37:25)
at ImageLoader.startGame [as callBack] (game.js:70:18)
at ImageLoader.imageLoaded (ImageLoader.js:45:18)

Et la voici mon sprite.js (de la ligne 9 à 43 et 56 à 74)

this.currentFrame = 0;
this.currentAnimation = null;

this.tileSize = {
x:0,
y:0
}
this.tileSheet = false;

this.animations = [];
}

addAnimation(pName, pFrames, pSpeed, pLoop = true) {
let animation = {
name: pName,
frame: pFrames,
speed: pSpeed,
loop: pLoop
}
this.animations.push(animation);
}

startAnimation(pName) {
if (this.currentAnimation != null) {
if (this.currentAnimation.name == pName) {
return;
}
}
this.animations.forEach(animation => {
if (animation.name == pName) {
this.currentAnimation = animation;
this.currentFrame = this.currentAnimation.frames[0];
}
});
}

draw(pCtx) {
if (!this.tileSheet) {
pCtx.drawImage(this.img, this.x, this.y);
}
else {
let nbCol = this.img.width / this.tileSize.x;
let c = 0;
let l = 0;

l = Math.floor(this.currentFrame / nbCol);
c = this.currentFrame – (l * nbCol);

let x = c * this.tileSize.x;
let y = l * this.tileSize.y;

pCtx.drawImage(this.img, x, y, this.tileSize.x, this.tileSize.y, this.x, this.y, this.tileSize.x * this.scaleX, this.tileSize.y * this.scaleY);
}
}
}

Merci de bien vouloir m’aider et de m’accorder une partie de votre temps ^^’
(Au cas ou je met la function « startGame » de game.js)

function startGame() {
console.log(« StartGame »)

lstSprites = [];

// Player
let imagePlayer = imageLoader.getImage(« images/player.png »);
let spritePlayer = new Sprite(imagePlayer);
spritePlayer.setTileSheet(30, 16);
spritePlayer.x = 25 * 4;
spritePlayer.currentFrame = 12;
spritePlayer.setScale(4, 4);
spritePlayer.addAnimation(« TURNRIGHT », [0, 1, 2, 3, 4, 5, 6, 7, 8], 10, false);
spritePlayer.addAnimation(« TURNUP », [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], 10, false)
spritePlayer.startAnimation(« TURNUP »);

// Red Ennemy
let imageEnnemy = imageLoader.getImage(« images/enemyred.png »);
let spriteEnemy = new Sprite(imageEnnemy);
spriteEnemy.setTileSheet(24, 24);
spriteEnemy.currentFrame = 3;
spriteEnemy.setScale(4, 4);

lstSprites.push(spritePlayer);
lstSprites.push(spriteEnemy);

gameReady = true;
}

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.

Dialoguez avec les autres membres de la gamecodeur school.

Accédez maintenant à notre serveur Discord privé : Entraide, Game Jams, Partage de projets, etc.

Vous devez être membre de la Gamecodeur School Premium pour être autorisé à accéder au serveur.