⬆️ ⬇️

Develop Flappy Bird on Phaser



Picture to attract attention



Good day, Habr!



Somewhere a month ago (at the time of writing this post) I set out to create my own clone of the game Flappy Bird. But all did not reach this hand. The catalyst for this action was a small hackathon. “And why not?” I thought, and took up the implementation of this game.

')

Considering that it was necessary to develop in 2 days, I did not invent a “bicycle” and took the ready-made game engine - the Phaser.



In this part we will look at the initialization of the game scene, write a “preloader” of resources and prepare the foundation for the game menu.





What is a Phaser?



HTML5 games. It uses Pixi.js internally for fast 2D Canvas and WebGL rendering.


Phaser is a framework that allows us to create games very quickly. I do not exaggerate, using it to create a game is really easy and fast. We are not distracted by Actors, rendering, physics - we focus on the game logic.

Its unique advantages are Pixi.js. This is one of the fastest engines that renders using WebGL. And in case WebGL is not supported - on Canvas.

Phaser also pleased with a huge set of ready-made classes: SpriteAnimation, TileMap, Timer, GameState and much more. Including, and components of the physical engine: RigidBody, Physics, etc.

The availability of these components greatly simplifies development.



We connect Phaser and other dependences



I didn't load the game with a lot of dependencies, so the list is small: Phaser, WebFont and Clay. The first is needed to develop the game, WebFont to download fonts from Google Fonts and Clay for the high score table.



The code below is contained in the index.html file.

index.html
<!DOCTYPE html> <head> <meta charset="utf-8"> <title>Flappy Bird</title> <link rel="shortcut icon" href="/favicon.ico" /> <style type="text/css"> * { margin: 0; padding: 0; } </style> </head> <body> <script type="text/javascript"> var Clay = Clay || {}; Clay.gameKey = "gflappybird"; Clay.readyFunctions = []; Clay.ready = function(fn) { Clay.readyFunctions.push(fn); }; (function() { var clay = document.createElement("script"); clay.async = true; clay.src = ("https:" == document.location.protocol ? "https://" : "http://") + "clay.io/api/api-leaderboard-achievement.js"; var tag = document.getElementsByTagName("script")[0]; tag.parentNode.insertBefore(clay, tag); })(); </script> <script src="//ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/phaser/1.1.4/phaser.min.js"></script> <script src="js/Game.js"></script> </body> </html> 


In index.html we simply add dependencies, nothing superfluous. This includes our Game.js script, which we will look at later. We do not add a single line of HTML, because Phaser renders the scene directly in body.

Phaser can also render to the container you created, if necessary.



We connect fonts



There is only one function in Game.js - GameInitialize () . In the closure of this function and all the calculations occur. Before calling it, you need to wait for the download fonts. Otherwise, there is a high probability that the fonts will not have time to load and they will not be available Phaser. For this we use WebFont:



 WebFont.load({ google: { families: ['Press+Start+2P'] }, active: function() { GameInitialize(); } }); 


We “asked” WebFont to download the “Press Start 2P” font from Google Fonts, and when the download is finished, we call the GameInitialize () function, which will continue to initialize all the necessary game objects.



In the future, the content of the post will be described exclusively within the GameInitialize () function .



We declare constants, create an instance of Phaser.Game, add gamestates



To begin with, let's add variables that will have de facto values ​​when used. Since the use of const is not too “valid”, we use variables:

