Programing > Phaser > Tutorial 01
高速でサクサク開発できる、HTML5/Javascript向け2Dゲームエンジン「Phaser」(フェイザー)。
これは、そのチュートリアル「Making your first Phaser game:はじめてのPhaserゲームを作ろう」をやってみたメモ。
というか、ほとんど日本語訳なんだけど、かなり超訳っぽくなっている。
ちなみに、こんな感じのゲームを作る。
ゲームの完成版はココ(矢印キーで操作)
http://www.catch.jp/program/phaser/001/making_your_first_Phaser_game.html
チュートリアルのオリジナル版は、ココ(Phaserの開発元であるPhotom Stormのブログ)に載っていて、Phaserにも収録されている。
http://www.photonstorm.com/phaser/tutorial-making-your-first-phaser-game
Copyright 2014 Yutaka Kachi released under MIT license.
間違いなどあるかも知れません。
もしも見つけたら、Twitter @y_catchへ連絡いただけると助かります。
Copyright 2006 - 2014 Photon Storm Ltd. All rights reserved.
Copyright (c) 2014 Richard Davey, Photon Storm Ltd.
Released under MIT License. see http://opensource.org/licenses/mit-license.php
このチュートリアルを書いたのは、 Alvin Ourrad と Richard Davey。
MITライセンスなPhaser配布セットに含まれている。
Phaserは、HTML5対応でクロスプラットフォームのWebゲーム開発フレームワーク。デスクトップとモバイルに対応、だそうな。
じつは、2DグラフィックライブラリPixi.jsをコアにして、サウンドとか衝突判定とか物理演算を追加した2Dゲーム開発フレームワーク。
PCとモバイルのマルチプラットフォーム対応で、オープンソース(MITライセンス)。
チュートリアルのサンプルファイルのうち、part1.htmlをエディタで開くと、こんなふうになっている。これがPhaserの基本的な骨組み。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Phaser - Making your first game, part 1</title> <script type="text/javascript" src="js/phaser.min.js"></script> <style type="text/css"> body { margin: 0; } </style> </head> <body> <script type="text/javascript"> var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create, update: update }); function preload() { } function create() { } function update() { } </script> </body> </html>
ただし、ただの骨組みなので、このpart1.htmlをブラウザで開いても残念ながら、なにも表示されない。
では、Javascriptの1行目(以下のところ)を見ていこう。
var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create, update: update });
この行では、Phaserのインスタンスを生成して、game変数に割り当てることで、Phaserに生命をあたえる。べつに"game"という変数名でなくてもいいけど、こんなふうにするのが一般的なやり方になっている。ほかのサンプルでも、こんなふうにしてある。
最初の2つの設定値は、幅(width = 800)と高さ(height = 600)で、Phaserが生成するCanvasタグのサイズを指定している。ここでは、800×600ピクセルになっている。これは、自分の好きなサイズを指定できるけど、実行するデバイスの解像度の中に収まっていなければならない。
3番目の設定値は、「Phaser.CANVAS」か「Phaser.WEBGL」か「Phaser.AUTO」のどれか。知っているとおり、CANVASもWebGLもJavascript/HTML5でグラフィックを描く方法で、自分の使いたいものを指定すればいい。オススメは、「Phaser.AUTO」。これは、まずWebGLで描いてみて、ブラウザがサポートしていなければCANVASで描いてくれる。
4番目の設定値は、空っぽの文字列になっているけど、Phaserが生成したCANVASのDOM-IDを指定できる。今回は、空っぽのままなので、body要素にCANVASが挿入される。
最後の設定値で、Phaserで必須となる関数をステータスとしてオブジェクトに割り当てている。使い方は、ここに説明してある。
Note: このオブジェクトにステータスを直接記述する方法はかならずしも最善じゃないけど、ここでは、すばやくプロトタイプを作るために、この方法を採用している。
で、ここで割り当てた関数( preload()、create()、update())の中身を、このあと記述していく。
訳注:これはゲームの進行ごとにステータスを割り当てる。ステートマシンとか状態遷移と呼ばれる技法になっている
では、ゲームで使う素材を指定してロードする。
preload関数の中で、game.loadメソッドを呼ぶと、ゲーム内に素材が読み込まれる。
こんな感じで、今まで空っぽだったpreload関数の中に記述してやる。
function preload() { game.load.image('sky', 'assets/sky.png'); game.load.image('ground', 'assets/platform.png'); game.load.image('star', 'assets/star.png'); game.load.spritesheet('dude', 'assets/dude.png', 32, 48); }
Phaserでこんなふうに記述しておくと、実行時に自動的に素材を読み込んでくれる。
実際のコードは、チュートリアルのpart2.htmlを参照。でも、まだ何も表示されない。
ちなみに、このような素材をAssets(アセット)と呼んでいる。
ここでは、3つの画像と1つのスプライトシートを読み込んでいる。
素材は、チュートリアルのassetsフォルダにあるので、ファイルパスとファイル名で記述してやる。
もうひとつ注目して欲しいのが、game.loadメソッドの最初の引数。'sky' 'ground' 'star' 'dude'という文字列が指定してあるけど、これがアセットキー。ゲームに素材を呼び出すには、このアセットキーをコードから指定する。アセットキーは、Javascriptの文字列なら何でも自由に使える。
スプライトのことは知っているかな?
スプライトとは、2Dゲームでキャラクターや背景のグラフィックを表示するための機能。ゲーム内のキャラクターは、小さな画像(スプライト)として表示される。これをすばやく切り替えることで、アニメーションさせる。
では、そのスプライトを表示させてみよう。まずは、星を1個表示させる。
create関数に、次のコードを記述する。
function create() { game.add.sprite(0, 0, 'star'); }
で、そのhtmlファイルをいったん保存して、ブラウザで表示させると、次のように、左上に星が表示される。
実際のコードは、チュートリアルのpart3.htmlを参照。うーむ、やっと出た!
今のところ、背景は黒色になっている。
スプライトが表示される順番は、コードを記述した順番になる。
だから、星の背景を表示したい場合は、星の前に背景のスプライトのコードを記述する。
ゲームの裏側で、game.add.spriteメソッドは、新しいPhaser.Spriteオブジェクトを生成して、スプライトを“game world”に追加する。この“game world”に、ゲームのオブジェクトが存在している。
この“game world”は、じつはサイズが固定されていないし、無限に広がっている。座標の原点は「0,0」になっていて、一見すると左上のコーナーに割り当てられている。だけど、組み込みのCameraコマンドで自由に調整できる。
では、“game world”を構築してみよう。
先のCreate関数のコードの変わりに、次のように記述する。
var platforms; function create() { // 物理演算エンジンとして、Arcade Physicsシステムをオンにする game.physics.startSystem(Phaser.Physics.ARCADE); // シンプルな背景 game.add.sprite(0, 0, 'sky'); // プラットフォームグループの生成。このグループは、地面(ground)と2つの張り出し(ledge)からできている platforms = game.add.group(); // プラットフォームグループのオブジェクトは、すべて物理演算をオンにする platforms.enableBody = true; // ここで、platformsグループに地面(ground)を追加する var ground = platforms.create(0, game.world.height - 64, 'ground'); // 地面のサイズをゲームの幅にフィットさせる (スプライトのオリジナルサイズは、400x32) ground.scale.setTo(2, 2); // 地面を固定する ground.body.immovable = true; // 同様に、platformsグループに張り出し(ledge)を追加する var ledge = platforms.create(400, 400, 'ground'); ledge.body.immovable = true; ledge = platforms.create(-150, 250, 'ground'); ledge.body.immovable = true; }
実際のコードは、チュートリアルのpart4.htmlを参照。動かないけど、物理演算エンジンが設定されている。
最初のパーツは、星のスプライトと同じだけど、アセットキーが"sky"になっていて、これで背景を指定する。
この背景には、800×600サイズのPNGファイルを読み込んでいる。
Phaserのグループ機能は、ほんとうに強力だ。グループ機能は、その名前が示すとおり、よく似たオブジェクトをひとつにまとめて、単一の部品のようにコントロールする。さらに、グループ間で衝突判定もサポートしている。このサンプルゲームでは、先ほどのコードで作ったplatformグループと、もうひとつのグループとの間で衝突を判定する。
次のコードは、先ほどplatformグループを定義したときのコードだ。
platforms = game.add.group();
ここでは、platformというローカル変数に、グループオブジェクトを代入している。このグループオブジェクトに、ゲーム画面に配置する要素を追加していく。最初に、地面(ground)を追加した。この地面オブジェクトは、ゲーム画面の最下部にあって、"ground"イメージが表示される。地面オブジェクトの幅は、ゲーム画面に合うよう拡大縮小している。それから、"immovable"プロパティを"true"にする。こうしておくと、プレーヤーのキャラクタが地面に衝突しても、地面オブジェクトは固定されている(さらに詳しい説明は、このあと)。
新しいローカル変数"player"を用意して、以下のコードをcreate関数に追加する。
// player変数を用意して、 'dude'スプライトを設定する player = game.add.sprite(32, game.world.height - 150, 'dude'); // 物理演算をオンにする game.physics.arcade.enable(player); // Playerの物理プロパティ. このちっちゃいヤツは、すこしばかりバウンドする player.body.bounce.y = 0.2; player.body.gravity.y = 300; player.body.collideWorldBounds = true; // 左右に歩くためのアニメーション player.animations.add('left', [0, 1, 2, 3], 10, true); player.animations.add('right', [5, 6, 7, 8], 10, true);
実際のコードは、part5.htmlを参照。表示すると、こんな感じ。
左下に見えるプレイヤー:dudeが、物理演算にしたがって、地面の下まで勝手に落ちる。
ここでは、playerスプライトを用意して、横32ピクセル、高さはゲーム画面の最下部から150ピクセルに配置している。この"dude"(デューデュ)アセットは、最初のところでロードしたヤツだ。preload関数のところをチラッとのぞいてみると、これがイメージではなく、スプライトシート(spritesheet)として読み込まれているのが分かるだろう。
game.load.spritesheet('dude', 'assets/dude.png', 32, 48);
スプライトシートは、単一のイメージではなく、動きを表現するアニメーションのコマが、ひとつの画像ファイルにまとめられている。実際のスプライトシートを画像ファイルとして開くと、こんな感じになっている。
コードでは、"left"と"right"という2つのアニメーションを定義している。
player.animations.add('left', [0, 1, 2, 3], 10, true);
"left"アニメーションは、スプライトシートの左から0, 1, 2, 3番目までのコマを使い、1秒当たり10コマの速度で表示する。"true"パラメータは、アニメーションをくり返す指示になる。これが、走りの基本周期で、これを反対側でも同じように定義している。
あと、物理的なパラメータも、すこし設定してある。
Note: Phaserは、スプライトの反転機能をサポートして、アニメーションのコマを節約できるけれど、ここでは基本に忠実にやっている。
Phaserは、いくつかの異なる物理演算エンジンをサポートしている。「Arcade Physics」と「Ninja Physics」「P2.JS Full-Body Physics」の3種類だ。このチュートリアルでは、モバイルに適した、シンプルで軽量な「Arcade Physics」エンジンを取り上げている。物理エンジンを使うには、コード上でそれを走らせ、さらに、すべてのスプライトとグループで物理エンジンを適用しなければならない。
一度、スプライトに、ArcadePhysics.Body.インスタンスのbodyプロパティを割り当てる。すると、gravityなどbodyオブジェクトが持つ多くの利用できるようになる。書くのは、こんなふうに簡単だ。
player.body.gravity.y = 300;
これは、テキトーな例だけど、それなりに動作する。設定値が大きすぎるので、プレイヤーのキャラは、重すぎて、すとんと地面の下まで落ちて画面の下に張り付いてしまう(part5.htmlで確認できる)。
さて、どうしてこんなふうになってしまうかと言うと、まだ、グループとプレーヤーの間に衝突判定を設定してないからだ。すでに、地面と張り出しは固定しておいた。まだやってないのは、プレイヤーが何かと衝突したとき、ちゃんと停止させることだ。
地面スプライトは、もともと動く物体で(ダイナミックボディとも言う)、プレイヤーがぶつかると、衝突の反動は地面に伝わる。この場合、2つの物体は、互いに反対方向に動いて、dudeははねかえり地面は落ち始めるはずだ。だけど、地面が固定してあるので、プレイヤーだけがジャンプすることになる。
さて、ようやく衝突判定について説明できる。
update関数に、次のようにプレイヤーの衝突判定を設定しよう。
function update() { // プレイヤーとplatformグループに衝突判定を追加 game.physics.arcade.collide(player, platforms); }
実際のコードは、part6.htmlを参照。表示するとこんな感じ。
update関数は、フレームごとに呼び出されるゲームのくり返し処理部分だ。Physics.collide関数は、このゲームプログラムに魔法をかけてくれる。対象となる2つのオブジェクトをテストして、それぞれのオブジェクトにどのように振舞うかを伝える。
この例では、プレイヤースプライトとplatformグループが対象オブジェクトになっている。グループメンバーの衝突判定をする場合は、地面と張り出しについて、それぞれテストしてくれる。
やっと地面に立った。
衝突判定は、これでOK。だけど、まだプレイヤーが動かない。ここで、たぶんドキュメントのイベントリスナーのところを調べなくちゃと思ったんじゃないかな。でも大丈夫。Phaserには、キーボードマネージャーが組み込んであり、こんなちょっとした関数で利用できる。
cursors = game.input.keyboard.createCursorKeys();
この関数には、上(up)、下(down)、左(left)、右(right)という4つのプロパティを持ったcursorsオブジェクトが仕込んであって、Phaser.Key:http://docs.phaser.io/Phaser.Key.htmlオブジェクトのインスタンスになっている。
あとは、update関数の中に、こんな感じのコードを書いてやればいい。
// プレイヤーの移動速度をリセット player.body.velocity.x = 0; if (cursors.left.isDown) { // 左へ移動 player.body.velocity.x = -150; player.animations.play('left'); } else if (cursors.right.isDown) { // 右へ移動 player.body.velocity.x = 150; player.animations.play('right'); } else { // そのまま player.animations.stop(); player.frame = 4; } // 上矢印キーがおされて、かつプレイヤーが地面についていたらジャンプ if (cursors.up.isDown && player.body.touching.down) { player.body.velocity.y = -350; }
実際のコードは、part7.htmlを参照。表示すると、こんな感じ。
ついに、キーボードで操作できた!
ここでは、コードを多めに追加したので、ちょっと解説してみよう。
まず最初に、水平方向の速度(移動量)をリセットしている。それから、どの矢印キーが押されているかチェックする。それで、もし左矢印キーが押されているなら、水平方向の移動量をマイナスにして、左向きのアニメーションを開始する。もしも、右矢印キーが押されているなら、水平方向の移動量をプラスにして、左向きのアニメーションを開始する。
ここでは、フレームごとに、速度をクリアしてから移動方向を設定する"ストップ-スタート"スタイルを採用している。そのおかげで、プレイヤーのスプライトは、矢印キーを押しているときには移動し、離すと確実に停止する。Phaserは、慣性とか加速とか、もっと複雑な動きを与えることができるけど、このサンプルでは、こんな感じにしておこう。
最後のコードでは、何もキーが押されていないとき、プレイヤーも4番目のフレームを表示させている。4番目のフレームでは、dudeはただ突っ立っているだけだ。
コードの最後のパートは、ジャンプ機能を追加している。上矢印キーがジャンプボタンになっているので、コードでは、このキーが押されているかをチェックしている。しかし、それだけではなく、プレイヤーが地面に付いているか、空中にいるかもチェックしている。そして、プレイヤーが地面についていて、かつ、上矢印キーが押されている場合、上方向に350px/秒 コマの速度を与える。
ジャンプしたプレイヤーは、自動的に落ちていき、platoformグループと衝突すると停止する。
キーを押して、プレイヤーをいろいろコントロールして、game worldを探検してみよう。part7.htmlのソースコードの値を調整して、どんな動きになるか、試してみると良いだろう。
では、ゲームに目的に付け加えよう。ゲーム画面に星をまきちらして、プレイヤーはそれを集めて回る。
そこで、"stars"グループを足して、設置する。このゲームのcreate関数に、次のコードを追加する。
stars = game.add.group(); stars.enableBody = true; // 12個の星を等間隔に配置する for (var i = 0; i < 12; i++) { // 'stars'グループの中に、星を生成する var star = stars.create(i * 70, 0, 'star'); // 星に重力を設定する star.body.gravity.y = 6; // 星には、ランダムなはねかえり具合を設定する star.body.bounce.y = 0.7 + Math.random() * 0.2; }
実際のコードは、チュートリアルのpart8.htmlを参照。これで、星が追加された。
星を作る手順は、platformグループを作ったときとほぼ同じだ。さらに、Javascriptの"for"ループで12個の星を作っている。"i * 70"と記述して、横70ピクセルずつ等間隔に配置する。
それから、プレイヤーに重力を与えたのと同じように、星にも与えるので、ゲームがスタートすると上から落ちてくるようになる。さらに、はねかえり(bounce)を設定して、platformとぶつかったとき、ちょっとジャンプする。このはねかえり(bounce)には、0(バウンドしない)から1(ずっとバウンドする)まで設定できる。このゲームでは、0.7から0.9までの適当な値を設定している。
だけど、衝突判定がないと、星はplatoformを通り抜けてしまう。だから、update関数の中に、次のように星とplatformの衝突判定を記述する。
game.physics.arcade.collide(stars, platforms);
もちろん、次のように、プレイヤーと星がぶつかった時の処理も必要だ。
game.physics.arcade.overlap(player, stars, collectStar, null, this);
このコードでは、プレイヤーと星のどれかが重なったら、次のcollectStar関数を呼び出す。
function collectStar (player, star) { // 画面から星を消す star.kill(); }
画面から星を消すには、killメソッドを呼び出すだけ。
これで、このゲームを実行すると、プレイヤーが走ったり、ジャンプでplatformに飛び乗ったりして、星を集めることができる。もしもアイデアがあれば、そのコードを追加してみるのも、悪くないかもね。
さあ最後に、得点を表示するよう改良してみよう。そのためには、Phaser.textオブジェクトを使う。そのために、次の二つの変数を用意する。ひとつは、現在の得点を保持するもの。もうひとつは、テキストオブジェクト自体だ。
var score = 0; var scoreText;
create関数のところで、このscoreTextオブジェクトを生成する。
scoreText = game.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
16x16は、テキストを表示する位置。'score: 0'は、表示する文字列の初期設定。そのあと、フォントサイズと文字色をオブジェクトとして記述する。フォントの種類は指定していなくて、ブラウザのデフォルトを使う。Windowsなら、Arialになるはず。
次に、collectStar関数を改良して、プレイヤーが星を集めたとき、得点がカウントアップするようにする。
function collectStar (player, star) { // 星を画面から消す star.kill(); // 得点をアップして、再度表示 score += 10; scoreText.text = 'Score: ' + score; }
星を集めるたびに、10ポイントを追加して、scoreTextを再表示する。
実際のコードは、チュートリアルのpart9.htmlを参照。これで、完成!
完成したコードは、チュートリアルのpart9.htmlを参照。
ここでも、試すことができる。
http://www.catch.jp/program/phaser/001/making_your_first_Phaser_game.html
されこれで、Phaserを使った簡単なゲームの作り方を理解した。物理演算を備えたスプライトの生成、プレイヤーの動かし方、ほかの物体と相互作用する方法などだ。
もちろん、もっといろいろな機能を付け加えることもできるだろう。たとえば、ゲームのクリア画面はまだないし、敵キャラもいない。敵キャラを作るには、"spikes"グループを作って、プレイヤーと衝突判定すればいい。
攻撃的なゲームの代わりに、一定時間内にどれだけ星を集めるか競うゲームにしてもいいね。assetsフォルダに、ほんの少しグラフィック素材を入れてあるので、アイデアをふくらませて見て欲しい。
あと250個以上あるコードサンプルも、役に立つはず。
疑問点があるなら、Phaserフォーラムhttp://www.html5gamedevs.com/で聞いてみてね。
このチュートリアルのゲームを、独自に改良して一段とゲームぽくしてみた。
改良点は、こんな感じ。