Après tout, quel jeu digne de ce nom ne comporte pas d’ennemis pour nous empêcher d’arriver au but ?
Un Sprite custom
On va donc commencer, comme pour les éléments personnalisés, par mettre en place les éléments graphiques :
Q.sheet('my_wolf', 'wolf-sprite.png', { tileW: 30, tileH: 23 }); // N'oubliez pas de pré charger l'image qui correspond !
Roulement de tambour
Mesdames et messieurs… le loup !
Oui, bon, d’accord, un tas de gribouillis ça ne ressemble pas vraiment à un loup, mais je ne sais pas dessiner un loup au repos…
On n’oublie pas de gérer les images des mouvements (attention, il y a quelques petites nouveautés) :
Q.animations('my_wolf', {
stand: { frames: [0], rate: 1/60, flip: false, loop: true },
walk_left: { frames: [1], rate: 1/60, flip: false, loop: true },
walk_right: { frames: [1], rate: 1/60, flip: 'x' }, // Eh oui, pour réduire le nombre d'images, il suffit de retourner celle que l'on souhaite !
dying: { frames: [1], rate: 1/60, flip: 'y' }, // Et ça fonctionne aussi pour une symétrie verticale !
});
Puis on va pouvoir créer un Sprite customisé :
Q.Sprite.extend('Wolf', {
init: function(p) {
this._super(p, {
sheet: 'my_wolf',
sprite: 'my_wolf',
vx: -100, // On part vers la gauche par défaut
collisionMask: Q.SPRITE_DEFAULT
});
this.add('2d, animation, aiBounce'); // On ajoute un peu d'intelligence artificielle et d'animations (aiBounce sert à gérer les collisions avec les murs, pour faire demi-tour automatiquement)
}
});
Vous pouvez aussi étendre vos propres Sprites : essayez donc Q.Wolf.extend('BabyWolf')
, pour voir !
Gérer les déplacements
Rien de bien compliqué, je vous rassure, puisque l’IA s’occupe du plus gros du travail. Il s’agit seulement d’éviter que notre loup se retrouve hors de la grille et de gérer ses animations (états).
On va donc toucher à notre objet Wolf
pour définir la méthode step
:
step: function (dt) {
if (this.p.x <= 0 || this.p.x >= Q.width) {
this.p.vx = -this.p.vx; // S'il va au bord, on inverse sa vitesse pour qu'il fasse demi-tour
}
if (this.p.vx > 0) {
this.p.direction = 'right';
}
else if (this.p.vx < 0) {
this.p.direction = 'left';
}
else {
this.p.direction = null;
}
if (this.p.direction) {
this.play('walk_' + this.p.direction);
}
else {
this.play('stand');
}
}
Et voilà. Pas trop perdu ? Bon, maintenant il va falloir voir ce que ça donne en vrai, quand même !
Les ajouter sur la grille
Pour simplifier le tout, j’ai pris des valeurs plus ou moins aléatoires, histoire d’éviter de faire trop de calculs. Mais rien ne vous empêche de faire quelques calculs chez vous, hein !
Bon, retour dans notre scène, où l’on va venir ajouter nos beaux petits loups à différents endroits et avec différentes propriétés :
stage.insert(new Q.Wolf({ x: tiles.p.w/2, y: tiles.p.h/2 })); // Au milieu de la carte
stage.insert(new Q.Wolf({ x: tiles.p.w/4, y: tiles.p.h - (player.p.cy + tiles.p.tileH) })); // En bas à gauche, pas loin du joueur (sinon ce serait trop simple)
stage.insert(new Q.Wolf({ x: tiles.p.w - Q.sheets['my_sheepfold'].tileW * 2, y: 0, vy: 100 })); // Celui-ci part de la bergerie, attention !
Bon, bah ce n’était pas si compliqué, si ?
Maintenant, il va falloir s’occuper du plus important…
Gérer les collisions
Et voilà, on y est : le cœur du sujet !
On va enfin pouvoir comprendre à quoi servent les fameux collisionMask
que l’on a utilisés, jusqu’ici sans rien dire (ni comprendre) !
Eh bien pour faire simple, ça permet de définir plusieurs choses, pour le moteur sache quoi faire :
- Si un sprite bute contre un autre (
hit
) ou lui tombe dessus (bump.top
) - Ce qu’il faut faire en cas de collision : empêcher le mouvement ou autoriser la superposition
Pour l’instant, c’est à peu près tout ce qu’il faut comprendre. Pour gérer tout cela plus facilement, Quintus propose quelques masques par défaut :
SPRITE_NONE
qui sera, je pense rarement utilisé pour vos projetsSPRITE_DEFAULT
qui correspond aux objets courants qui peuvent entrer en collision et bougerSPRITE_PARTICLE
qui, comme son nom l’indique, sert à gérer des particulesSPRITE_ACTIVE
SPRITE_FRIENDLY
SPRITE_ENEMY
qui sert surtout pour les ennemis (comment ça c’était évident ?)SPRITE_POWERUP
, pratique pour les champignonsSPRITE_UI
, pour tout ce qui sert à l’interfaceSPRITE_ALL
qui regroupe tous les types précédents
Tous correspondent à de simples entiers, vous pouvez donc définir vos propres masques, par exemple en mettant dans votre code Q.SPRITE_DOOR = 8;
pour définir le masque d’une porte.
Passons à la pratique !
Pour gérer les collisions de notre ennemi, il va falloir rajouter quelques lignes dans le constructeur :
this.on('bump.left, bump.right, bump.bottom', function(collision) {
if(collision.obj.isA('Player')) { // S'il rentre dans un joueur, la partie est finie
Q.stageScene('endGame', 1, { label: 'Perdu !' }); // Tiens, un paramètre est passé à notre scène, ça vous rappelle quelque chose ?
collision.obj.destroy(); // On détruit le joueur, le jeu n'étant pas en pause, pour éviter de lancer plusieurs fois l'écran de fin
}
});
this.on('bump.top', this, 'die'); // Si le joueur lui tombe dessus, on lance la méthode `die`
Et donc, la méthode die()
de notre loup :
die: function (collision) {
this.p.vx = this.p.vy = 0; // Pas bouger !
this.play('dying'); // Fais le mort
(function (wolf) {
setTimeout(function() {
wolf.destroy(); // On attend un peu puis on le détruit (il ne gênera pas les autres loups ou le joueur, comme ça)
}, 300);
})(this);
collision.obj.p.vy = -300; // On fait rebondir le joueur
}
Et voilà, vous savez maintenant gérer des collisions et agir en conséquence !
Et voilà ! Maintenant vous avez donc un niveau sur lequel vous déplacer et des ennemis qu’il vaut mieux éviter.
Vous n’avez plus qu’à vous amuser un peu, tester tout ça et voir ce que vous pouvez en tirer.