はじめに
前回の記事でちょっと紹介したサンプルを読みつつ、実際にPhaser使ってシューティングゲームを作りたいと思います。
なおこの記事は、JavaでもHSPでもBasicでも何でもいいので、シューティングゲームを作った事がある方向けです。
あくまでPhaserというライブラリについてのお話です。
Phaserの話
ゲームオブジェクトを作る時の話
Phaserでゲームオブジェクトを作成するコードは、サンプルから引っ張ってくるとこんな感じです。
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
$('#phaser-example')で拾える要素に、800x600のcanvasを叩き込みゲームで利用する、というコードです。
最後に渡しているオブジェクトは、PhaserではStateと呼ばれており、ゲームの状態を表す単位です。
ゲーム開発等ではよく、シーンと呼ばれている気がします。
RPG的に言うとタイトル画面で1シーン、メニュー画面で1シーン、フィールド移動で1シーン、戦闘で1シーンです。
Stateの話
Stateは大まかに言えば、ゲーム本体から呼ばれるいくつかのメソッドを実装したオブジェクトです。とりわけ重要なメソッドを以下に出します。
- init() : Stateがstartされた直後に呼ばれる。
- preload() : init()後呼ばれる。通常ここでリソースの読み込みを指定する。
- create() : preload()で指定したリソースの読み込みが全部終わったあと呼ばれる
- update() : 毎フレーム呼ばれる、いわゆるメインループ
- render() : 全てのゲームオブジェクトが描画された後に呼ばれる。追加で独自に何か出したい場合、ここに書く
最低限、4まであれば動きますが、debugメッセージをcanvas上に出す場合は、render()を書いた方が楽です。詳しくは公式のドキュメントへ。
ついでなので、いくつか分かりにくかった点を挙げておきます。
- Stateのインスタンスは、gameオブジェクトが持っているStateManagerが管理する
- ゲームオブジェクト作成時に渡したStateは、managerが勝手にstartする
- つまり勝手にinitからcreateまでの流れが始まる
- 別のStateをstartする時は、現在start済みのStateがあれば、そのStateのshutDown()が呼ばれる
- start時に、startするStateのinitメソッドにパラメータを渡せる
- 読み込んだリソース(画像等)はキャッシュされ、他Stateおよびgameオブジェクト全域で利用可能
基本的には、シーンAからシーンBへの、不可逆(?)な状態遷移での利用が適していると考えます。
シーンAからBへ移動する際、AはshutDown()が呼ばれ、BからAに戻る際はまたAのinit()が呼ばれます。
つまり、Bから、Bを呼んだ直後の状態のAへ戻る、という機構はライブラリ側にはありませんので、やる場合はinit()で初期化する情報を工夫する必要があると思います。
Stateを継承して作る
参考までに、僕が作っているTitleState.tsを貼っておきます。
画像の読み込みをして、タイトル画面を出して、実際のゲーム画面に遷移する機能を持っています。
タイトル画面を出す処理は何も書いてないですが、そのうちかく予定です。
///<reference path="../../../../typings/node/node.d.ts" />
///<reference path="../../../../typings/phaser/phaser.d.ts" />
import GameState = require("./GameState");
class TitleState extends Phaser.State {
static NAME = "Title";
public preload():void {
this.load.spritesheet("char", require("../../../assets/img/char.png"), 32, 32);
this.load.spritesheet("myShot", require("../../../assets/img/shot01.png"), 16, 16);
this.load.spritesheet("explode", require("../../../assets/img/exp.png"), 32, 32);
}
public create():void {
this.game.state.start(GameState.NAME);
}
public update():void {
}
}
export=TitleState;
アニメーションの話
スプライトをアニメーションさせる方法はいくつかありますが
https://www.codeandweb.com/texturepacker
こちらで出力できる画像とjsonデータに対応しているため、作った画像は纏めてパックしてしまうのも一興。ツール側でも、Phaser用のプロジェクト作成ができます。
Spriteを拡張する時
HPやlifetimeなど、やたらプロパティが生えてるSpriteオブジェクトですが、それでも拡張したいときがあります。
サンプル等ではよく、以下のようにしてSpriteを生成しています。
var sprite = game.add.sprite(x, y, 'imageKey');
実際にはこのコードは、new Sprite()して、それをゲームのデフォルトグループ(World)へ追加する、という処理になっています。
そのため、Spriteを継承したオブジェクトを作った場合は、それを自分で行う必要があります。
var mySprite = new MySprite(....);
//追加したいグループ(今回はworld)に手動でaddする
game.world.add(mySprite);
やや冗長な感じもしますがSpriteをラップしたクラスを作ってもいいかもしれません。
画像の話
webpackでビルドするために、requireしました
this.load.spritesheet("char", require("../../../assets/img/char.png"), 32, 32);
こんな感じです。assetsフォルダの中に入っている画像のうち、実際にソースコード上でrequireされた物だけが、release用ディレクトリにコピーされるので、あれこれ考える必要がなくていいです。
ファイル名のハッシュ化も可能ですね。別にエロゲー作る訳じゃないので、ハッシュ化する意義はあまりないかもですが。
ほんとはDataURIを使ってバカでかいjsを作ってみようと思ったんですが、PhaserがDataURIからうまい事Spriteをつくってくれず、断念しました。
キー入力の話
任意のキーの参照を引っ張ってきて、state内のupdateで状態をチェックする事になります。
// 初期化時
this._keys = this.game.input.keyboard.createCursorKeys();
this._fireButton = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
// メインループ内
if (this._keys.left.isdown) {
// 左移動
} else if (this._keys.right.isdown) {
// 右移動
}
ゲームを作った事無い人には想像できないかもしれないですが、ゲームの入力は基本的にイベント駆動にはしないのです。
まとめ
- Gameオブジェクトを作る
- それにぶち込むStateオブジェクトを作る
- Stateオブジェクトの中で、画像読んだりSprite作ったりする
- メインループでキーチェックして動かす
まだ敵は出てこないですが、とりあえずコレだけで、画面を動き回る自分、は作れるはずです。
敵の動きについては、Phaserがかなりガッツリ機能を用意してくれているので、別途記事を書こうと思います。