Introduction à la création d’un jeu HTML5 avec ImpactJS

impact

Pour ce deuxième article de la série Introduction à la création d’un jeu HTML5, nous allons nous pencher sur le cas d’ImpactJS.

Vous pouvez dès maintenant jouer au jeu que nous allons créer dans cet article.

J’ai également créé un jeu nommé Dreamless à partir de la base de cet article.

Il faut savoir qu’Impact est payant ($99).

J’espère que cet article vous aidera à prendre la décision de l’acheter ou non.

Comme il est payant, je ne peux pas distribuer la source d’Impact dans le répertoire GitHub, pour faire fonctionner le jeu et les exemples en local il suffira simplement d’ajouter la source d’Impact dans le dossier /lib.

Impact est fourni avec :

Impact dispose de son propre système de module, ce qui permet aux développeurs d’organiser le code de leur jeu sans lib externe, mais également de concevoir et partager facilement des plugins et snippets. La communauté a d’ailleurs créé un site dédié à ce type de ressources : Point Of Impact.

Cette fois, je vais décortiquer la réalisation d’un jeu de plateforme Step-By-Step. J’ai choisi ce type de jeu car j’ai constaté qu’il est l’un des plus adaptés à Impact.

Pour les sprites et les tiles nous utiliseront ceux gracieusement offerts par Jesse Freeman.

En parlant de Freeman, sachez qu’il a écrit un livre qui m’a beaucoup inspiré pour la rédaction de cet article, si vous souhaitez aller plus loin je vous conseille fortement de le lire !

Vous trouverez ici, tous les assets utilisés cet article.

Sommaire :

La structure par défaut

Lorsque l’on dé-zip Impact, on constate qu’un jeu vide avec une structure prête à l’emploi sont présents.

  • media/ -> Les assets du jeu (images et sons)
  • lib/ -> Le jeu et le moteur
    • lib/game/
      • lib/game/entities/
      • lib/game/levels/
    • lib/impact/ -> Le moteur
    • lib/weltmeister/ -> L’éditeur de niveaux

Il est préférable d’utiliser cette convention mais rien ne vous empêche de réorganiser ça à votre sauce.

Pour lancer le jeu, ouvrez index.html dans votre navigateur.

Retour au sommaire.

Les modules

Voici comment utiliser le système de modules :

ig.module(
    'game.my-file'
)
.requires(
    'impact.game',
    'impact.image',
    'game.other-file'
)
.defines(function(){
    // Code du module
});

La définition d’un module se fait en 3 parties :

  1. Le nom du module dans ig.module()
  2. Les dépendances dans .requires()
  3. Le code du module dans .defines()

Retour au sommaire.

Les classes

Impact utilise un système d’héritage classique basé sur le celui de John Resig.

La méthode .extend() permet de créer une nouvelle Class à partir d’une classe parente :

// Création d'une classe à partir de celle de base (ig.Class)
var Person = ig.Class.extend({});

// Création de la classe Ninja à partir de la classe Person
var Ninja = Person.extend({});

La méthode .init() est automatiquement appelée par le constructeur de la classe, c’est à dire à chaque fois que vous créez une instance :

var InitTest = ig.Class.extend({
    init: function(param) {
        console.log("Init appelé avec "+param);
    }
});

new InitTest("ZOMG"); // => Init appelé avec ZOMG

La méthode .parent() permet d’appeler la méthode actuelle de la classe parente :

// Création de la classe "Person"
var Person = ig.Class.extend({
    name: "",
    init: function(name) {
        this.name = name; // On affecte le nom passé en paramètre
    }
});

// Création de la classe "Ninja" qui hérite de la classe "Person"
var Ninja = Person.extend({
    init: function(name) {
        // On appelle la fonction parente
        this.parent("Ninja: "+name);
    }
});

// Création d'un objet "Person"
var person = new Person("Generic Person");
person.name; // => Generic Person

// Création d'un objet "Ninja"
var ninja = new Ninja("John Resig");
ninja.name; // => Ninja: John Resig

Retour au sommaire.

Editeur de niveau – Création du niveau

