PhaserでFlappy Birdを開発する



注目を集める絵



こんにちは、Habr!



約1か月前(この投稿を書いている時点)で、Flappy Birdゲームの独自のクローンを作成しようと思いました。 しかし、すべてがこの手に届くわけではありませんでした。 この行動のきっかけは小さなハッカソンでした。 「そして、なぜそうではない」と私は考え、このゲームの実装に取り​​掛かりました。



2日で開発する必要があることを考慮して、私は「自転車」を発明せず、既製のゲームエンジンであるPhaserを使用しました。



このパートでは、ゲームシーンの初期化を検討し、リソースの「プリローダー」を記述し、ゲームメニューの基盤を準備します。





フェイザーとは何ですか?



Phaserは、デスクトップおよびモバイルブラウザのHTML5ゲームを作成するための高速で無料の楽しいオープンソースゲームフレームワークです。 内部でPixi.jsを使用して、高速の2D CanvasおよびWebGLレンダリングを行います。


Phaserは、非常に迅速にゲームを作成できるフレームワークです。 ゲームの作成は非常に簡単で高速なので、誇張しません。 アクター、レンダリング、物理学に気を取られることはありません。ゲームロジックに焦点を当てています。

その明確なプラスはPixi.jsです。 これは、WebGLを使用してレンダリングする最速のエンジンの1つです。 また、WebGLがサポートされていない場合-Canvasで。

また、Phaserは、SpriteAnimation、TileMap、Timer、GameStateなど、すぐに使用できる大量のクラスを用意しています。 物理エンジンのコンポーネントを含む:RigidBody、Physicsなど。

これらのコンポーネントが存在すると、開発が大幅に簡素化されます。



Phaserとその他の依存関係を接続します



多くの依存関係を持つゲームをロードしなかったため、リストは小さくなります:Phaser、WebFont、Clay。 1つはゲームの開発に必要で、WebFontはGoogle Fontsからフォントをダウンロードするために、Clayは高得点表用に必要です。



以下のコードは、index.htmlファイルに含まれています。

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>
      
      





index.htmlでは、依存関係をプラグインするだけで、それ以上何もしません。 後で検討するスクリプトGame.jsを含めます。 HTMLの1行は追加しません。 Phaserは、シーンをボディに直接レンダリングします。

Phaserは、必要に応じて、作成したコンテナにレンダリングすることもできます。



フォントを接続します



Game.jsにはGameInitialize()という関数が1つしかありません。 この関数を閉じると、すべての計算が行われます。 呼び出す前に、フォントがロードされるのを待つ必要があります。 そうしないと、フォントをロードする時間がなく、Phaserで使用できない可能性が高くなります。 これを行うには、WebFontを使用します。



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





Google Fontsから「Press Start 2P」フォントをダウンロードするようにWebFontに「要求」し、ダウンロードが完了したら、GameInitialize()関数を呼び出します。これにより、必要なすべてのゲームオブジェクトが引き続き初期化されます。



将来、投稿の内容はGameInitialize()関数のフレームワーク内でのみ説明されます



定数を宣言し、Phaser.Gameのインスタンスを作成し、GameStatesを追加します



開始するには、使用時に事実上の値を持つ変数を追加します。 constの使用は「有効」ではないため、変数を使用します。

ゲーム定数
 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;
      
      





作成されたすべてのPhaserオブジェクトを保存するための補助変数も必要です。

Phaserオブジェクトの変数
 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
      
      





変数の種類と、なぜ必要なのかを簡単に説明します。



すべての変数を宣言したら、Phaser.Gameの初期化を開始し、必要なGameStateをゲームに追加します。



Phaser.Game()は、次のパラメーターを受け入れます。

新しいゲーム(幅、高さ、レンダラー、親、状態、透明、アンチエイリアス)
高さレンダラー親に興味があります 。 Phaserが本体のゲームシーンのレンダリングを開始できるように、キャンバスの寸法、レンダリング方法、空のコンテナーを指定するだけで十分です。



前に宣言した定数を使用してPhaser.Gameを初期化します。



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





ゲームシーンを初期化しましたが、ゲームState'ovはまだありません。 この間違いを修正する必要があります。

Game.stateは、 Phaser.StateManagerへのポインターを格納します。 独自のState'ovを追加する必要があるadd()関数があります。 彼女の署名:

追加(キー、状態、autoStart)
keyはState(そのID)を識別するための文字列、 stateはPhaser.Stateオブジェクト、 autoStartは初期化の直後にStateを開始します。 この場合、ゲームの適切なタイミングで状態の呼び出しを判断できるように、autoStartは必要ありません。

すべてのゲーム状態をゲームシーンに追加します。



 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);
      
      





これらのゲームの状態のそれぞれについてさらに説明します。



ゲームプレイループを開始する最後の手順は、BootGameStateを開始することです。



 Game.state.start('Boot');
      
      





完全なゲーム初期化コードを提供します。

ゲームの初期化
 // 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' }); });
      
      





ゲームの状態を作成する方法は?



PhaserにはPhaser.State()コンストラクターがあります。 ゲームステートを作成するために必要なのは、このコンストラクターを呼び出すことだけです。



 var BootGameState = new Phaser.State();
      
      





その後、自分でPhaser関数の実行をオーバーライドできます。 Stateには、 createpreloadrenderupdateの 4つのメインループがあります



次に、ゲームループを初期化する開始状態を検討します。



後続のセクションでは、Phaser.State()が格納されている変数の名前を括弧内に示します



ダウンロードが開始されたことをプレーヤーに通知する(BootGameState)



インスタンスPhaser.Stateを作成します。 正常にロードされたら、「Loading ...」という碑文のテキストを追加し、中央に配置します。 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); };
      
      







リソースの「プリローダー」を作成します(PreloaderGameState)



スプライト、サウンド、アニメーションなどをPhaserにロードするには、Phaser.Loaderを使用できます。 シーンを初期化した後、Game.loadにポインタがあります。 ゲームには3つの方法で十分です。



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





これらのメソッドを使用して、リソースをゲームにロードする関数を作成します。



 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'); };
      
      





次に、PreloaderGameStateに進みましょう。 新しいPhaser.State()を作成します。



 var PreloaderGameState = new Phaser.State();
      
      





プリロードメソッドを再定義し、loadAssets()関数を呼び出します。



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







リソースのロードに成功すると、create関数が呼び出されます。この関数では、テキストのロードと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); };
      
      





完全なソースコードPreloaderGameState():

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); };
      
      





最後に



この作業の結果は、ゲームシーン、動作するプリローダーの存在です。 すべてのリソースのロードが正常に完了すると、MainMenuStateが呼び出されます。これは、ゲームメニューの描画をすでに担当しています。



便利なリンク



フェイザー

フェイザー(GitHub)

Phaser(ドキュメント)

Phaser.Game()

Phaser.Loader()

Phaser.State()

Phaser.StateManager()

Pixi.js(GitHub)



ゆるい鳥

FlappyBird(GitHub)

UPD:最近の修正では、多くの人がパフォーマンスに不満を持っているため、フルスクリーンモードを削除しました。



Habrahabrコミュニティの意見を聞きたいです。 続編に興味がありますか? 2番目の部分では、次のことを考慮します。




All Articles