Bienvenue sur notre tutoriel Flappy Bird avec Phaser 2.0. Ce tutoriel se découpe en plusieurs parties. Vous trouverez les liens vers les autres partie au bas de cet article.
Pour rappel, ce tutoriel est basé sur un tutoriel rédigé en anglais par Jeremy Dowell.
Dans cette partie du tutoriel, nous allons voir les timers ou minuterie, la génération d’obstacle et le recyclage d’obstacles pour économiser de la mémoire.
La génération d’obstacles
La première chose que nous voulons faire, c’est de mettre en place une loop avec un timer ( boucle avec minuterie, appelée à interval régulier donc ). Et ce pour générer des obstacles régulièrement.
Ouvrons game/states/play.js
et ajoutons les lignes suivantes au bas de la méthode create()
.
- create: function() {
- /* all previous code here */
- // add a timer
- this.pipeGenerator = this.game.time.events.loop(Phaser.Timer.SECOND * 1.25, this.generatePipes, this);
- this.pipeGenerator.timer.start();
- },
Cela nous donne une variable dans notre état nommé this.pipeGenerator
qui contient un un timer ou minuterie qui appellera la fonction this.generatePipes()
toutes les 1.25 secondes.
Si vous essayer de faire tourner le code maintenant, cela ne marchera pas. Vous aurez une erreur dés que le programme essaiera d’exécuter la fonction this.generatePipes()
qui n’est pas encore définie.
Bon, ben, continuons alors !
Dans le même fichier, allons sous la méthode update()
et ajoutons ce qui suit :
- generatePipes: function() {
- console.log('generating pipes!');
- }
Maintenant, quand nous faisons tourner notre code, toutes les 1.25 seconde, la console affiche un message nous disant que la fonction generatePipes()
est bien appelée par le timer de façon régulière.
Excellent !
But, cela ne fait rien de bien intéressant au fond...
Faisons quelque chose d’amusant !
Avant toutes choses, nous devons précharger notre sprite sheet pipe.png
. Pour cela, ouvrons game/states/preload.js
et just sous les lignes qui ajoute le sprite sheet de notre oiseau, ajoutons les lignes suivantes :
- this.load.spritesheet('pipe', 'assets/pipes.png', 54,320,2);
Cette ligne dit à Phaser de charger l’image pipe.png
comme une srite sheet avec 2 frames, chacune de 54 pixels de de large et 320 de haut.
Créons les prefabs Pipe et PipeGroup
Ouvrez votre terminal et allez dans le répertoire de votre projet et créez les prefabs : pipe
et pipeGroup
. Si vous avez besoin d’un petit rappel sur comment faire cela, jeter un oeil à la partie 2 de ce tutoriel.
Vous devriez vous retrouver avec les fichiers pipe.js
et pipeGroup.js
dans votre répertoire game/prefabs
.
Tout d’abord, personnalisons le prefab qui controle une colonne ( Pipe ).
Ouvrez game/prefabs/pipe.js
, réglez l’ancre du sprite au centre, ajoutez un corps physique, désactivez la gravité sur ce corps et rendez le « immovable ». Rappelez-vous, nous avons fait tout cela dans la partie 2 lorsque nous avons traité notre tileSprite ground pour le sol.
- var Pipe = function(game, x, y, frame) {
- Phaser.Sprite.call(this, game, x, y, 'pipe', frame);
- this.anchor.setTo(0.5, 0.5);
- this.game.physics.arcade.enableBody(this);
- this.body.allowGravity = false;
- this.body.immovable = true;
- };
Maintenant, nous allons gréer un groupe instanciable qui contient une paire de de colonnes ( pipe ). Celle du haut et celle du bas.
Ouvrons game/prefabs/pipeGroup.js
et commençons par changer la class Phaser.Sprite
par Phaser.Group
:
- var Pipe = require('./pipe');
- var PipeGroup = function(game, parent) {
- Phaser.Group.call(this, game, parent);
- };
- PipeGroup.prototype = Object.create(Phaser.Group.prototype);
- PipeGroup.prototype.constructor = PipeGroup;
- module.exports = PipeGroup;
Maintenant, notre prefab PipeGroup
hérite de Phaser.Group
. Cela signifie que nous avons maintenant accès à toutes les fonction d’un groupe. Remarquez que les arguments passés dans cette class sont totalement différent de ceux d’une class Sprite.
Remarquez également que nous avons un require()
pour importer notre class prefab pipe
.
Bien ! Maintenant, faisons en sorte que notre groupe ajoute quelque chose à lui-même.
Ajoutons la colonne du haut
- var PipeGroup = function(game, parent) {
- Phaser.Group.call(this, game, parent);
- this.topPipe = new Pipe(this.game, 0, 0, 0);
- this.add(this.topPipe);
- };
Les deux lignes nouvellement ajoutées génère une colonne qui sera référencée dans notre PipeGroup
comme étant topPipe
. Elle utilise la première frame de son spritesheet pipe
. Ensuite, nous l’ajoutons comme enfant à notre PipeGroup
.
Pourquoi tous ces zéro dans l’instanciation de Pipe
?
Regardons le constructeur de notre prefab Pipe
:
- var Pipe = function(game, x, y, frame) {
- Phaser.Sprite.call(this, game, x, y, 'pipe', frame);
- ...
- };
Souvenez-vous d’une explication dans la partie 1 de ce tutoriel. Positionner un groupe transpose toutes les position des membres de ce groupe. Du coup les deux premiers 0 sont les valeurs de x
et de y
. Le dernier 0 est la frame que nous allons utiliser. Souvenez vous, l’image de nos colonnes est un sprite sheet avec deux frames.
La première frame ( frame[0]
) est notre colonne du haut tandis que la deuxième frame ( frame[1]
) est celle du bas.
Ajoutons la deuxième colonne ( Pipe
) :
- var PipeGroup = function(game, parent) {
- Phaser.Group.call(this, game, parent);
- this.topPipe = new Pipe(this.game, 0, 0, 0);
- this.add(this.topPipe);
- this.bottomPipe = new Pipe(this.game, 0, 440, 1);
- this.add(this.bottomPipe);
- };
C’est quasiment un copier-coller des deux premières lignes. La seule différence est qu’il y a des valeurs autres que 0 passées en argument pour le y
et frame
au constructeur Pipe
. Le y
est un nombre magique ! Il a été calculé de la façon suivante :
- y = pipe.height + (bird.height * 5)
Ce qui veut dire que la distance entre la colonne du haut ( topPipe
) et celle du bas ( bottomPipe
) sera environ 5 fois la hauteur de notre oiseau. On peut jouer là dessus... Nous verrons cela plus tard...
Nous devons ajouter une dernière chose. Il s’agit d’un switch pour dire si notre oiseau à franchi l’obstacle ou pas. Dans le constructeur de notre prefab PipeGroup
, nous ajoutons donc un switch this.hasScored
.
- var PipeGroup = function(game, parent) {
- Phaser.Group.call(this, game, parent);
- this.topPipe = new Pipe(this.game, 0, 0, 0);
- this.add(this.topPipe);
- this.bottomPipe = new Pipe(this.game, 0, 440, 1);
- this.add(this.bottomPipe);
- this.hasScored = false;
- };
Nous l’utiliseront donc plus tard pour déterminer si notre oiseau à franchi l’obstacle que constitue les deux colonnes.
Ajoutons les colonnes à notre jeu
De retour à game/states/play.js
, nous avaons besoin d’un require()
pour appeler notre class PipeGroup
.
En haut de notre fichier, sous les require()
de notre oiseau et du sol ajoutons :
- var PipeGroup = require('../prefabs/pipeGroup');
Maintenant, allons vers le bas et faisons faire quelque chose à notre méthode generatePipes()
.
- generatePipes: function() {
- var pipeY = this.game.rnd.integerInRange(-100, 100);
- var pipeGroup = new PipeGroup(this.game);
- pipeGroup.x = this.game.width;
- pipeGroup.y = pipeY;
- },
La première ligne fait quelque chose que nous n’avaons pas encore vue ! Et c’est un truc super à connaitre ! Phaser possède son propre générateur de nombre. Dans notre cas, nous allons générer une valeur aléatoire pour la position y
de notre pipeGroup
. Comme cela, les colonnes ne seront pas toujours à la même place.
La syntaxe de integerInRange()
ressemble à cela :
- this.game.rnd.integerInRange(min, max);
Dans notre cas, (-100,100)
sont deux nombres magiques. Ils donnent l’écart que Jeremy a jugé adéquat pour la jouabilité de notre jeu. Vous pouvez le modifier et tester pour trouver votre propre réglage.
Lorsqu’un nouveau PipeGroup
est généré sa position selon l’axe des x est la largeur de l’écran du jeu et la position selon l’axe des y est généré aléatoirement.
Regardons le résultat de ce que nous venons de faire :
Ouais, sympa ! Mais il y a quelque chose qui cloche quand même...
Les colonnes ne bougent pas et restent collées sur la droite de l’écran.
Arrangeons cela dans notre prefab PipeGroup
.
A la fin du constructeur, sous notre switch hasScore
, nous avons besoin de régler la velocité des enfants de ce groupe dans l’axe des x
.
Nous pourrions le faire manuellement...
- this.topPipe.body.velocity.x = -200;
- this.bottomPipe.body.velocity.x = -200;
ou nous pouvons utiliser une des fonction de base de la class Phaser.Group
pour nous faciliter la vie !
- var PipeGroup = function(game, parent) {
- Phaser.Group.call(this, game, parent);
- this.topPipe = new Pipe(this.game, 0, 0, 0);
- this.bottomPipe = new Pipe(this.game, 0, 440, 1);
- this.add(this.topPipe);
- this.add(this.bottomPipe);
- this.hasScored = false;
- this.setAll('body.velocity.x', -200);
- this.width = this.topPipe.width;
- };
Cette ligne appelle une méthode spéciale des instances de Phaser.Group
qui régle la propriété voulue à tous les membres d’un groupe. Elle permet me de faire de changement profond sur les propriétés. Nous réglons la propriété x
de l’objet velocity
de tous les corps physiques des enfants à -200. Encore une fois, -200 est un numéro magique qui colle avec l’autoscrolling de notre objet Ground
que nous avons créé dans la partie 2
Maintenant sur notre écran, nous avons :
Vous remarquez que les collision ne sont pas détectées... C’est bon pour le moment. Nous devons parler de quelque chose avant de voir cela.
Recyclage
Lorsque vous instanciez un nouveau sprite, le navigateur charge l’image dans le canvas, créé les méthodes du sprite, son affichage et, gère la physique associé si nécessaire. Lorsque vous géné rez beaucoup de sprite de cette façon, vous finissez par engorger la mémoire du système et le système d’affichage.
Une petite expérience
Nous ne verrons pas le code que Jeremy a écrit pour l’expérience, nous verrons seulement le résultat. Nous verrons d’abord ce qui se passe sans recyclage puis avec.
Jeremy a écrit un programme qui génére un nouveau sprite oiseau toutes les 1/1000eme de seconde, appliqué la physique au corps physique et le laissez tomber selon un y
constant et un x
itératif. Jeremy a attahé un nom à chaque oiseau pourmieux visualiser ceux qui sont unique ou ceux qui sont recyclés.... Il a egalement affiché le nombre de fois que le générateur d’oiseau a été appelé ainsi que le nombre total d’oiseaux générés.
D’abord sans recyclage
Au début :
Environ 1200 appels du générateur :
Environ 2000 appels du générateur :
A ce point, Jeremy à vérifié sa mémoir utilisé, et elle était à environ 22 megs de mémoire utilisée. Remarquez qu’il y a autant d’oiseau que d’appel du générateur. Ce qui veut dire qu’il y a environ 2000 oiseaux de chargés dans le canvas.
Ensuite, avec recyclage
Au début :
Environ 2000 appels du générateur :
Environ 5000 appels du générateur :
Remarquez que maintenant, entre 52 et 53 oiseaux sont générés au total. L’utilisation de la mémoire pour cela reste à environ 10 megs. Vous pouvez clairement voir que le recyclage est absoluement nécessaire dans les jeux qui génére des sprite d’une manière procédural.
Implémenter le recyclage
La première dont nous avons besoin pour faire du recyclage, c’est d’un groupe pour y mettre nos prefabs pipeGroup
une fois qu’ils sont créés.
Dans la méthode create()
de game/states/play.js
, ajoutons un groupe pipes
juste en dessous l’instanciation de this.bird
juste au-dessus de de là où nous créons un Ground
object.
- create: function() {
- ...
- // create and add a new Bird object
- this.bird = new Bird(this.game, 100, this.game.height/2);
- this.game.add.existing(this.bird);
- // create and add a group to hold our pipeGroup prefabs
- this.pipes = this.game.add.group();
- // create and add a new Ground object
- this.ground = new Ground(this.game, 0, 400, 335, 112);
- this.game.add.existing(this.ground);
- ...
- },
Maintenant, nous avons un groupe depuis lequel nous pouvons recycler. Ajoutons la partie logique pour gérer le recyclage.
Regardons notre code generatePipes()
:
- generatePipes: function() {
- var pipeY = this.game.rnd.integerInRange(-100, 100);
- var pipeGroup = new PipeGroup(this.game);
- pipeGroup.x = this.game.width;
- pipeGroup.y = pipeY;
- }
Nous allons le modifier légèrement pour que le recyclage marche.
- generatePipes: function() {
- var pipeY = this.game.rnd.integerInRange(-100, 100);
- var pipeGroup = this.pipes.getFirstExists(false);
- if(!pipeGroup) {
- pipeGroup = new PipeGroup(this.game, this.pipes);
- }
- pipeGroup.reset(this.game.width + pipeGroup.width/2, pipeY);
- }
La première ligne n’a pas changé. Par contre, la deuxième est vraiment importante. Elle a pour but de récupérer le premier élément du groupe qui a sa propriété exist
réglée à false.
Mais que fait « exists » ?
La propriété
exists
sur un objet du jeu dit à Phaser si il doit ou pas appelé la méthodeupdate()
de cet objet durant l’appelupdate()
global du jeu. Cette propriété est automatique réglée à àtrue
lorsque l’objet est instancié. Sil’objet est un sprite, il est automatiquement réglée àfalse
lorsque l’on lui applique la méthodekill()
. C’est une manière pratique de savoir si un object a été tué ( killed ) ou d’une façon ou d’une autre « désactivé ».
Quant à la deuxième ligne, elle dit à notre groupe pipes
de passer en revue ses enfants par itération et de returner le premier qui n’existe pas dans le monde de notre jeu.
Cependant, nous devons également faire une autre vérification. C’est ce que fait le bloc if
. Si le groupe pipes
n’a pas d’enfants dont la propriété exists
est à false
, nous devons créer un nouveau PipeGroup
.
Regardons cette ligne :
- pipeGroup = new PipeGroup(this.game, this.pipes);
Elle est légèrement différente maintenant. En effet, PipeGroup
hérite de Phaser.Group
. nous pouvons donc passer un second paramètre qui dit à Phaser d’ajouter au groupe l’objet nouvellement crée. Dans ce cas, nous voulons automatiquement ajouter le tout nouveau PipeGroup
à notre groupe pipes, this.pipes
.
Pour finir, regardons la dernière ligne que nous avons ajouté :
Toutefois, nous allons devoir faire quelque ajustements à notre class prefab PipeGroup
.
- pipeGroup.reset(this.game.width + pipeGroup.width/2, pipeY);
Cela appelle la méthode reset sur le sprite nouvellement créé ou recyclé avec un nouvelle position x
et y
. Notre position x
sera le côté droit de notre écran de jeu , et notre position y
sera la position généré aléatoirement au début du générateur de colonne.
L’important Reset
Tout les objet du jeu qui hérite de Phaser.Sprite
ont de bas une méthode reset()
. Si vous vouliez appeler la méthode reset de notre oiseau, vous la syntaxe pourrez ressembler à cela :
- this.bird.reset(200, 100);
Les argument de la méthode reset()
sont respectivement les coordonnées x
et y
de l’endroit ou doit être placé le sprite. L’appel de cette méthode fait bien plus que cela.
reset(x,y)
Cela place le sprite dans le jeu selon les coordonnées
x
ety
. Il règle également alive, exists, visible et renderable à true. Il règle également l’état outOfBounds et la valeur de health. Si le sprite a un corps physique, celui-ci est ré-initialisé. ( src : Phaser.Sprite documentation )Note :
La manière dont nous allons faire cela n’est la la démontration de recyclage la plus simple. Comme nous somme en train de faire un véritable jeu, nous devons comprendre ces concept de base, ainsi nous pourrons les utiliser au mieux.
L’insoutanable légèreté de l’existence : une expérimentation
Regardez à nouveau cette image :
Vous vous demandez surement comment le recyclage marche avec un groupe qui contient seulement des sprites basiques et comment savoir quand tuer
un oiseau pour qu’il soit réutilisé.
Voyons comment Jeremy à géré cela :
- var Bird = function(game, x, y, frame) {
- Phaser.Sprite.call(this, game, x, y, 'bird', frame);
- this.anchor.setTo(0.5, 0.5);
- this.animations.add('flap');
- this.animations.play('flap', 12, true);
- this.name = 'bird';
- // enable physics on the bird
- // and disable gravity on the bird
- // until the game is started
- this.game.physics.arcade.enableBody(this);
- this.checkWorldBounds = true;
- this.outOfBoundsKill = true;
- };
les deux lignes qui doivent vous interpeller sont les deux suivantes :
- this.checkWorldBounds = true;
- this.outOfBoundsKill = true;
La première ligne dit au sprit, à chaque frame, de vérifier si il y a encore une partie de lui-même dans l’écran de jeu.
La deuxième dit au sprite d’automatiquement appelé sa méthode kill()
quand il sort entièrement de l’écran de jeu.
Pour le dire comme une histoire :
Ok, c’est une nouvelle phase de l’update. La position de
bird
est :{ x: -32, y : 100 }
et sa largeur est de 32 pixels. Ce qui signifie qu’il est entièrement en dehors de l’écran de jeu ( out of bounds ). je vais donc appelerbird.kill()
ce qui va régler les propriétésalive
,exists
etvisible
à false.
Le code du générateur d’oiseau pour l’expérience ressemble à cela :
- generateBird: function() {
- var bird = this.birdGroup.getFirstExists(false);
- if(!bird) {
- bird = new Bird(this.game, x, y);
- this.birdGroup.add(bird);
- }
- bird.reset(x, y);
- }
Note : L’appel du reset n’est pas nécessaire lorsque l’on génère un nouvel objet Bird
. Jeremy l’a fait comme cela car cela lui évité un extra else et qu’il trouvait cela plus propre.
Recyclage d’un groupe
Malheureusement, Phaser.Group
n’a pas de reset de base, et ce pour de bonne raison. En effet, il n’existe aucune manière de savoir quoi faire automatique lors du reset d’un groupe. D’un coup, nous devant l’implémenter nous même.
Notre reset va suivre les étapes suivantes :
- Reset l’objet topPipe (0,0) par rapport au groupe
- Reset l’objet bottomPipe (0,440) par rapport au groupe
- Régler les coordonnées
x
ety
selon les valeurs passées pour le groupe par rapport à l’écran de jeu. - Régler la vélocité des enfants du groupe à -200
- Mettre le switch
hasScored
du groupe àfalse
- Régler la propriété
exists
du groupe àtrue
Allons au game/prefabs/pipeGroup.js
et ajoutons le méthode reset()
:
- PipeGroup.prototype.reset = function(x, y) {
- // Step 1
- this.topPipe.reset(0,0);
- // Step 2
- this.bottomPipe.reset(0,440); // Step 2
- // Step 3
- this.x = x;
- this.y = y;
- // Step 4
- this.setAll('body.velocity.x', -200);
- // Step 5
- this.hasScored = false;
- // Step 6
- this.exists = true;
- };
A l’étape 1 et 2 nous voyons que nous utilisons lma méthode reset()
de nos colonnes topSprite
et bottomSprite
pour les repositionner relativement au groupe.
Nopus avons une dernière chose à ajouter à notre pipeGroup
pour que le recyclage marche correctement.
Implémentons checkWorldBounds pour un groupe de sprites
Vous vous souvenez des propriétés checkWorldBounds
et de outOfBoundsKill
de notre prefab Bird
dans l’expérience dont nous avons parlé précédement ? Nous avons besoin d’implémenter quelque chose comme cela pour notre groupe PipeGroup
pour savoir quand la propriété exists
de notre instance de Pipegroup
est à false.
Construisons une fonction simple checkWorldBounds
dans game/prefabs/pipeGroup.js
:
- PipeGroup.prototype.checkWorldBounds = function() {
- if(!this.topPipe.inWorld) {
- this.exists = false;
- }
- };
Ce que nous faisons ici est de vérifier si la propriété inWorld
de notre sprite topPipe
est à false. inWorld
est une proriété que Phasers met sur tous les sprites et met à jour à chaque frame. Nous pourrions vérifier sur les deux colonnes, maos nous savons qu’elle ont le même position selon l’axe x
de l’écran et qu’elle se déplace à la même vitesse. Du coup, il n’y a aucune raison de faire cela.
Maintenant, dans la méthode update()
de pipeGroup, disons à Phaser de lancer notre méthode perso checkWorldBounds()
à chaque update :
- PipeGroup.prototype.update = function() {
- this.checkWorldBounds();
- };
Vous vous demandez peut être pourquoi il n’y a pas de vérification pour savoir si une instance de PipeGroup
existe ou pas dans notre méthode checkWorldBounds()
... Souvenez-vous que lorsque la propriété d’un objet est réglé à false, il ne lance pas sa méthode update()
...
Maintenant, nous avons à l’écran :
Bon, ça ressemble beaucoup à ce que nous avions avant de mettre en place le recyclage...
La mort
Maintenant que les colonnes apparaissent et de l’oiseau vol, il reste une chose à faire dans cette leçon !
Faire mourir notre oiseau.
Dans le jeu original, il y a deux façon de le faire mourir :
- Toucher le sol
- Toucher une colonne
Dans notre méthode update()
de notre état play
, modifiez la ligne seule et unique ligne par la suivante :
- update: function() {
- // enable collisions between the bird and the ground
- this.game.physics.arcade.collide(this.bird, this.ground, this.deathHandler, null, this);
- },
Un appel complet de la méthode collide()
ressemble à ça :
- game.physics.arcade.collide(gameObject1, gameObject2, collisionCallback, processCallback, context);
Vous devez être familier avec tous ces éléments, à l’exception de l’argument processCallback
. Nous n’allons pas utiliser un process callback ici, mais nous avons besoin de de faire une vérification spéciale du fait des deux corps entrant en collision pour la collision pour déterminer si il entre vraiment en collision. Nous aurons le feraont dans un process callback et retournerons true
pour une collision et false
dans le cas contraire.
Maintenant, nous allons tester si notre oiseau entre en collision avec les colonnes.
Avec un groupe de sprites, vous souhaitez simplement faire ce qui suit :
- game.physics.arcade.collide(sprite, group, callback, null, this);
Le système de détection de collision va automatique tester la collision d’un sprite avec tous les sprites d’un groupe.
Cependant, ayant une group de groupes, nous devons aider Phaser.
Pour cela, ajoutons ce qui suit juste sous la détection avec le sol que nous venons d’ajouter.
- // enable collisions between the bird and each group in the pipes group
- this.pipes.forEach(function(pipeGroup) {
- this.game.physics.arcade.collide(this.bird, pipeGroup, this.deathHandler, null, this);
- }, this);
Ici, nous passons en revue par itération tous les PipeGroup
qui existent dans notre groupe pipes
et disont à Phaser de tester leur collision avec bird
. Nous allons utiliser le même callback que pour le sol ( this.ground
).
Créons maintenant notre méthode deathHandler()
...
Que devons-nous faire à la mort de notre oiseau ?
Il y aurait beaucoup de chose à faire à la mort de l’oiseau. Mais pour le moment, nous n’allons en faire qu’une... Aller à noter état GameOver
.
Créons notre méthode deathHandler
dans notre état Play comme cela :
- deathHandler: function() {
- this.game.state.start('gameover');
- },
Il nous reste cependant encore une chose à faire. Lorsque nous quittons l’état d’un jeu, Phaser appelle la méthode shutdown()
de l’état.
C’est une bonne idée de détruire les objets qui consomment de la mémoire ainsi que les méthodes actives.
Créons la méthode shutdown()
au bas de notre état Play
:
- shutdown: function() {
- this.game.input.keyboard.removeKey(Phaser.Keyboard.SPACEBAR);
- this.bird.destroy();
- this.pipes.destroy();
- }
La première ligne va retirer le lien de la barre espace à flapKey
. Sans cette ligne, lorsque nous retourneront à l’état Play
, La barre espace serait toujours lié à une implémentation précédente et ne marcherait pas...
Les deus ligne suivantes détruisent complétement notre bird
et notre pipes
et les retire complètement de la mémoire et du canvas. Encore plus pratique, lorsque l’on détruit un groupe, ses enfants sont également détruits.
L’état final
Bien, nous devrions, à la fin de cette partie du tutoriel, avoir une version plus ou moins abouti de Flappy Bird. Voyons vite ce que nous devrions obtenir :
Remarque : Jeremy a changé l’appel de game.state.start()
dans game/states/preload.js
pour charger ’menu
’ au lieu de ’play
’ pour l’enregistrement de l’image.
Tout le code pertinent de cette partie du tutoriel est disponible sous forme gist ici.
Prochaine étape !
Nous verrons le scoring, HUD Management, le son,les particules et le Game Over...
Répondre à cet article
Tous les champs sont obligatoires
Suivre les commentaires :
|
