
注目を集める絵
こんにちは、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
変数の種類と、なぜ必要なのかを簡単に説明します。
- 背景 -ここでは、色#53BECEの四角形を保存します。
- クラウドはオブジェクトのグループです。 それぞれが通常のスプライトです。
- CloudsTimerは、新しいクラウドを生成するタイマーです。
- パイプ -オブジェクトのグループ。 雲のように、すべてのオブジェクトはスプライトです。
- PipesTimerは、新しいパイプを生成するタイマーです。
- FreeSpacesInPipes-鳥が飛んだことを判断するために、何らかの形でこのイベントをキャッチする必要があります。 この変数は、トリガーであるスプライトなしのオブジェクトのみを保存します。
- Bird-アニメーション用のRigidBodyとSpriteMapを持つ鳥を保存します。
- タウン -バックグラウンドで移動する都市のTileMap。
- FlapSound-マウス(フラップの羽)をクリックすると再生されるサウンド。
- ScoreSound-パイプを飛行する音。
- HurtSound-ゲームの終わり、パイプとの衝突、またはゲームの世界を超えた音。
- SoundEnabledIcon、SoundDisabledIcon-含まれるサウンドのアイコンを表示する2つのスプライト。同様にオフにします。
- TitleText、InstuctionsText、DeveloperText、GraphicText-ゲームメニューに表示するテキスト要素。
- ScoreText-ゲーム中に表示されるテキスト。
- HighScoreTitleText、HighScoreText、PostScoreText-ハイスコアテーブル内のテキスト。
- LoadingText-ゲームの読み込みテキスト。
- PostScoreClickArea-ユーザーが[スコアの投稿]ボタンをクリックしたかどうかを判断するのに役立つ四角形。
- isScorePosted-同じレコードの再投稿を防ぐためのフラグ(ユーザーがハイスコアで投稿スコアをダブルクリックした場合)。
- isSoundEnabled-ゲームでサウンドをオン/オフするかどうかを決定するフラグ。
- 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には、 create 、 preload 、 render 、 updateの 4つのメインループがあります 。
- Phaser.State.createは、状態の変更が成功した後に呼び出されます。 ここで、ゲームのロジックの初期化、変数の入力などを記述できます。
- Phaser.State.preloadが呼び出され、リソースのロード中に実行されます。 ある種のスプライトまたはサウンドをロードする必要がある場合は、ここで実行します。
- Phaser.State.renderは、フレームがレンダリングされるたびに呼び出されます。 ここでレンダリング操作を行います。
- Phaser.State.updateは、レンダリング後に呼び出されます。 ここでは、計算を行い、実際にはゲームのビジネスロジックを作成します。
次に、ゲームループを初期化する開始状態を検討します。
後続のセクションでは、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番目の部分では、次のことを考慮します。
- ゲームメニューの作成
- すべてのゲームオブジェクトを初期化する
- 素敵なものを追加します。
- ゲームプレイ自体へのシームレスな移行の基盤を準備します