Game constants
 var DEBUG_MODE = true, //   SPEED = 180, //   GRAVITY = 1800, //     BIRD_FLAP = 550, //    "" PIPE_SPAWN_MIN_INTERVAL = 1200, //     PIPE_SPAWN_MAX_INTERVAL = 3000, //  AVAILABLE_SPACE_BETWEEN_PIPES = 130, //     ( ) CLOUDS_SHOW_MIN_TIME = 3000, //     CLOUDS_SHOW_MAX_TIME = 5000, //     MAX_DIFFICULT = 100, //         SCENE = '', // ,   .     (    body) TITLE_TEXT = "FLAPPY BIRD", //     HIGHSCORE_TITLE = "HIGHSCORES", //   HIGHSCORE_SUBMIT = "POST SCORE", //        INSTRUCTIONS_TEXT = "TOUCH\nTO\nFLY", //    DEVELOPER_TEXT = "Developer\nEugene Obrezkov\nghaiklor@gmail.com", //    :) GRAPHIC_TEXT = "Graphic\nDmitry Lezhenko\ndima.lezhenko@gmail.com", LOADING_TEXT = "LOADING...", //    WINDOW_WIDTH = window.innerWidth || document.documentElement.clientWidth || document.getElementsByTagName('body')[0].clientWidth, WINDOW_HEIGHT = window.innerHeight || document.documentElement.clientHeight || document.getElementsByTagName('body')[0].clientHeight; 


We also need auxiliary variables for storing all the created Phaser objects:

Variables for Phaser Objects
 var Background, //  Clouds, CloudsTimer, //      Pipes, PipesTimer, FreeSpacesInPipes, // ,   "" ,   ""  Bird, // Town, //TileSprite    FlapSound, ScoreSound, HurtSound, // ,     SoundEnabledIcon, SoundDisabledIcon, // \  TitleText, DeveloperText, GraphicText, ScoreText, InstructionsText, HighScoreTitleText, HighScoreText, PostScoreText, LoadingText, //   PostScoreClickArea, //     isScorePosted = false, //  ,    "" isSoundEnabled = true, //  ,     Leaderboard; //  Leaderboard   Clay.io 


Briefly describe what kind of variable and why it is needed.



After all the variables have been declared, we can start initializing Phaser.Game and adding the necessary GameState's to the game.



Phaser.Game () takes the following parameters:

new Game (width, height, renderer, parent, state, transparent, antialias)
We are interested in width , height , renderer , parent . It is enough to specify the size of the canvas, the rendering method and the empty container so that Phaser starts rendering the game scene in the body.



We initialize Phaser.Game using our constants declared earlier:



 var Game = new Phaser.Game(WINDOW_WIDTH, WINDOW_HEIGHT, Phaser.CANVAS, SCENE); 


We initialized the game scene, but we still do not have game states. Need to correct this misstep.

A pointer to a Phaser.StateManager is stored in Game.state. It has the add () function we need to add our own States. Its signature is:

add (key, state, autoStart)
key is the string for identifying the State (its ID), state is the Phaser.State object, and autoStart - whether to start the State immediately after its initialization. In this case, we do not need autoStart, so that we can determine the call of the states at the right moments of the game.

Add all gaming states to the gaming scene:



 Game.state.add('Boot', BootGameState, false); Game.state.add('Preloader', PreloaderGameState, false); Game.state.add('MainMenu', MainMenuState, false); Game.state.add('Game', GameState, false); Game.state.add('GameOver', GameOverState, false); 


Each of these gaming states will be reviewed further.



The last step to start the gameplay loop is to start the BootGameState.



 Game.state.start('Boot'); 


I give the full code of the game initialization:

Game initialization
 // instance       Canvas var Game = new Phaser.Game(WINDOW_WIDTH, WINDOW_HEIGHT, Phaser.CANVAS, SCENE); //  RequestAnimationFrame Game.raf = new Phaser.RequestAnimationFrame(Game); Game.antialias = false; Game.raf.start(); //   State   Game //     State'    Game.state.add('Boot', BootGameState, false); Game.state.add('Preloader', PreloaderGameState, false); Game.state.add('MainMenu', MainMenuState, false); Game.state.add('Game', GameState, false); Game.state.add('GameOver', GameOverState, false); //     Boot State' Game.state.start('Boot'); // Clay Leaderboard      Clay.ready(function() { Leaderboard = new Clay.Leaderboard({ id: 'your-leaderboard-id' }); }); 