Attaquons la partie fun : créer un premier niveau à l’aide de l’éditeur de niveau (Weltmeister) et les tiles de Jesse Freeman.

Pour que Weltmeister fonctionne il faut le faire tourner sur un serveur apache/php.

Des alternatives sont disponibles sur le getting started : ruby, nodejs, python, …

Lancez votre serveur et ouvrez la page de l’éditeur de niveau : http://localhost/impact/weltmeister.html.

Pour l’instant vous avez un niveau vierge untitled.js.

01-weltmeister

Nous allons créer un nouveau calque pour peindre les tiles de notre niveau.

Cliquez sur le + juste à coté de Layers dans la barre de droite :

02-weltmeister

Une liste de paramètres apparaît, renommez ce calque : main.

03-layer-settings

Ensuite on va chercher notre tileset, cliquez sur le champ tileset et naviguez jusqu’à ce dernier.

04-tileset

Il faut spécifier la taille d’une tile (Tilesize) en pixels et le nombre de tiles sur ce calque :

05-layer-settings

Cliquez sur Apply Changes :

06-layer-settings

Maintenant nous allons peindre le niveau, cliquez dans la map (vide pour l’instant) et appuyez sur la barre d’espace, puis sélectionnez une tile :

07-tile-selection

Vous pouvez placer vos tiles pour créer un niveau :

08-map

Vous pouvez zoomer/dezoomer avec la molette de la souris et déplacer la vue en maintenant appuyé le click droit.

Si vous voulez effacer une tile, appuyez sur la barre d’espace et sélectionnez le carré orange vide, ça fonctionne comme une gomme.

09-blank-tile

Vous pouvez sélectionner plusieurs tiles d’un coup en maintenant la touche MAJ appuyée lors de la sélection.

Nous allons rajouter les collisions, créez un nouveau calque (avec le +), puis cochez le paramètre Is Collision Layer, modifiez la taille de carré de collision pour qu’elle soit identique à l’autre calque :

10-collision-layer

Cliquez sur la map et appuyez sur la barre d’espace pour choisir le type de collision, choisissons des collisions pleines et plaçons-les aux bons endroits :

11-collisions-tiles

Une fois le niveau prêt il faut le sauvegarder, cliquez sur save, placez vous dans le dossier lib/game/levels, donnez un nom à votre niveau (level1.js par exemple) et cliquez sur save.

N’oubliez pas de donner les droits d’écriture et de créer le dossier levels/.

13-save

14-save-prompt

Retour au sommaire.

Charger le niveau

Source de ce chapitre

Reprenons le fichier du jeu principal fourni par Impact et analysons-le avant de le modifier pour charger notre niveau fraîchement créé :

ig.module(
	"game.main" // Le nom du module est main
)
// Inclusion des dépendances
.requires(
	"impact.game",
	"impact.font"
)
.defines(function(){
// Création du jeu
MyGame = ig.Game.extend({

	// Chargement d'une font
	font: new ig.Font("media/04b03.font.png" ),

	init: function() {
		// Initialisation du jeu ici
	},

	update: function() {
		// On appelle la gameloop d'Impact qui update
                // les entities et la map
		this.parent();

		// Ici on peut ajouter notre propre logique
	},

	draw: function() {
		// Ici on appelle la fonction qui dessine
                // les entities et la map sur le canvas
		this.parent();

		// Ici on peut ajouter notre propre logique d'affichage
		var x = ig.system.width/2,
			y = ig.system.height/2;

		this.font.draw( "It Works!", x, y, ig.Font.ALIGN.CENTER );
	}
});

// Démarrage du jeu à 60fps
// avec une résolution de 320x240 agrandie x2
ig.main( "#canvas", MyGame, 60, 320, 240, 2 );

});

Pour ajouter notre niveau au jeu, il faut d’abord charger le fichier dans .requires() :

.requires(
    'impact.game',
    'game.levels.level1' // game/levels/level1.js
)

Puis dans .init(), donc à l’initialisation du jeu, on demande de charger le niveau :

init: function() {
    // Chargement du level
    this.loadLevel(LevelLevel1);
}

Maintenant vous pouvez tester : http://localhost/impact/.

Retour au sommaire.

Création du personnage

