Phaser 2.0 Tutoriel : Flappy Bird (Partie 5)

Publié le .

Phaser 2 Tutoriel
Phaser 2.0 Tutoriel : Flappy Bird (Partie 5)

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.

C’est la partie finale du tutoriel !

Nous allons voir comment apporter les touches final à notre jeu en y ajoutant du son, en utilisant le générateur de particule et le stockage persistant.

Notre jeu tel qu’il est actuellement :

Et ce vers quoi nous allons : démo jouable de Flappy Bird Reborn !

Télécharger les nouveaux assets

Jeremy à ajouté de nouveau éléments aux assets. Vous vous les avez téléchargés après le 14.04.2014, c’est bon. Sinon, vous devez les retélécharger depuis ici et les mettre dans votre dossier.

Créer les instructions du jeu

Nous devons informer les joueurs sur comment jouer à notre jeu. Pour cela, nous allons utiliser un simple groupe pour montrer ou cacher ces instructions ainsi que de faire voler notre oisaeu sans qu’il tombe ou pivote tant que le jeu n’est pas démarré.

Le résultat final ressemblera à cela :

Tout d’abord, nous devons charger les assets

Tout d’abord, ouvrez game/states/preload.js et ajoutez les lignes suivantes : au bas de la méthode preload() :

  1. this.load.image('instructions', 'assets/instructions.png');
  2. this.load.image('getReady', 'assets/get-ready.png');

Télécharger

Maintenant, nous devons créer un groupe pour y mettre nos assets. Ouvrez maintenant game/states/play.js et ajoutez au bas de la méthode create() :

  1. create: function() {
  2. ...
  3. this.instructionGroup = this.game.add.group();
  4. this.instructionGroup.add(this.game.add.sprite(this.game.width/2, 100,'getReady'));
  5. this.instructionGroup.add(this.game.add.sprite(this.game.width/2, 325,'instructions'));
  6. this.instructionGroup.setAll('anchor.x', 0.5);
  7. this.instructionGroup.setAll('anchor.y', 0.5);
  8. }

Télécharger

Nous utilisons un simple Phaser.Group et y ajoutons deux sprites : getReady et instructions. Ensuite nous utilisons le méthode setAll() pour mettre l’ancre de chaque élément à 0.5,0.5

Si vous regardez l’écran du jeu, vous devriez y voir quelque chose comme cela :

Nous +devons dire à notre oiseau de ne pas tomber tant que le joueur n’a pas interagit avec le jeu. Nous pouvons faire cela en disant au corps physique de notre oiseau de ne pas être affecté par la gravité jusqu’au bon moment.

Dans game/prefabs/bird.js, dans la méthode create(), nous devons mettre this.body.allowGravity à false.

  1. var Bird = function(game, x, y, frame) {
  2. Phaser.Sprite.call(this, game, x, y, 'bird', frame);
  3. this.anchor.setTo(0.5, 0.5);
  4. this.animations.add('flap');
  5. this.animations.play('flap', 12, true);
  6.  
  7. // enable physics on the bird
  8. // and disable gravity on the bird
  9. // until the game is started
  10. this.game.physics.arcade.enableBody(this);
  11. this.body.allowGravity = false;
  12. };

Télécharger

Nous avons parlé de cette méthode plus tôt lorsque nous avons créé notre prefab Pipe.

Maintenant, notre oiseau ne tombe plus :

Mais, nous avons un autre problème !

Dans game/prefabs/bird.js, dans la fonction update, nous disons à notre oiseau de pivoter automatiquement vers le sol à chaque frame :

  1. Bird.prototype.update = function() {
  2. // check to see if our angle is less than 90
  3. // if it is rotate the bird towards the ground by 2.5 degrees
  4. if(this.angle < 90) {
  5. this.angle += 2.5;
  6. }
  7. };

Télécharger

Nous devons faire en sorte que notre oiseau pivote seulement lorsque qu’il est « vivant ». Allons faire cela avec this.alive :

  1. Bird.prototype.update = function() {
  2. // check to see if our angle is less than 90
  3. // if it is rotate the bird towards the ground by 2.5 degrees
  4. if(this.angle < 90 && this.alive) {
  5. this.angle += 2.5;
  6. }
  7. };

Télécharger

Mais nous devons démarrer avec notre oiseau « mort » ( this.alive = false; ) et le rendre vivant une fois que le joueur a interagit.