How to create gaming states?



Phaser has a Phaser.State () constructor. All you need to create a gaming State is to call this constructor:



 var BootGameState = new Phaser.State(); 


After that, we can override the Phaser functions with our own. In State, there are 4 main loops: create , preload , render , update .



Now consider our starting State, which initializes the game loop.



In the following paragraphs, I will indicate in brackets the name of the variable in which Phaser.State () is stored.



Notify player that download has started (BootGameState)



Create an instance Phaser.State. After its successful download, add the text with the inscription "Loading ..." and place it in the center. Do not forget to start downloading PreloaderState.



 var BootGameState = new Phaser.State(); BootGameState.create = function() { LoadingText = Game.add.text(Game.world.width / 2, Game.world.height / 2, LOADING_TEXT, { font: '32px "Press Start 2P"', fill: '#FFFFFF', stroke: '#000000', strokeThickness: 3, align: 'center' }); LoadingText.anchor.setTo(0.5, 0.5); Game.state.start('Preloader', false, false); }; 




Writing resource “preloader” (PreloaderGameState)



To download the sprite, sound, animation, etc., in Phaser, you can use Phaser.Loader. The pointer to it lies in Game.load after we initialized the scene. Three methods will be enough for our game:



 Phaser.Loader.spritesheet(key, url, frameWidth, frameHeight, frameMax, margin, spacing) Phaser.Loader.image(key, url, overwrite) Phaser.Loader.audio(key, urls, autoDecode) 


Using these methods, we write a function that will load resources into the game:



 var loadAssets = function loadAssets() { Game.load.spritesheet('bird', 'img/bird.png', 48, 35); Game.load.spritesheet('clouds', 'img/clouds.png', 64, 34); Game.load.image('town', 'img/town.png'); Game.load.image('pipe', 'img/pipe.png'); Game.load.image('soundOn', 'img/soundOn.png'); Game.load.image('soundOff', 'img/soundOff.png'); Game.load.audio('flap', 'wav/flap.wav'); Game.load.audio('hurt', 'wav/hurt.wav'); Game.load.audio('score', 'wav/score.wav'); }; 


We now turn to PreloaderGameState. Create a new Phaser.State ().



 var PreloaderGameState = new Phaser.State(); 


Override the preload method, in which we call the loadAssets () function:



 PreloaderGameState.preload = function() { loadAssets(); }; 




After the resources are successfully loaded, the create function is called in which we can add a disappearing animation to the Loading Text and the loading of the MainMenuState.

 PreloaderGameState.create = function() { var tween = Game.add.tween(LoadingText).to({ alpha: 0 }, 1000, Phaser.Easing.Linear.None, true); tween.onComplete.add(function() { Game.state.start('MainMenu', false, false); }, this); }; 


Complete PreloaderGameState () source code:

PreloaderGameState
 var PreloaderGameState = new Phaser.State(); PreloaderGameState.preload = function() { loadAssets(); }; PreloaderGameState.create = function() { var tween = Game.add.tween(LoadingText).to({ alpha: 0 }, 1000, Phaser.Easing.Linear.None, true); tween.onComplete.add(function() { Game.state.start('MainMenu', false, false); }, this); }; 


Eventually



The result of this work is the presence of a game scene, a working preloader. After successful loading of all resources, MainMenuState is called, which is already responsible for drawing the game menu.



useful links



Phaser

Phaser (GitHub)

Phaser (documentation)

Phaser.Game ()

Phaser.Loader ()

Phaser.State ()

Phaser.StateManager ()

Pixi.js (GitHub)



FlappyBird

FlappyBird (GitHub)

UPD: In recent fixes, I removed the fullscreen mode, as many complain about performance.



I want to hear the opinion of the Habrahabr community. Are you interested in the continuation? In the second part, consider the following:

Source: https://habr.com/ru/post/214013/



All Articles