Source, démo de ce chapitre

Nous allons ajouter un peu de gravité dans notre monde.

Dans main.js ajoutons la propriété .gravity.

gravity: 275

Ensuite ajoutons les contrôles au clavier toujours dans main.js grace à ig.input.bind().

init: function() {

// Définition des contrôles
    ig.input.bind(ig.KEY.LEFT_ARROW, 'left');
    ig.input.bind(ig.KEY.RIGHT_ARROW, 'right');
    ig.input.bind(ig.KEY.X, 'jump');
    ig.input.bind(ig.KEY.C, 'shoot');

    // Chargement du level
    this.loadLevel(LevelLevel1);
}

Créons un nouveau fichier game/entities/player.js dans lequel nous allons construire la classe PlayerEntity qui servira à faire naître (spawn) notre personnage-joueur dans le jeu.

Dans un premier temps on renseigne le nom du module : ig.module(‘game.entities.player’), ses dépendances : .requires(‘impact.entity’).

Puis dans .defines() on peut commencer à construire notre classe à partir de la classe Entity : EntityPlayer = ig.Entity.extend().

ig.module(
    'game.entities.player'
).requires(
    'impact.entity'
).defines(function() {

    EntityPlayer = ig.Entity.extend({

Ensuite il faut définir les propriétés de notre personnage :

  • Création d’une AnimationSheet avec le spritesheet player.png.
  • La taille de l’entité : size et offset.
  • Les propriétés physiques de l’entité : maxVel pour la vitesse, friction pour les frottements et jump pour définir l’impulsion du saut.
        // Spritesheet du joueur
        animSheet: new ig.AnimationSheet('media/player.png', 17, 17),

        // Taille de l'entité (TailleDuSprite-TailleDesMarges)
        size: { x: 11, y: 17 },

        // Marge interne
        offset: { x: 3, y: 0 },

        // Le sprite est il retourné horizontalement ?
        flip: false,

        // Vitesse maximale
        maxVel: { x: 100, y: 150 },

        // Frottements avec le sol
        friction: { x: 600, y: 0 },

        // Impulsion du saut
        jump: 225,

Ensuite nous allons créer la fonction d’initialisation de l’entité, qui est donc appelée à chaque fois qu’une nouvelle entité est créée.

Il faut appeler .parent() qui va appeler la fonction .init() de la classe parente (ig.Entity) pour initialiser et positionner personnage.

Ensuite il faut définir les différentes animations grâce à .addAnim() :

  • Le premier paramètre est le nom de l’animation.
  • Le second est le temps entre chaque frame (en secondes).
  • Le dernier est la liste des frames à jouer dans un ordre défini.
        init: function(x, y, settings) {
            this.parent(x, y, settings);

            // Animations
            this.addAnim('idle', 1, [0]);
            this.addAnim('run', 0.07, [0, 1, 2, 3, 4, 5, 6, 7, 8]);
            this.addAnim('jump', 1, [7]);
            this.addAnim('fall', 0.1, [7, 8]);

        },

Créons maintenant la game loop : .update().

Cette méthode est appelée 60 fois par seconde environ pour chaque entité.

Nous allons vérifier que le joueur presse une touche de déplacement (flèche gauche ou flèche droite) grâce à ig.input.

Si le joueur presse la flèche gauche nous appliquons une accélération négative et inversement vers la droite nous appliquons une accélération positive.

A la fin de la boucle .update() la méthode .parent() est appelée, celle-ci traitera automatiquement cette accélération pour déplacer le personnage.

Si le joueur est au sol (this.standing) et qu’il presse la touche de saut nous appliquons une vitesse verticale (this.vel.y).

Le spritesheet de base étant orienté vers la droite si le joueur presse la touche pour se déplacer vers la gauche il faut lui appliquer un flip horizontal.

Ensuite il faut sélectionner l’animation appropriée (this.currentAnimation) :

  • Si la vitesse verticale est négative, le personnage saute, on sélectionne l’animation jump.
  • Si la vitesse verticale est positive, le personnage tombe, on sélectionne l’animation fall.
  • Si il y a une vitesse horizontale, le personnage court, on sélectionne l’animation run.
  • Sinon on sélectionne l’animation idle.
        update: function() {

            var accel = 350;

            // Mouvements
            if (ig.input.state('left')) {
                this.accel.x = -accel;
                this.flip = true;
            } else if (ig.input.state('right')) {
                this.accel.x = accel;
                this.flip = false;
            } else {
                this.accel.x = 0;
            }

            // Saut
            if (this.standing && ig.input.pressed('jump')) {
                this.vel.y = -this.jump;
            }

            // Choix de l'animation appropriée
            if (this.vel.y < 0) {

                this.currentAnim = this.anims.jump;

            } else if (this.vel.y > 0) {

                this.currentAnim = this.anims.fall;

            } else if (this.vel.x != 0) {

                this.currentAnim = this.anims.run;

            } else {

                this.currentAnim = this.anims.idle;

            }

            // Direction du sprite
            this.currentAnim.flip.x = this.flip;

            this.parent();

        }
    });

});

