Animer et déplacer un sprite en javascript
Cet article fait suite à Coder un sprite javascript html5 en y ajoutant cette fois le déplacement du sprite javascript html5 au clavier.
Le mouvement au clavier
Le mouvement du sprite javascript html5 est déclenché en appuyant sur la touche q pour aller à gauche et d pour aller à droite.
C’est par le biais du gestionnaire d’événements javascript que nous allons déplacer notre sprite javascript html5.
L’événement en question est keypress qui se déclenche lorsqu’une touche est pressée. Lorsqu’une touche est pressée, la fonction passée en paramètre est appelé avec en paramètre un objet avec différentes propriétés dont le code de la touche pressée.
En testant ce code, nous savons si oui ou non les touches q et d sont pressées.
Ce qui donne en javascript.
document.addEventListener("keypress", function(e) {
if (e.keyCode == 100 ) {
// le touche d est pressée
} else if (e.keyCode == 113 ) {
// le touche q est pressée
}
});
Nous y reviendrons après.
Le détail de l’animation
Voici le détail des mouvements de l’animation.
Tous ces mouvements constituent un seul et unique fichier image de 108 pixels sur 46 qui va servir à produire l’animation. Il y a six mouvements répartis sur les 108 pixels, chaque mouvement faisant une taille de 18 pixels de large sur 46 de haut.
La taille de 18 pixels est obtenue en divisant la largeur du fichier image par le nombre total d’images (108/6=18).
Pour produire l’animation, les images doivent se succéder les unes aux autres dans l’ordre de gauche à droite. Lorsque la dernière image est affichée, il faut reprendre l’animation depuis le début. Tout ceci en boucle.
Il faut donc découper la bonne image dans le ficher image avant de l’afficher. Les images étant placées dans le bon ordre d’affichage dans ce même fichier, la tâche en est facilitée.
Pour résumer, vous n’affichez pas toute l’image mais seulement une coupe, celle relative à l’image de l’animation puis vous passez à la coupe suivante: 1, puis 2, puis 3, puis 4, puis 5, puis 6, puis 1, puis 2, etc… en boucle.
Le canvas html5
Pour l’exercice, vous initialisez un canvas html5 dans une page html.
Et vous récupérer ce même canvas html5 depuis javascript.
<html>
<body>
<canvas id="spritedemo" style="border:1px solid black;width:1600px;height:1200px;" width="800" height="600"></canvas>
</body>
</html>
<script>
{
let canvasGameContext = document.getElementById("spritedemo").getContext("2d");
}
</script>
Ce canvas html5 de 800 sur 600 pixels sera le support d’affichage du sprite.
Le code existant
Repartez du code existant sur le sprite javascript en y ajoutant tout le nécessaire pour que l’animation prenne vie et en gardant la compatibilité avec ce que vous auriez créé avant de mettre à jour cet objet javascript.
const game = {};
game.display = {
sprite : {
image : null,
width : null,
height : null,
posX : null,
posY : null,
setSize : function(width, height) {
this.width = width;
this.height = height;
},
render : function (canvasContext, x, y) {
this.posX = x;
this.posY = y;
canvasContext.drawImage(this.image, this.posX, this.posY);
}
},
createSprite : function(imagePath) {
var sprite = Object.create(this.sprite);
sprite.image = new Image();
sprite.image.src = imagePath;
return sprite;
}
}
Ce qui évolue
La méthode render affiche le sprite dans le canvas html5 par le biais de la fonction drawImage. C’est donc ici qu’il faut modifier le code existant pour sélectionner et afficher la partie coupée qui correspond à une image de l’animation.
Par chance, la fonction drawImage a plusieurs signatures dont l’une permet de couper et afficher une partie d’une image passée en paramètre.
La signature.
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
Où :
– img : l’image;
– sx et sy : les coordonnées du point à partir duquel l’image sera coupée;
– swidth et sheight : la largeur et la hauteur de la coupe en pixels;
– x et y : la position de la coupe dans le canvas html5;
– width et height : la hauteur et la largeur de l’image (la coupe) affichée dans le canvas html5.
Pour les deux derniers paramètres, si les valeurs sont supérieures aux valeurs de la coupe, l’image sera étirée.
Inversement si les valeurs sont inférieures aux valeurs de la coupe, l’image sera réduite.
Au préalable, vous devez stocker le nombre d’images qui composent l’animation dans une variable dédiée numberOfFrames ainsi que le numéro de l’image frameIndex qui est affichée.
const game = {};
game.display = {
sprite : {
image : null,
width : null,
height : null,
posX : null,
posY : null,
numberOfFrames : null,
frameIndex : 0,
.....
}
}
La propriété frameIndex est une variable à usage interne, elle n’est pas exposée ou manipulée par l’utilisateur de la classe. Son rôle est de stocker le numéro de l’image affichée.
L’ajout de la propriété numberOfFrames nécessite de modifier la fonction createSprite.
createSprite : function(imagePath, numberOfFrames) {
let sprite = Object.create(this.sprite);
sprite.image = new Image();
sprite.image.src = imagePath;
sprite.numberOfFrames = numberOfFrames;
return sprite;
}
Pour faire les coupes d’images successives, il faut faire varier sx et sy. Dans le cas présent, uniquement sx puisque toutes les images sont placées horizontalement les unes derrières les autres. sy prend uniquement la valeur 0.
La valeur de sx peut se déduire du numéro d’image ou plutôt du numéro d’image moins 1 (this.frameIndex) multiplié par la largeur de l’image (this.width / this.numberOfFrames):
Pour ce qui est de la taille de la coupe, elle est toujours la même :
– la taille totale de l’image (width) divisée par le nombre d’images (numberOfFrames) donne la largeur de la coupe;
– la hauteur de la coupe est toujours la même puisque toutes les images sont placées horizontalement les unes derrières les autres.
Et c’est la même chose pour la largeur et la hauteur de l’image affichée. Les paramètres d’appel de la fonction drawImage deviennent.
canvasContext.drawImage(
this.image, // l'image
this.frameIndex * this.width / this.numberOfFrames, // coordonnées x du début de la coupe
0, // coordonnées y du début de la coupe
this.width / this.numberOfFrames, // largeur de la coupe
this.height, // hauteur de la coupe
this.posX, // coordonnées x du sprite animé
this.posY, // coordonnées y du sprite animé
this.width / this.numberOfFrames, // largeur de l'image affichée
this.height // hauteur de l'image affichée
);
let mySprite = game.display.createSprite("walk.png", 6);
mySprite.setSize(108,46);
setInterval(function() {
mySprite.render(canvasGameContext,0,0);
}, 50);
En l’état, il n’y a pas d’animation, seule la première image est affichée : sx ne varie pas. C’est là qu’entre en jeu la propriété frameIndex: en la faisant varier de 0 à 5, vous faites varier sx aussi.
Ajoutez une nouvelle fonction update à la classe sprite qui ne fait qu’incrémenter de 1 le frameIndex et boucler sur les valeurs de 0 à 5.
const game = {};
game.display = {
sprite : {
image : null,
width : null,
height : null,
posX : null,
posY : null,
numberOfFrames : null,
frameIndex : 0,
.....
update : function() {
this.frameIndex = (this.frameIndex+1)%6;
},
}
let mySprite = game.display.createSprite("walk.png", 6);
mySprite.setSize(108,46);
setInterval(function() {
mySprite.update();
mySprite.render(canvasGameContext,0,0);
}, 50);
let mySprite = game.display.createSprite("walk.png", 6);
mySprite.setSize(108,46);
setInterval(function() {
mySprite.update();
canvasGameContext.clearRect(0,0,800,600);
mySprite.render(canvasGameContext,0,0);
}, 50);
Le travail est terminé.
Simple information, l’image n’ayant pas le temps de se charger en mémoire, votre sprite html5 pourrait ne pas s’afficher. Je vous invite donc à procéder au chargement des images avant d’afficher un sprite html5. L’article Loader d’images en javascript vous montre comment faire.