enchant.js - 4e partie

Rédigé par Fred - - Aucun commentaire

Enfin, on va pouvoir faire des jeux interactifs ! On va continuer de voir un peu des choses sur les sprites mais surtout, on va voir les événements utiles à la souris et au clavier.

L’utilisation du clavier

On a déjà vu l’événement qu’il faut utiliser lorsque l’on entre dans une frame. Il faut faire un

player.addEventListener('enterframe', function(){
//code here
}

Quand on appuie sur une touche, par exemple la flèche droite, il y a en fait deux événements : quand on commence à appuyer sur la touche, et quand on la relâche. Deux événements sont donc émis qui peuvent nous intéresser : RIGHT_BUTTON_DOWN et RIGHT_BUTTON_UP.

Dans le premier exemple, on lie l’action d’appuyer/relâcher une flèche avec l’affichage dans un label de l’événement qui s’est produit.

game.rootScene.addEventListener(direction + 'buttondown', function(){
    status.add(d + ' pushed');
})

Dans le second exemple, on lie l’action à un décalage d’un sprite. Si on ajoute le changement de frame tant qu’on laisse la touche enfoncée, on a un ours qui marche. On utilise également l’attribut .scaleX que l’on valorise à 1 ou -1 suivant le sens de la marche, et on revient à la frame 0 lorsque l’on relâche la touche.

game.rootScene.addEventListener('leftbuttondown', function(){
    player.x -= 5;
    player.frame = (player.frame+1) %3;
    player.scaleX = -1;
});

game.rootScene.addEventListener('leftbuttonup', function(){
    player.frame = 0;
});

Dans ces deux exemples, on ajoute un écouteur sur la rootScene pour les événements d’appui de touche. On peut également rester sur l’écoute de l’entrée dans une frame et tester si une touche est enfoncée avec game.input. Cet objet correspond en fait à un dictionnaire du type {left: true, up: false, right: false, down: false}.

L’exemple 3 reprend le code du second exemple, en utilisant game.input :

game.rootScene.addEventListener('enterframe', function(){
    if (game.input.left) {           
        player.x -= 5;
        player.frame = (player.frame+1) %3;
        player.scaleX = -1;
    }
    //...
}

Noter la différence entre les deux exemples si on appuie sur droite et gauche en même temps. Pour contourner le problème, on peut utiliser :

if (game.input.left && !game.input.right) {

Comment faire en revanche pour les autres touches ? On peut soit construire à la main des fonctions qui émettent le bon événement, soit utiliser la méthode game.keybind(key, button).

Dans tous les cas, il faut connaître le code ASCII de la touche sur laquelle on veut appuyer. Par exemple, si on veut pouvoir appuyer sur m pour afficher un menu, on peut utiliser ='M'.charCodeAt(0)= qui nous retourne 77. Ou alors, une recherche sur duckduckgo, et on trouve que le + du clavier numérique a comme code 107. Après avoir fait

game.keybind(77, 'm');
game.keybind(107, 'plus');

on peut écouter quatre nouveaux événements : mbuttondown/mbuttonup et plusbuttondown/plusbuttonup. game.input est également enrichi avec m et plus qui sont valorisés à vrai/faux.

Dans l’exemple 4, deux méthodes sont utilisées pour gérer les touches. Dans des addEventListener('enterframe',… on teste game.input.m/game.input.q pour afficher/quitter le menu. Une fois ce menu affiché, on peut appuyer sur le +/- du clavier numérique pour augmenter/diminuer la vitesse de déplacement du sprite (la valeur affichée correspond à combien de pixels le sprite se déplace). Pour gérer le +, on utilise menuScene.addEventListener('plusbuttondown',… : à chaque appui sur la touche, le compteur de vitesse est incrémenté. Pour le -, on teste dans menuScene.addEventListener('enterframe',… si game.input.moins est vrai. Du coup, comme il se passe plusieurs frames pendant lesquelles la touche est enfoncée, le compteur décroît trop rapidement.

On peut bien sûr associer plusieurs touches au même bouton : game.keybind(68, 'right'); permet d’associer la touche d au bouton droit. C’est utile si on veut pouvoir utiliser q,s,d,z pour se déplacer.

L’utilisation de la souris

Utiliser une souris ou toucher l’écran de sa tablette/son smartphone, c’est géré de la même manière. Et ça n’est pas très différent de l’appui sur une touche : quand on touche/clique l’écran l’événement TOUCH_START est émis. Quand on lâche le bouton de la souris/l’écran, c’est TOUCH_END qui émis. La où ça change, c’est qu’on peut déplacer le curseur/doigt tout en appuyant (l’événement TOUCH_MOVE est alors émis) et surtout, il faut récupérer où le curseur est. Pour cela, il faut passer un argument à fonction exécutée lors du touch — l’événement — qui a comme attributs le x/y où a eu lieu l’événement :

game.rootScene.addEventListener('touchstart', function(event){
        player.x = event.x;
        player.y = event.y;
});

Dans l’exemple 5, on met l’écouteur sur touchstart seulement, et dans l’exemple 6, sur le touchmove. Et ça ne fait pas vraiment ce à quoi on s’attendait : pour utiliser le touchmove, il faut en effet que l’écouteur soit sur l’objet que l’on veut déplacer ! L’exemple 7 corrige cela.

Premier « jeu »

Comme promis, on va faire un premier vrai jeu. Pas transcendantal certes, mais un jeu quand même : cliquer sur toutes les boules rouges en évitant les bleues.

On commence par initialiser les variables dont on aura besoin :

var HEIGHT = 320;
var WIDTH = 320;

var ICON0 = '../images/icon0.png';
var NB_BONUS = 10;
var NB_MALUS = 15;
var FPS = 24;

Le fait de mettre le chemin de l’image en variable permet de faire game.preload(ICON0); puis plus tard this.image = game.assets[ICON0];. Et si on veut changer d’image, on n’a qu’un endroit à modifier.

On ajoute ensuite les boules rouges/bleues dans le game.onload

for (var i = 0; i < NB_BONUS; i++)
    var player = new Item(19);
for (var i = 0; i < NB_MALUS; i++)
    var player = new Item(20);

var lbl_score = new Label('');
game.rootScene.addChild(lbl_score);

ainsi qu’un label qui nous servira à afficher le nombre de boules restantes.

On instancie quelques variables puis on regarde à chaque entrée de frame si :

  • on vient d’écouler une seconde (24 frames sont passées depuis la dernière fois)
if (game.frame%FPS===0){
    timer--;
}
  • on a eu toutes les boules, ou si on n’a plus de temps. Dans ce cas là le jeu doit s’arrêter
if (game.score===0 || timer===0){
    game.running = false;
}

puis, si le jeu continue, on met à jour le label avec le score, et sinon, on regarde pourquoi le jeu est terminé :

if (game.running){
    lbl_score.text = timer+"<br/>Plus que " + game.score;
}else{
    if (game.score===0){
        lbl_score.text = "Bravo !!!";
    }else{
        lbl_score.text = "Il en restait encore " + game.score;
    }
    game.stop();
}

La nouvelle chose par rapport aux exemples qu’on a vu jusqu’à présent, c’est l’utilisation d’une classe pour créer toutes les boules. Rien de bien différent, si ce n’est que comme on utilise des objets, il y a des this un peu de partout.

Item = enchant.Class.create(Sprite, {
    initialize: function(whichFrame) {
        var game = enchant.Game.instance;
        Sprite.call(this, 16, 16);        
        this.image = game.assets[ICON0];
        this.frame = whichFrame;
        this.x = 16 + rand(WIDTH - 32);
        this.y = 16 + rand(HEIGHT - 32);
        this.angle = (10 + rand(70))* Math.PI/180;
        this.speed = 1 + rand(5);
        this.vx = this.speed * Math.cos(this.angle);
        this.vy = this.speed * Math.sin(this.angle);

Quand on touche la boule, Si c’est un bonus, on a utilisé la frame 19, et donc on décrémente le nombre restant, sinon, on arrête le jeu :

this.addEventListener('touchstart', function(){
    if (this.frame===19){//bonus
        game.score--;
        game.rootScene.removeChild(this);
    }else{
        game.running = false;
    }

});

Et à chaque fois qu’on rentre dans une frame, on effectue le déplacement :

this.addEventListener('enterframe', function() {
    this.y += this.vy;
    this.x += this.vx;
    if (this.y > (HEIGHT-16) || this.y <0) {
        this.vy = -this.vy;
    }
    if (this.x > (WIDTH-16) || this.x <0) {
        this.vx = -this.vx;
    }
});

Bien entendu, ne pas oublier de rajouter la boule à la rootScene : game.rootScene.addChild(this);.

Une autre façon de faire la classe, de façon un peu plus orienté objet, est d’utiliser les méthodes ontouchstart et onenterframe. L’exemple 9 reprend le code de l’exemple précédent en changeant la façon de définir les écouteurs :

ontouchstart: function(){
    if (this.frame===19){//bonus
        this.game.score--;
        this.game.rootScene.removeChild(this);
    }else{
        this.game.running = false;
    }
},

La prochaine fois

Avec tout ce que l’on vient de voir, vous pouvez déjà faire pas mal de jeux différents. Dans le tutoriel suivant, on va voir comment faire pour détecter simplement qu’un sprite est en contact avec un autre. On pourrait déjà le faire avec tout plein de if, mais enchantjs permet de simplifier tout ça.


Écrire un commentaire

Quelle est la première lettre du mot qfmof ?