Maintenant nous voulons ajouter notre personnage dans le niveau créé précédemment.

Ouvrez level1.js dans l’éditeur de niveaux, cliquez sur entities puis appuyez sur la barre d’espace comme pour les tiles.

Sélectionnez Player dans le menu déroulant :

19-entity

Puis positionnez le joueur où vous le souhaitez :

20-player

Retour au sommaire.

Création des ennemies

Source, démo de ce chapitre

De la même manière que pour la classe EntityPlayer nous allons créer le fichier games/entities/zombie.js et la classe EntityZombie.

ig.module(
    'game.entities.zombie'
).requires(
    'impact.entity'
).defines(function() {

    EntityZombie = ig.Entity.extend({
        animSheet: new ig.AnimationSheet('media/zombies.png',17,17),
        size: { x: 11, y: 17 },
        offset: { x: 3, y: 0 },
        maxVel: { x: 100, y: 100 },
        flip: false,
        friction: { x: 300, y: 0 },
        speed: 24, // Vitesse de déplacement
        init: function(x, y, settings) {

            this.parent(x, y, settings);

            // Animation
            this.addAnim('walk', .07, [0, 1, 2, 3, 4, 5]);

        },

Dans la boucle .update() nous allons faire avancer le zombie dans une direction définie par this.flip.

La boucle .handleMovementTrace() gère les collisions avec la map.

Lorsque le zombie entre en collision avec un mur nous le faisons changer de direction.

        update: function() {

            // Déterminer la vitesse et la direction du zombie
            var direction = this.flip ? -1 : 1;
            this.vel.x = this.speed * direction;

            this.currentAnim.flip.x = this.flip;
            this.parent();
        },
        handleMovementTrace: function(res) {
            this.parent(res);

            // Retourner le zombie lorsqu'il se heurte à un mur
            if (res.collision.x) {
                this.flip = !this.flip;
            }
        }
    });

});

Ajoutons plusieurs zombies dans le level1 dans l’éditeur de niveau, comme pour le joueur.

21-player-zombies

Retour au sommaire.

Un peu d’action

Source, démo de ce chapitre

D’abord je vais vous parler du système de collisions d’Impact.

Il y a les collisions statiques (Entity vs World), elle sont gérées par la méthode .handleMovementTrace() comme on l’a vu plus haut.

Et les collisions dynamiques (Entity vs Entity), qui sont gérées par la méthode .check().

Les collisions dynamiques proposent plusieurs type :

Les types permettent de grouper des entités afin de gérer facilement les collisions d’un groupe contre un autre. Il y a 3 types de collisions :

  • TYPE.NONE
  • TYPE.A
  • TYPE.B

La propriété .checkAgainst définit contre quel TYPE l’entité doit gérer les collisions, 4 possibilités :

  • TYPE.NONE
  • TYPE.A
  • TYPE.B
  • TYPE.BOTH (TYPE.A et TYPE.B)