Pour cela, modifions la méthode create() dans game/prefabs/bird.js, pour régler la propriété du sprite alive à false.

  1. var Bird = function(game, x, y, frame) {
  2. Phaser.Sprite.call(this, game, x, y, 'bird', frame);
  3. this.anchor.setTo(0.5, 0.5);
  4. this.animations.add('flap');
  5. this.animations.play('flap', 12, true);
  6.  
  7. this.alive = false;
  8.  
  9. // enable physics on the bird
  10. // and disable gravity on the bird
  11. // until the game is started
  12. this.game.physics.arcade.enableBody(this);
  13. this.body.allowGravity = false;
  14. };

Télécharger

Maintenant, vous devriez avoir quelque chose comme cela :

Nous avons encore de gros problème :

  1. Les colonnes se génèrent automatiquement
  2. Les instructions ne disparaissent pas

Nous devons attendre que le joueur interagisse pour commencer à générer des colonnes et nous devons également en profiter pour retirer les instructions.

Pour faire cela, nous devons intercepter la première fois qu’un joueur clique/tapote/presse la barre espace et retirer les instructions, commencer à générer des colonnes et rendre vivant notre oiseau en réglant sa propriété alive à true !

La première chose que nous allons faire, c’est d’ajouter un callback pour la première interaction du joueur. Dans la méthode create() de game/states/play.js, allez là ou nous avons créé nos interaction précédement :

  1. create: function() {
  2. ...
  3. // add keyboard controls
  4. this.flapKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
  5. this.flapKey.onDown.add(this.bird.flap, this.bird);
  6.  
  7. // add mouse/touch controls
  8. this.game.input.onDown.add(this.bird.flap, this.bird);
  9.  
  10.  
  11. // keep the spacebar from propogating up to the browser
  12. this.game.input.keyboard.addKeyCapture([Phaser.Keyboard.SPACEBAR]);
  13. ...
  14. }

Télécharger

Modifions ce code pour qu’il ressemble à cela :

  1. create: function() {
  2. // add keyboard controls
  3. this.flapKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
  4. flapKey.onDown.addOnce(this.startGame, this);
  5. flapKey.onDown.add(this.bird.flap, this.bird);
  6.  
  7.  
  8. // add mouse/touch controls
  9. this.game.input.onDown.addOnce(this.startGame, this);
  10. this.game.input.onDown.add(this.bird.flap, this.bird);
  11.  
  12.  
  13. // keep the spacebar from propogating up to the browser
  14. this.game.input.keyboard.addKeyCapture([Phaser.Keyboard.SPACEBAR]);
  15. ...
  16. },

Télécharger

Les deux lignes qui sont importantes ici sont les deux qui ajoute l’appel d’une méthode addOnce() à nos propriétés onDown. Nous ajoutons un callback à la première fois que les joueurs interagissent avec une de ces inputs ou méthodes d’entrées. Le callback sera la méthode, à créer, this.startGame().

Nous devons aussi retirer notre timer pipeGenerator de la méthode create() comme nous ne voulons pas générer de colonnes avant d’avoir commencer le jeu en lui-même...

Allez au bas de game/states/play.js, juste sous la méthode shutdown() et créons la méthode startGame().

  1. ... ,
  2. startGame: function() {
  3. this.bird.body.allowGravity = true;
  4. this.bird.alive = true;
  5.  
  6. // add a timer
  7. this.pipeGenerator = this.game.time.events.loop(Phaser.Timer.SECOND * 1.25, this.generatePipes, this);
  8. this.pipeGenerator.timer.start();
  9.  
  10. this.instructionGroup.destroy();
  11. }

Télécharger

Avec cette méthode, lorsqu’un joueur interagit avec le jeu pour la première fois, nous activon la gravité sur le corps physique de notre oiseau et réglons sa propriété alive à true. Nous avons également bougé la création de colonnes de la méthode create() à la méthode startGame(). Maintenant, lorsque l’on interagit avec le jeu, que ce soit en pressant une touche, en cliquant avec la souris ou en touchant un écran tactile, notre oiseau commencera immédiatement à à réagir à la gravité, voler et les colonnes seront générées...

Score

Qu’est ce qu’un jeu sans système de récompense ? Dans notre cas, nous allons implémenter le plus simple des système de récompense : un score !

Pour implémenter un système de score, nous allons avoir besoin de faire les choses suivantes :

  1. Déterminer quand le joueur marque des points
  2. Augmenter le score lorsque le joueur marque des points
  3. Afficher le score

Dans game/states/play.js, ajoutons les lignes suivantes au bas de la méthode create(), avant le groupe instructionGroup :

  1. create: function() {
  2. ...
  3.  
  4. this.score = 0;
  5.  
  6. ...
  7. },

Télécharger

C’est notre « mémoire » de score. Et nous allons l’incrémenter au fur et à mesure que le joueur marque des points évidemment.

