Licence CC BY-NC-SA

Des ennemis, parce qu'il en faut

Dernière mise à jour :

Après tout, quel jeu digne de ce nom ne comporte pas d'ennemis pour nous empêcher d'arriver au but ? :P

Un Sprite custom

On va donc commencer, comme pour les éléments personnalisés, par mettre en place les éléments graphiques :

1
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 !

Sprite du 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) :

1
2
3
4
5
6
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é :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 ! :P

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 :

1
2
3
4
5
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 ? :D

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 projets
  • SPRITE_DEFAULT qui correspond aux objets courants qui peuvent entrer en collision et bouger
  • SPRITE_PARTICLE qui, comme son nom l'indique, sert à gérer des particules
  • SPRITE_ACTIVE
  • SPRITE_FRIENDLY
  • SPRITE_ENEMY qui sert surtout pour les ennemis (comment ça c'était évident ?)
  • SPRITE_POWERUP, pratique pour les champignons
  • SPRITE_UI, pour tout ce qui sert à l'interface
  • SPRITE_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 :

1
2
3
4
5
6
7
8
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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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.