Et plusieurs modes :

  • NEVER : Toutes les collisions sont ignorées pour cette entité.
    > Exemple : des éléments de décor.
  • LITE : Si deux entités LITE entrent en collision elles peuvent se traverser.
    > Exemple : les particules.
  • PASSIVE : Si deux entités PASSIVE entrent en collision elles peuvent se traverser.
    > Exemple : le joueur.
  • ACTIVE : Si une entité ACTIVE entrent en collision avec une entité plus faible (LITE ou PASSIVE), elle bloquera cette dernière.
    > Exemple : des caisses.
  • FIXED : L’entité ne bougera pas suite au résultat d’une collision.
    > Exemple : des plateformes qui bougent.

Je vous conseille de lire ce tutoriel pour mieux comprendre.

Commençons par la partie la plus simple : tuer le joueur lorsqu’il entre en collision avec un zombie.

Retournez sur le fichier player.js. et ajoutez les propriétés de collision suivantes :

type: ig.Entity.TYPE.A,
checkAgainst: ig.Entity.TYPE.NONE,
collides: ig.Entity.COLLIDES.PASSIVE,

Puis ouvrez zombie.js. et ajoutez les propriétés de collision suivantes :

type: ig.Entity.TYPE.B,
checkAgainst: ig.Entity.TYPE.A,
collides: ig.Entity.COLLIDES.PASSIVE,

Ensuite surchargeons la méthode .check() pour tuer le joueur lorsqu’il entre en collision avec le zombie :

check: function(other) {
    other.kill();
}

Maintenant que les zombies peuvent tuer le joueur, il faut lui donner un moyen de se défendre, des grenades me paraissent une solution appropriée.

Créons games/entities/grenade.js :

Sans oublier d’assigner .checkAgainst à TYPE.B (le type du zombie) et en ajoutant une nouvelle propriété : .bounciness qui est le taux de rebond de la grenade.

ig.module(
    'game.entities.grenade'
).requires(
    'impact.entity'
).defines(function() {
    EntityGrenade = ig.Entity.extend({
        size: { x: 4, y: 4 },
        offset: { x: 2, y: 2 },
        animSheet: new ig.AnimationSheet('media/grenade.png', 8, 8),
        type: ig.Entity.TYPE.NONE,
        checkAgainst: ig.Entity.TYPE.B,
        collides: ig.Entity.COLLIDES.PASSIVE,
        maxVel: { x: 200, y: 200 },
        bounciness: 0.6, // Taux de rebond
        bounceCounter: 0, // Nombre de rebonds effectués

Dans .init() nous allons dans un premier temps positionner la grenade en fonction de la direction et la position du joueur:

  • Si la direction du joueur est vers la droite nous positionnons la grenade à la position du joueur +7 pixels vers la droite.
  • Si la direction du joueur est vers la gauche nous positionnons la grenade à la position du joueur +4 vers la gauche.

Ensuite nous donnons une vitesse initiale à la grenade :

  • Vitesse verticale : -65px (donc vers le haut).
  • Si la direction du joueur est vers la droite, il faut définir une vitesse horizontale positive et inversement : vers la gauche une vitesse négative.
init: function(x, y, settings) {
    x += settings.flip ? -4 : 7
    this.parent(x, y, settings);

    // Impulsion
    this.vel.x = settings.flip ? -this.maxVel.x : this.maxVel.x;
    this.vel.y = -65;

    this.addAnim('idle', 0.2, [0, 1]);
},

Dans .handleMovementTrace() nous allons gérer les rebonds de la grenade : faire exploser la grenade au bout de 3 rebonds maximum.

handleMovementTrace: function(res) {
    this.parent(res);

    // Si la grenade entre en collision avec un mur
    if (res.collision.x || res.collision.y) {

        // On la laisse rebondir
        this.bounceCounter++;

        // 3 fois maximum
        if (this.bounceCounter > 3) {

            // Avant de la détruire
            this.kill();
        }
    }
},

Si une grenade touche un joueur, elle le tue et explose.

        check: function(other) {
            other.kill();
            this.kill();
        }

    });

});

Maintenant il faut inclure la grenade dans les dépendances du module player.

.requires(
    'impact.entity',
    'game.entities.grenade' // EntityGrenade
)

Dans la boucle .update() nous allons faire naître une grenade grâce à la méthode .spawnEntity() lorsque le joueur presse la touche pour tirer.