Mais quand notre joueur marque-t-il des points ?

Dans la version classique de Flappy Bird, le joueur marque des points lorsque l’oiseau au minimum la moitié d’une paire de colonnes. Nous allons reproduire cela.

Dans game/states/play.js, dans la méthode update(), nous avons actuellement :

  1. update: function() {
  2. // enable collisions between the bird and the ground
  3. this.game.physics.arcade.collide(this.bird, this.ground, this.deathHandler, null, this);
  4.  
  5. // enable collisions between the bird and each group in the pipes group
  6. this.pipes.forEach(function(pipeGroup) {
  7. this.game.physics.arcade.collide(this.bird, pipeGroup, this.deathHandler, null, this);
  8. }, this);
  9. },

Télécharger

Nous parcourons déjà le groupe PipeGroups qui est stocké dans this.pipes... Allez ! Ajoutons un autre appel à l’intérieur de la boucle :

  1. update: function() {
  2. // enable collisions between the bird and the ground
  3. this.game.physics.arcade.collide(this.bird, this.ground, this.deathHandler, null, this);
  4.  
  5. // enable collisions between the bird and each group in the pipes group
  6. this.pipes.forEach(function(pipeGroup) {
  7. this.checkScore(pipeGroup);
  8. this.game.physics.arcade.collide(this.bird, pipeGroup, this.deathHandler, null, this);
  9. }, this);

Télécharger

Nous appelons maintenant this.checkScore() en passant un argument qui est une référence à un seul PipeGroup. Comme nous parcouru déjà PipeGroups dans this.pipes, nous n’avons rien d’autre à faire que de créer la méthode qui contiendra la logique qui vérifiera si notre joueur à marqué un point.

Allons sous la méthode startGame() et ajoutons la méthode checkScore() :

  1. ... ,
  2. checkScore: function(pipeGroup) {
  3. if(pipeGroup.exists && !pipeGroup.hasScored && pipeGroup.topPipe.world.x <= this.bird.world.x) {
  4. pipeGroup.hasScored = true;
  5. this.score++;
  6. this.scoreText.setText(this.score.toString());
  7. }
  8. }

Télécharger

La partie condition est un peu ardue, non ? Prenez chaque élément un par un :

  1. if(pipeGroup.exists)

Ici nous vérifions que le PipeGroup que nous venon de passer a bien la propriété exists à true. Souvenez-vous, nous avons régler notre prefab PipeGroup pour automatiquement mettre la propriété exists à false lorsqu’il quitte les frontière du monde du jeu.. Et notre itérateur de la méthode update() de notre état play passe en revue tous les enfants qu’il contient. Du coup, cette première condition vérifie si le PipeGroup doit être pris en considération ou pas...

La deuxième condition :

  1. if(!pipeGroup.hasScored)

Cela vérifie que nous n’avons pas déjà pris en compte dans le score ce PipeGroup. Souvenez-vous, nous avions ajouté la propriété hasScored au prefab PipeGroup pour cela exactement.

et finalement :

  1. if(pipeGroup.topPipe.world.x <= this.bird.world.x)

Cela vérifie que la position de notre oiseau dans l’axe des x est plus grande ou égale à la position dans le monde de la colonne du haut du prefab passé par l’argument PipeGroup.

Qu’est ce que la position dans le monde ?

La position dans le monde ( world position ) est la position d’un objet du jeu relativement au monde du jeu et pas par rapport à son parent ou ni sa position locale. Dans notre cas, la position locale x de la colonne du haut serait négative de 300 à 400 parce qu’il bouge par rapport à son parent vers la gauche. La position x dans le monde de la colonne du haut devrait cependant être de 100.

Souvenez-vous, nous avons régler l’ancre de notre oiseau ( bird ) et de nos colonnes ( pipeGroups ) à (0.5, 0.5) Donc la position x des ces objets sera toujours le centre horizontal de ces objets.

Littéralement, la condition fait que :

Lorsque le pipeGroup est pris en considération, que le score n’a pas encore était établi et que la position dans les x de notre oiseau est plus grande ou égale à la position dans le monde du jeu de topPipe alors, régler la propriété hasScored du groupe PipeGroup à true ( ce qui empêchera de renouveller l’action de marquer aux prochains appels de update() ), augmente notre score et appelle la méthode setText de l’objet scoreText pour afficher notre score selon la propriété numérique score.

Attendez ! Nous n’avons pas d’objet scoretext !
Ne vous inquiètez pas... Nous sommes juste sur le point d’introduire un nouveau concept !

Créer des objets BitmapText

Au lieu d’utiliser des polices mille fois vues que tout le monde possède sur son système (Arial, Times, Verdana, Georgia), nous pouvons fournir à phaser une image png et un fichier XML de correspondance entre un caractère et son pendant sur l’image PNG. Il n’y a pas besoin de créer cela à la main. Jeremy adore le littera bitmap font generator pour transformer n’importe quel fichier de font en fichier Bitmap Font utilisable par Phaser. Jeremy a fait cela pour la font que nous allons utiliser. Nous devons juste la charger.

Dans le fichier game/states/preload.js, ajoutons les lignes suivantes à la méthode preload() :

  1. preload: function() {
  2. ...
  3. this.load.bitmapFont('flappyfont', 'assets/fonts/flappyfont/flappyfont.png', 'assets/fonts/flappyfont/flappyfont.fnt');
  4. ...
  5. }

Télécharger

Nous chargeons maintenant la bitmap font que nous pouvons utiliser ailleurs.

Allons maintenant dans le fichier de l’état de jeu ( play ) game/states/play.js et ajoutons au bas de de la méthode create(), les lignes suivantes :

  1. function create() {
  2. ...
  3. this.scoreText = this.game.add.bitmapText(this.game.width/2, 10, 'flappyfont',this.score.toString(), 24);
  4. this.scoreText.visible = false;
  5. ...
  6. },

Télécharger

Même si c’est un nouveau morceau de code, cela devrait nous paraître familier :

  1. var text = this.game.add.bitmapText(x, y, 'assetKey', 'text', fontSize);

Les seules différences entre l’objet que nous venont de créer et ceux que nous avons créés précédemment sont les arguments 'text' et fontSize.

Dans notre implémentation, nous disont à Phaser d’ajouter un nouvel objet de type BitmapText positionné à la moitié de l’écran selon l’horizontal et à 10 pixels selon la vertical en utilisant l’asset repéré par la clé flappyfont avec une taille de 24 et une chaîne de caractères qui issue de la propriété score.

Simple, non ?

Cela, en conjonction avec la méthode checkScore() affichera le score et le mettra à jour lorsque nécessaire.

Voyons le résultat de cela :

Fantastique !!!

Et maintenant... Nous allons... Ajouter...

Le son !

Le son est important dans un jeu. Les différents son dans un jeu aide à avertir qu’il se passe quelque chose sans qu’il ai à observer en détail tout l’écran. Dans ce cas, le son le plus important est celui qui informe le joueur qu’il a marqué un point !

En avant donc ! Chargeons les sons dont nous allons avoir besoin dans notre jeu !

Dans game/states/preload.js, au bas de la méthode preload(), ajoutons les lignes suivantes :

  1. preload: function() {
  2. ...
  3. this.load.audio('score', 'assets/score.wav');
  4. this.load.audio('flap', 'assets/flap.wav');
  5. this.load.audio('pipeHit', 'assets/pipe-hit.wav');
  6. this.load.audio('groundHit', 'assets/ground-hit.wav');
  7. ...
  8. },

Télécharger

Nous avons maintenant les fichiers audio dans le cache du jeu. Vous avez sans doute remarquez que nous avons utiliser la méthode load.audio(). Cela dit à Phaser de nonseulement chargé le son depuis l’url, mais aussi de le décoder, enfin, si il est encodé... Dans notre cas, les .wav sont considéré comme sont considéré comme des fichier audio brut et non pas besoin d’être décodé.

Retournons à game/states/play.js et ajoutons les lignes suivantes au bas de la méthode create()

  1. create: function() {
  2. ...
  3. this.scoreSound = this.game.add.audio('score');
  4. ...
  5. },

Télécharger

Une fois de plus, ce style de syntaxe ne devrait pas vous surprendre maintenant.

Pour finir, rendez-vous à checkScore() et modifiez le score pour qu’il ressemble à cela :

  1. checkScore: function(pipeGroup) {
  2. if(pipeGroup.exists && !pipeGroup.hasScored && pipeGroup.topPipe.world.x <= this.bird.world.x) {
  3. pipeGroup.hasScored = true;
  4. this.score++;
  5. this.scoreText.setText(this.score.toString());
  6. this.scoreSound.play();
  7. }
  8. }

Télécharger

Tout ce que nous avons fait, c’est d’ajoutez une ligne qui dit à Phaser d’appeler la méthode play() de note objet audio soreSound. Maintenant, chaque fois qu’un joueur marque un point, on entend un "coin" !

Ajoutons maintenant le son de battement des ailes à notre prefab Bird et faisons le jouer lorsque notre oiseau vole vers le haut :

  1. var Bird = function(game, x, y, frame) {
  2. ...
  3.  
  4. this.flapSound = this.game.add.audio('flap');
  5.  
  6. ...
  7. };
  8.  
  9. ...
  10.  
  11. Bird.prototype.flap = function() {
  12. this.flapSound.play();
  13. //cause our bird to "jump" upward
  14. this.body.velocity.y = -400;
  15. // rotate the bird to -40 degrees
  16. this.game.add.tween(this).to({angle: -40}, 100).start();
  17. };

Télécharger

Maintenant, chaque fois que la méthode flap() est appelé, le son « flap » est joué !

Ca pète du tonnerre de dieu !

Game Over

Dans la plupart des jeux, le PlayState et le GameOver seront deux écrans complètement différents. Vous irez à l’écran de GameOver lorsque qu’un événement particuliers sera rencontré, la mort de l’oiseau dans notre cas. Cependant, dans Flappy Bird, il apparaît simplement un tableau de bord qui affiche le score de la partie que vous venez de terminer, votre meilleur score et et les médailles que vous avez gagné. De ce fait, nous n’allons pas avoir le traditionnel état GameOver. Vous pouvez donc effacer le fichier game/states/gameover.js. En effet, nous ne l’utiliserons pas.

Ce qui veut dire que nous allons devoir travailler sur...

Le tableau des scores

Ouvrez un terminal et créé un prefab Scoreboard. Puis, ouvrez le fichier game/prefabs/scoreboard.js

Scoreboard va hériter de Phaser.Group au lieu de Phaser.Sprite. Comme ce fut le cas pour PipeGroup. Faisons donc les changements suivants :

  1. var Scoreboard = function(game) {
  2.  
  3. Phaser.Group.call(this, game)
  4.  
  5. };
  6.  
  7. Scoreboard.prototype = Object.create(Phaser.Group.prototype);
  8. Scoreboard.prototype.constructor = Scoreboard;

Télécharger

Maintenant que Scoreboard est une sous classe de Phaser.Group, nous pouvons y ajouter des sprites.

Allons-y et ajoutons les assets scoreboard.png, gameover.png, medals.png, and particle.png au chargeur game/states/preload.js.

  1. preload: function() {
  2. ...
  3. this.load.image('scoreboard', 'assets/scoreboard.png');
  4. this.load.image('gameover', 'assets/gameover.png');
  5. this.load.spritesheet('medals', 'assets/medals.png', 44, 46, 2);
  6. this.load.image('particle', 'assets/particle.png');
  7. ...
  8. },

Télécharger

Maintenant, ajoutons le code suivant à game/prefabs/scoreboard.js :

  1. var Scoreboard = function(game) {
  2.  
  3. var gameover;
  4.  
  5. Phaser.Group.call(this, game);
  6. gameover = this.create(this.game.width / 2, 100, 'gameover');
  7. gameover.anchor.setTo(0.5, 0.5);
  8.  
  9. this.scoreboard = this.create(this.game.width / 2, 200, 'scoreboard');
  10. this.scoreboard.anchor.setTo(0.5, 0.5);
  11.  
  12. this.scoreText = this.game.add.bitmapText(this.scoreboard.width, 180, 'flappyfont', '', 18);
  13. this.add(this.scoreText);
  14.  
  15. this.bestScoreText = this.game.add.bitmapText(this.scoreboard.width, 230, 'flappyfont', '', 18);
  16. this.add(this.bestScoreText);
  17.  
  18. // add our start button with a callback
  19. this.startButton = this.game.add.button(this.game.width/2, 300, 'startButton', this.startClick, this);
  20. this.startButton.anchor.setTo(0.5,0.5);
  21.  
  22. this.add(this.startButton);
  23.  
  24. this.y = this.game.height;
  25. this.x = 0;
  26.  
  27. };

Télécharger

Il n’y a rien que nous n’ayons déjà vu. La chose la plus importante à noter est le fait que nous ajoutons tous les sprites et tous les objets texte au groupe en appelant this.add(gameObject);. Une autre chose à remarquer est est que nous réglons la position y initial du groupe pour être celle de la hauteur de du jeu, this.game.height. Ce qui siginifie que tous les objet ajouté à notre Scoreboard sera sous la limite de ce qui est visible dans l’écran du jeu proprement dit... C’est ainsi que nous pourrons tweener notre Scoreboard vers le haut pour le rendre visible.

Est ce que l’ordre de création a de l’importance ?

Absoluement ! Dans un Group, un State, Sprite ou n’importe quel objet du jeu qui peut avoir des enfants, l’ordre de création affecte leur « layer » ou couche dans lequel ils se trouveront.. Par exemple, dans le code ci-dessus, nous avons créé l’objet scoreboard avant les objets scoreText et bestScoreText. Nous avons réglé la position des objets texte de tel sorte qu’ils soient par dessus l’objet scoreboard. Si nous avions créé l’objet scoreboard après les objets texte, le sprite du scoreboard aurait été par dessus les couches de ces objets texte et nous n’aurions pas pu les voir. C’est aussi pourquoi nous avons créé l’objet background en premier dans notre état PlayState. Si nous l’avions créé après le Bird ou le groupe Pipes, le sprite background aurait été par dessus et nous n’aurions pas vu le reste du jeu...

Affichons le tableau des scores

Nous avons aussi besoin de créer une méthode show() dans notre prefab Scoreboard qui fait les chose suivantes :

  1. Mettre à jour scoretext pour afficher le score
  2. Regarder dans le stockage local pour connaitre la valeur du meilleur score, bestScore.
  3. La logique pour déterminer si score est plus haut que bestScore et le stocker si c’est le cas
  4. Mettre à jour « bestScoreText » pour afficher le bestScore
  5. Déterminer si on affiche ou pas une médaille
  6. Positionner la médaille
  7. Si une médaille est a affichée, créer et lancer un émetteur de particules pour afficher des scintillements
  8. Tweener le groupe pour qu’il soit visible
  1. Scoreboard.prototype.show = function(score) {
  2. var medal, bestScore;
  3.  
  4. // Step 1
  5. this.scoreText.setText(score.toString());
  6.  
  7. if(!!localStorage) {
  8. // Step 2
  9. bestScore = localStorage.getItem('bestScore');
  10.  
  11. // Step 3
  12. if(!bestScore || bestScore < score) {
  13. bestScore = score;
  14. localStorage.setItem('bestScore', bestScore);
  15. }
  16. } else {
  17. // Fallback. LocalStorage isn't available
  18. bestScore = 'N/A';
  19. }
  20.  
  21. // Step 4
  22. this.bestScoreText.setText(bestScore.toString());
  23.  
  24. // Step 5 & 6
  25. if(score >= 10 && score < 20)
  26. {
  27. medal = this.game.add.sprite(-65 , 7, 'medals', 1);
  28. medal.anchor.setTo(0.5, 0.5);
  29. this.scoreboard.addChild(medal);
  30. } else if(score >= 20) {
  31. medal = this.game.add.sprite(-65 , 7, 'medals', 0);
  32. medal.anchor.setTo(0.5, 0.5);
  33. this.scoreboard.addChild(medal);
  34. }
  35.  
  36. // Step 7
  37. if (medal) {
  38.  
  39. var emitter = this.game.add.emitter(medal.x, medal.y, 400);
  40. this.scoreboard.addChild(emitter);
  41. emitter.width = medal.width;
  42. emitter.height = medal.height;
  43.  
  44. emitter.makeParticles('particle');
  45.  
  46. emitter.setRotation(-100, 100);
  47. emitter.setXSpeed(0,0);
  48. emitter.setYSpeed(0,0);
  49. emitter.minParticleScale = 0.25;
  50. emitter.maxParticleScale = 0.5;
  51. emitter.setAll('body.allowGravity', false);
  52.  
  53. emitter.start(false, 1000, 1000);
  54.  
  55. }
  56. this.game.add.tween(this).to({y: 0}, 1000, Phaser.Easing.Bounce.Out, true);
  57. };

Télécharger

Etape Par étape

1 : Mettre à jour scoretext pour afficher le score

  1. this.scoreText.setText(score.toString());

Un appel à la méthode de l’objet texte setText() mettra à jour le teste afficher par cet objet.

2 :Regarder dans le stockage local pour connaitre la valeur du meilleur score, bestScore.

  1. bestScore = localStorage.getItem('bestScore');

Si vous n’êtes pas familier avec les possibilité du localStorage de l’HTML5, je vous suggère de lire cela ou de chercher des informations à ce sujet grâce à notre ami Google.

3 : La logique pour déterminer si score est plus haut que bestScore et le stocker si c’est le cas

  1. if(!bestScore || bestScore < score) {
  2. bestScore = score;
  3. localStorage.setItem('bestScore', bestScore);
  4. }

Télécharger

4 :Mettre à jour « bestScoreText » pour afficher le bestScore

  1. this.bestScoreText.setText(bestScore.toString());

C’est exactement comme à l’étape 1.

5 & 6 : Déterminer si on affiche ou pas une médaille et, le cas échéant, positionner la médaille

  1. if(score >= 10 && score < 20)
  2. {
  3. medal = this.game.add.sprite(-65 , 7, 'medals', 1);
  4. medal.anchor.setTo(0.5, 0.5);
  5. this.scoreboard.addChild(medal);
  6. } else if(score >= 20) {
  7. medal = this.game.add.sprite(-65 , 7, 'medals', 0);
  8. medal.anchor.setTo(0.5, 0.5);
  9. this.scoreboard.addChild(medal);
  10. }

Télécharger

Dans le Flappy Bird original, lorsque l’on atteignait un score de 10 points, on obtenait une médaille de bronze. Un score de 25 ou plus vous rapportait une médaille de d’argent, et un score de 50 ou plus, une médaille d’or ( Enfin, c’est ce que crois Jeremy, il n’a jamais été si loin ). Pour simplifier les choses, il a décidé que deux médailles serait suffisant : un médaille de bronze à 10+ et et une médaille platinum à 25+ points.

Nous positionnons les médailles relativement aux origines du sprite scoreboard et réglons l’ancre des médailles à (0.5,0.5) .

Finalement, nous ajoutons la medal comme enfant du sprite scoreboat

7 : Si une médaille est a affichée, créer et lancer un émetteur de particules pour afficher des scintillements

  1. if (medal) {
  2. var emitter = this.game.add.emitter(medal.x, medal.y, 400);
  3. this.scoreboard.addChild(emitter);
  4. emitter.width = medal.width;
  5. emitter.height = medal.height;
  6.  
  7. emitter.makeParticles('particle');
  8.  
  9. emitter.setRotation(-100, 100);
  10.  
  11. emitter.minParticleScale = 0.25;
  12. emitter.maxParticleScale = 0.5;
  13.  
  14. emitter.setXSpeed(0,0);
  15. emitter.setYSpeed(0,0);
  16.  
  17. emitter.setAll('body.allowGravity', false);
  18.  
  19. emitter.start(false, 1000, 1000);
  20.  
  21. }

Télécharger

Youpi, un nouveau truc !!!!

Qu’est ce qu’un émetteur de particules

Un émetteur de particules est un type d’objet particuliers qui peut générer des groups d’autres objets appellé particules. Ils peuvent être utiliser pour créer de nombreux effets spéciaux incluant : rain, neige, feuilles, feu,fumée, brouillard, chutes d’eau,feux d’artifice, etc.

Avec Phaser, la classe Phaser.Emitter hérite de Phaser.Group. Ce qui veut dire qu’il peut accéder à toutes les méthodes des groupes mais aussi aux méthodes spéciales de Phaser.Emitter qui ont été créé spécifiquement pour ce propos.

Regardons l’étape 7 ligne par ligne :

  1. if (medal) {

Nous ne voulons pas faire ce qui suit si medal n’est pas défini

  1. var emitter = this.game.add.emitter(medal.x, medal.y, 400);

Cette syntaxe devrait vous être familière. Les arguments passés sont les position x et y et le nombre maximum de particules à créer.

  1. this.scoreboard.addChild(emitter);

Nous ajoutons l’émetteur comme enfant du sprite scoreboard. Ainsi nous pourrons modifier sa position relative.

  1. emitter.width = medal.width;
  2. emitter.height = medal.height;

Télécharger

Nous spécifions la hauteur et la largeur de l’émetteur selon celles du sprite medal. Avec un émetteur, les propriétés hauteur et largeur dicte la surface depuis laquelle sont émises les particules. Donc, si un émetteur a une largeur de 30 et une hauteur de 10, une particule peut être émise avec une position x de 0 à 30 ( relativement à l’émetteur ). Le même concept s’applique pour la position y ( de 0 à 10 ). Cela donne une position aléatoire pour le point démission. Sans ce réglage, les particules seraient toujours émise depuis le même point.

  1. emitter.makeParticles('particle');

Cette ligne unique dit à l’émetteur de créer touts les particules en utilisant la clé d’asset 'particle'. Auparavant, nous avons dit à l’émetteur d’avoir une capacité de 400 particules. Donc après cette ligne, notre émetteur aura 400 enfants.

  1. emitter.setRotation(-100, 100);

Cette ligne dit à l’émetteur de choisir une valeur de rotation aléatoire entre -100 et 100 pour chacune des particules émises.

  1. emitter.minParticleScale = 0.25;
  2. emitter.maxParticleScale = 0.5;

Télécharger

Ceci définit une plage de valeurs pour l’échelle de particule que l’émetteur peut choisir lorsqu’il émet une particule. L’échelle choisie s’appliquera homothétiquement à la particule.

  1. emitter.setXSpeed(0,0);
  2. emitter.setYSpeed(0,0);

Télécharger

C’est un appel à la méthode de l’émetteur setXSpeed()  :

  1. emitter.setXSpeed(min, max);

Cela dit à notre émetteur de choisir une valeur aléatoire entre le min et le max pour chacune des particules. Il en est de même pour la méthode setYSpeed()

Cependant, nous ne voulons pas que nos particules bougent... Alors nous réglons la plage de vitesse à (0,0). Cela fait que nos particules devrait rester en place. Mais...

  1. emitter.setAll('body.allowGravity', false);

Comme l’émetteur est basé sur l’ « Arcade physics system », chacunes des particules sera soumis au game.physics.arcade.enableBody(). Sans cette ligne de code, toutes les particules tomberait, soumise à la gravité spécifiée.

  1. emitter.start(false, 1000, 1000);

Cela déclenche tout ! La class Phaser.Emitter a beaucoup d’options et et de propriétés lorsque l’on appelle certaines de ces méthodes, et start() n’est pas une exception.

L’appel complet accepte les arguments suivants :

  1. emitter.start(explode, lifespan, frequency, quantity)

explode est un booléen. Lorsqu’il est régler à true, toutes les particules seront émises en même temps. lifespan est un nombre. Il détermine, en millisecondes, la durée de vie des particules. frequency est un nombre. Il détermine, en millisecondes, la fréquence d’émission des particules. Ce paramètre est ignorés si explode est est réglé à true. quantity est un nombre. Il détermine combien de particules sont émises.

Notre appel :

  1. emitter.start(false, 1000, 1000);

Cela dit à notre émetteur de ne pas émettre de particule d’un seul coup, mais d’émettre une particule toute les secondes avec une durée de vie d’une seconde.

Yop !

Bon, ben, voilà !

8 : Tweener le groupe pour qu’il soit visible

  1. this.game.add.tween(this).to({y: 0}, 1000, Phaser.Easing.Bounce.Out, true);

Une fois de plus, il n’y arien que nous n’ayons vu. Nous faisons aller notre groupe de sa position y actuelle à une position y de 0 sur une course d’une durée d’une seconde... En utilisant la méthode Phaser.Easing.Bounce.Out.

Relancer le jeu

Nous devons ajouter une méthode de plus à notre prefab Scoreboard

  1. Scoreboard.prototype.startClick = function() {
  2. this.game.state.start('play');
  3. };

Télécharger

Lorsque le bouton start est cliqué, cela va relancer PlayState et le jeu recommencera.

Instancions notre tableau de score

Maintenant que nous avons construit notre prefab Scoreboard, nous devons l’ajouter et l’afficher le moment venu depuis notre PlayState. Ouvrons le fichier game/states/play.js et allons à la méthode deathHandler() et modifions la comme cela :

  1. deathHandler: function() {
  2. this.bird.alive = false;
  3. this.pipes.callAll('stop');
  4. this.pipeGenerator.timer.stop();
  5. this.ground.stopScroll();
  6. this.scoreboard = new Scoreboard(this.game);
  7. this.game.add.existing(this.scoreboard);
  8. this.scoreboard.show(this.score);
  9. },

Télécharger

Ne pas oublier de mettre var Scoreboard = require('../prefabs/scoreboard'); au début de ce fichier.

Maintenant, lorsque les conditions seront réunies pour un « Game Over », le tableau des scores coulissera au dessus du jeu en lui-même.

Une dernière chose

Nous devons nettoyer notre PlayState avant qu’il soit instancié à nouveau. Auparant, nous avons vu comment ajouter une méthode shutdown() . Nous devons la modifier pour que tout soit bien nettoyé :

  1. shutdown: function() {
  2. this.game.input.keyboard.removeKey(Phaser.Keyboard.SPACEBAR);
  3. this.bird.destroy();
  4. this.pipes.destroy();
  5. this.scoreboard.destroy();
  6. },

Télécharger

Maintenant, chaque fois que le Playstate est détruit avant qu’un nouveau soit créé, les objets de notre jeu sont retirés par le ramasse-miettes et nous n’allons pas surcharger la mémoire.

Et... C’est un jeu !

Enfin, presque ! La version de démo et le jeu que nous avons ne sont pas exactement les mêmes... Jeremy a passé du temps dessus pour ajouter certaines choses. je vous invite à faire la même chose. Cloner : github : Flappy Bird Reborn et regarder les différences et les améliorations faites par Jeremy !

Vous avez aimé ? Partager !

Twitter Facebook Google Plus Tumblr Linkedin
Cet article à 5 articles connexes :