.spawnEntity() accepte 4 paramètres :

  • L’entité à faire naître.
  • Sa position x.
  • Sa position y.
  • (optionnel) un objet de paramètres additionnels.
        update: function() {

            // ...

            // Grenade
            if(ig.input.pressed('shoot')) {
                ig.game.spawnEntity(EntityGrenade, this.pos.x, this.pos.y, {
                    flip: this.flip
                });
            }

            // ...

        }

Retour au sommaire.

Mourir et renaître

Source, démo de ce chapitre

Si le joueur meurt, pour le moment le jeu continue.

Pour cet article nous allons faire le minimum : Relancer le niveau en cours.

Surchargeons la méthode .kill() de PlayerEntity, cette méthode est appelée à la mort du joueur.

Nous allons utiliser .loadLevelDeferred() au lieu de .loadLevel().

La différence entre les deux est que .loadLevelDeferred() est déclenchée à la fin de la gameloop, ça évite d’éventuelles erreurs d’affichage.

kill: function() {
    // Rechargement du niveau lorsque le joueur meurt
    ig.game.loadLevelDeferred(ig.global.LevelLevel1);
}

Retour au sommaire.

Particules et explosions

Source, démo de ce chapitre

Nous allons créer un générateur de particules pour les explosions et les projections de sang.

L’image “particles.png” comporte 2 particules de 2x2px, une pour l’explosion et une pour le sang.

Créons le fichier game/entities/particles.js :

ig.module(
    'game.entities.particle'
).requires(
    'impact.entity'
).defines(function() {

    EntityParticle = ig.Entity.extend({
        size: { x: 2, y: 2 },
        maxVel: { x: 160, y: 200 },
        lifetime: 1, // Durée de vie : 1 seconde
        bounciness: 0,
        vel: { x: 100, y: 100 }, // Vitesse de base
        friction: { x:100, y: 0 },
        collides: ig.Entity.COLLIDES.LITE,
        animSheet: new ig.AnimationSheet('media/particles.png',2,2),

Dans .init() nous allons :

  • Définir le type de particule à afficher (blood ou explosion).
  • Générer une vitesse aléatoire.
  • Définir un timer pour la durée de vie de la particule.
        init: function(x, y, settings) {
            this.parent(x, y, settings);

            // Choix de la particule en fonction du type
            var types = {
                blood: 0,
                explosion: 1
            };
            this.addAnim('idle', 0.2, [types[settings.type]]);

            // Vitesse aléatoire pour une impression d'explosion
            this.vel.x = (Math.random()*2-1) * this.vel.x;
            this.vel.y = (Math.random()*2-1) * this.vel.y;

            // Création d'un timer pour la durée de vie de la particule
            this.lifeTimer = new ig.Timer();
        },

Dans la boucle .update() nous allons détruire la particule à la fin de son temps de vie.

Et diminuer son opacité au fil du temps grâce à la propriété .alpha.

        update: function() {

            // Si le timer excède la durée de vie
            // de la particule, on la détruit
            if(this.lifeTimer.delta() > this.lifetime) {
                this.kill();
                return;
            }

            // Calcul de la couche alpha en fonction
            // du temps de vie restant pour un effet de fade
            this.currentAnim.alpha = this.lifeTimer.delta().map(
                0, this.lifetime,
                1, 0
            );

            this.parent();
        }
    });

Maintenant nous allons créer une entité qui génère des particules, son unique but est de générer un certain nombre de particules avant de de disparaître :

    EntityParticlesEmitter = ig.Entity.extend({
        particles: 25, // Nombre de particules à générer
        init: function(x, y, settings) {
            this.parent(x, y, settings);

            // Génération des particules
            for(var i = 0; i < this.particles; i++) {
                ig.game.spawnEntity(EntityParticle, x, y, {
                    type: settings.type
                });
            }

            // Destruction immédiate du générateur
            this.kill();
        }
    });

});

Maintenant que notre système de particules est prêt il faut l’ajouter aux dépendances de notre jeu.

.requires(
    'impact.game',
    'game.levels.level1', // game/levels/level1.js
    'game.entities.particle' // game/entities/particle.js
)

Lorsqu’une grenade explose il faut générer des particules d’explosion (grenade.js).

kill: function() {
    // Génération de l'explosion
    ig.game.spawnEntity(EntityParticlesEmitter, this.pos.x, this.pos.y, {
        type: "explosion"
    });
    this.parent();
}

Lorsqu’un zombie meurt il faut générer des particules de sang (zombie.js).

kill: function() {
    // Génération de l'explosion
    ig.game.spawnEntity(EntityParticlesEmitter, this.pos.x, this.pos.y, {
        type: "blood"
    });
    this.parent();
}

Retour au sommaire.

Camera

Source, démo de ce chapitre

Nous allons modifier un peu notre niveau pour qu’il dépasse de l’écran sur la largeur (par exemple 50 ou 100 tiles de large).

Puis nous allons modifier notre caméra, ça se passe dans la boucle .update() de main.js :

A l’aide de la méthode .getEntitiesByType() récupérons l’entité de notre joueur et ainsi récupérer sa position.

Nous allons également bloquer la caméra lorsque le joueur se trouve au bord de la map.

update: function() {

    // Récupération de notre entité player
    var player = this.getEntitiesByType(EntityPlayer)[0];

    // Si l'entité existe on centre la caméra sur le personnage
    if(player) {

        // Récupération de la largeur du niveau en pixel
        var levelWidth = ig.game.backgroundMaps[0].width*ig.game.backgroundMaps[0].tilesize;

        // Récupération de la position de l'écran centrée sur le joueur
        var centeredPosition = player.pos.x-ig.system.width/2;

        // On fige la caméra si on arrive sur les bords du niveau
        this.screen.x = Math.min(
            Math.max(centeredPosition, 0),
            levelWidth-ig.system.width
        );
    }
    this.parent();
}

Retour au sommaire.

Interface utilisateur

Source, démo de ce chapitre

La petite touche finale, ajoutons des instructions à l’écran. Pour cela nous allons utiliser la lib font d’Impact. Il faut l’ajouter aux dépendances de notre jeu (main.js).

.requires(
    'impact.game',
    'impact.font',
    'game.levels.level1',
    'game.entities.particle'
)

Ajoutons notre font dans la classe MyGame.

instructions: new ig.Font('media/04b03.font.png'),

Maintenant nous pouvons l’utiliser, nous allons surcharger la boucle .draw() du jeu pour afficher les instructions grâce à la méthode .draw() de la font.

draw: function() {
    this.parent();

    var controls = '[Controls] Arrows: Moves, X: Jumps & C: Fires.';
    var zombiesLeft = 'Zombies left : '+this.getEntitiesByType(EntityZombie).length;

    // On positionne les instructions en bas au centre
    var x = ig.system.width/2;
    var y = ig.system.height-8;

    // On dessine les instructions
    this.instructions.draw(controls, x, y, ig.Font.ALIGN.CENTER);

    var y = 5;
    // Nombre de zombies restants
    this.instructions.draw(zombiesLeft, x, y, ig.Font.ALIGN.CENTER);
}

Retour au sommaire.

Debug

Impact dispose d’un outil de debug et monitoring, pour l’activer c’est très simple, il suffit d’ajouter impact.debug.debug dans les dépendances du jeu et une barre de debug sera automatiquement affichée en bas de l’écran.

.requires(
    'impact.debug.debug'
)

debug-menu

Retour au sommaire.

Minification

Lorsque vous voulez publier votre jeu, vous pouvez le minifier en 1 seul fichier à l’aide d’un petit script PHP fournit avec Impact.

Pour Windows le script se lance avec tools/bake.bat.

Pour MacOSX le script se lance avec tools/bake.sh.

Ou vous pouvez le lancer directement en ligne de commande.

$ php tools/bake.php lib/impact/impact.js lib/game/main.js my_game.min.js

Retour au sommaire.

Ressources

Retour au sommaire.

Si vous avez décidé de vous mettre à Impact, n’hésitez pas à partager vos créations en commentaire.

Nous parlerons de EaselJS dans le prochain article de cette série.

Have fun !

One thought on “Introduction à la création d’un jeu HTML5 avec ImpactJS”

Leave a Reply

Your email address will not be published. Required fields are marked *