Phaser.js
まさか返り咲くことになるとは…
しかし全然覚えていないので一から勉強
Typescriptで書く
参考
まずはこれに従う
↑はwebpackやPhaserのバージョンとかが古いのでこっちの方を見ながら直していく
Phaser3は2と全然違う
ブロック崩し
easing
迷路ゲーム
アニメーション処理
ライト
等等
導入
プロジェクトの作成とパッケージのインストール
code: bash
$ mkdir phaser-test
$ cd phaser-test
$ yarn init
$ yarn add -D webpack webpack-cli webpack-dev-server
$ yarn add -D typescript tslint awesome-typescript-loader
$ yarn add -D phaser
パッケージの説明
typescript: typescriptのコンパイラ(トランスパイラ)
tslint: typescript用のコードチェックツール.vscodeから使える.
package.json
yarn initで作られたpackage.jsonにビルドとサーバ起動用のスクリプトを追記
code: package.json
...
"scripts": {
"build": "webpack -p --progress --colors",
"start": "webpack-dev-server"
}
...
tsconfig.json
typescriptのコンパイル設定を記載(awesome-typescript-loader用)
code: tsconfig.json
{
"compilerOptions": {
"outDir": "dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5"
},
"include": [
"./src/**/*"
]
}
compilerOptions: この中のプロパティはデフォルト値が定められており,省略も可能
outDir: 出力先としてコンパイル対象外にするディレクトリ
sourceMap: ソースマップを含めてコンパイル
commonjsはおそらく記述量少なめ,Nodejsなどサーバサイド向け
include: 読み込むtypescriptファイルが入ったフォルダのことかと
** はサブディレクトリを再帰的に探索,*は0文字以上の文字,つまり`.src/"以下全て
webpack.config.js
code: webpack.config.js
const path = require("path");
module.exports = {
devtool: "inline-source-map",
entry: "./src/app.ts",
resolve: {
},
module: {
rules: [
{
test: /\.ts$/,
use: "awesome-typescript-loader",
}
]
},
output: {
filename: "app.js",
path: path.resolve(__dirname, "dist"),
},
devServer: {
open: true,
hot: true,
contentBase: path.join(__dirname, 'dist'),
watchContentBase: true,
}
}
(source-map-loaderはwebpackのinline-source-map機能で問題ないので削除)
vscode
TypeScript Hero: コード中に存在しないクラスのimportを自動で追加するなど
なお,option+Shift+Oで使用していないimportを削除してくれる
Getting Started
まずはdistディレクトリをトップに作成し,その中に以下を作成
code: index.html
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Phaser Test</title>
<script src="app.js"></script>
</head>
<body>
<div id="app"></div>
<!-- <script></script>で読み込むこともできる -->
</body>
</html>
続いて,srcディレクトリを作成し,その中に以下を作成
code: app.ts
// Typescriptの型定義ファイルを読み込み,Intellisenceを有効化
/// <reference path="../node_modules/phaser/types/phaser.d.ts"/>
import "phaser"
const config: Phaser.Types.Core.GameConfig = {
title: "Phaser Test",
width: 800,
height: 600,
parent: "app", // ゲームを埋め込むDOMのidを指定.なくても<script>に読み込む
backgroundColor: "#18216D"
}
export class PhaserTestGame extends Phaser.Game {
constructor(config: Phaser.Types.Core.GameConfig) {
super(config)
}
}
window.onload = () => {
var game = new PhaserTestGame(config);
}
yarn startでブラウザが開き,青い画面がゲーム画面として出力されることを確認
シーンの作成
ゲームのプレイ画面とかショッピング画面とか.
以下のmainScene.tsをsrcに作成
code: mainScene.ts
import "phaser"
export class MainScene extends Phaser.Scene {
constructor() {
super({
key: "MainScene" // scene.start(key)で使うkeyを指定
});
}
init(params: any) {
// シーンが開始される時に呼ばれる
// scene.start(key, params)のparamsが渡される
}
preload() {
// シーンオブジェクトが作成される前に呼ばれる
// アセットのローディングに使う
// アセットはキャッシュされるため,シーンが再開される時にはロードされない
}
create() {
// アセットがロードされた後に呼ばれる
// ゲームのオブジェクト(プレイヤー,障害物,敵など)を生成するのに使う
}
update(time: number) {
// フレーム毎に呼ばれる.大体60fpsで動く.(16.666...ms / frame)
// シーンの動的な処理に用いる
// timeはゲームを起動してから経過したフレーム数
}
}
続いて,app.tsを書き換え,MainSceneを読み込めるようにする
config変数に以下を追加
code: app.ts
...
scene: MainScene, // ゲームで使うシーンを指定.複数ある時は0をゲーム開始時に読込 ...
星取りゲームの作成
以下では,上から落ちてくる星をクリックでキャッチするゲームを作成する.
背景の作成
まずはapp.tsのconfig変数で物理エンジンを指定する
code: app.ts
...
physics: { // オブジェクトの落下などを再現する物理エンジンを指定
default: "arcade",
arcade: { // 物理エンジンのオプションを指定
debug: false
}
},
...
続いて,星取りゲームのMainSceneに必要な変数を設定&初期化
code: mainScene.ts
...
export class MainScene extends Phaser.Scene {
delta: number;
lastStarTime: number;
starsCaught: number;
startFallen: number;
sand: Phaser.Physics.Arcade.StaticGroup;
info: Phaser.GameObjects.Text;
...
init(params: any) {
this.delta = 1000;
this.lastStarTime = 0;
this.starsCaught = 0;
this.startFallen = 0;
}
...
画像アセットの取得
code: mainScene.ts
...
preload() {
this.load.setBaseURL(
"starfall-phaser3-typescript/master/");
this.load.image("star", "assets/star.png");
this.load.image("sand", "assets/sand.jpg");
}
...
静的オブジェクト(床や壁)をセット
code: mainScene.ts
create() {
// 静的オブジェクトのグループを作成
this.sand = this.physics.add.staticGroup({
key: 'sand', // グループに使うimageのキー
frameQuantity: 20 // グループに含まれるフレーム(オブジェクト?)の数
})
// 地面のオブジェクトを一列に並べる
Phaser.Actions.PlaceOnLine(
// 並べたいオブジェクト.sandから取ってくる
this.sand.getChildren(),
// y座標を同じにすることで横一列に並べることになる
// 40*40の画像を並べて,Lineの左端は画像の中央に位置するので20ズラす
new Phaser.Geom.Line(20, 580, 820, 580) // x1, y1, x2, y2
);
// グループをリフレッシュしなければ,衝突判定が発生してしまう.
this.sand.refresh();
// 左上にスコアなどを表示するテキストを設置
this.info = this.add.text(
10, 10, "Text for Score",
{font: '24px Ariel Bold', fill: '#FBFBAC' }
)
}
以上で背景が表示される
https://gyazo.com/b1bc38fb513f8cf760b3035c83bd317a
動的オブジェクトの操作
続いて,動的オブジェクトのコントロールをすべくupdate関数を実装する
code: mainScene.ts
update(time: number) {
// 最後に星を出してから経過した時間を取得
const diff: number = time - this.lastStarTime;
// 星を出してからdelta時間だけ経過したら星を射出
if(diff > this.delta) {
// 最後に星を出した時間を更新
this.lastStarTime = time;
// deltaを縮めることで,段々星が出てくるまでの時間を短くする
if (this.delta > 500) {
this.delta -= 20;
}
// 星を射出(後に実装)
this.emitStar();
}
// スコア(クリックできた星と落ちてしまった星の数)を表示
this.info.text = ${this.starsCaught} caught - +
${this.starsFallen} fallen (max 3);
}
星を射出するための関数emitStarをmainSceneクラスの中に作成
code: mainScene.ts
private emitStar() {
// starが画面外に出ないx=25~775の間,上から26の位置に星を生成
const x = Phaser.Math.Between(25, 775);
const y = 26;
const star = this.physics.add.image(x, y, "star");
star.setDisplaySize(50, 50); // 生成した星のサイズを指定
star.setVelocity(0, 200); // y軸下方向へ毎秒200pxで移動させる
star.setInteractive(); // クリックなどのInputを受け付けるようになる
// pointerdown(クリック)が発生したとき,onClick関数をこのstarについて呼び出す.+context
star.on('pointerdown', this.onClick(star), this);
// star(object1)とsandグループ(object2)が衝突したとき,onFallを呼び出す +context
this.physics.add.collider(star, this.sand, this.onFall(star), null, this);
}
生成されるオブジェクトが多い時にはグループを使うべきだが,星はすぐ消えるので個別に生成
星は重力で落とすのではなく,移動によって落とす.
重力を使いたい場合はsetMassとsetGravityを使う
colliderにはcollideCallbackとprocessCallbackを指定できるが,processCallbackの方が先に呼ばれる
processCallbackは衝突判定を複雑にするときに使われる?
そして,starに対するonClickとonFallを実装する
code: mainScene.ts
// コールバック関数を返す関数
private onClick(star: Phaser.Physics.Arcade.Image): () => void {
return () => {
star.setTint(0x00ff00); // クリックされたら緑色にする
star.setVelocity(0, 0); // クリックされたら速度を0にする
this.starsCaught += 1; // クリックできた星の数を増やす
// 100ms後にstarをコールバックで削除.コールバックに渡すものとthisの参照先も渡す
this.time.delayedCall(100, (star: Phaser.Physics.Arcade.Image) => {
star.destroy();
}
}
private onFall(star: Phaser.Physics.Arcade.Image): () => void {
return () => {
star.setTint(0xff0000); // 落ちてしまったら赤にする
this.starsFallen += 1; // 落ちてしまった星の数を増やす
// 100ms後にstarをコールバックで削除
this.time.delayedCall(100, (star: Phaser.Physics.Arcade.Image) => {
star.destroy();
}
}
以上を実装することで,終了判定以外の星取りゲームができる
https://gyazo.com/1e9b735a04b436b0c8ed1d44b98d5080
開始シーンと終了シーン
複数のシーンを扱い,開始シーンと終了シーンを作成する
開始シーンとして以下のwelcomeScene.tsを作成する
code:welcomeScene.ts
import "phaser";
export class WelcomeScene extends Phaser.Scene {
title: Phaser.GameObjects.Text;
hint: Phaser.GameObjects.Text;
enter: Phaser.Input.Keyboard.Key;
constructor() {
super({
key: "WelcomeScene"
});
}
create() {
const titleText: string = "Starfall";
this.title = this.add.text(
150, 200, titleText,
{ font: '128px Arial Bold', fill: '#FBFBAC'}
);
const hintText: string = "Enter to start";
this.hint = this.add.text(
300, 350, hintText,
{ font: "24px Arial Bold", fill: '#FBFBAC' }
);
this.enter = this.input.keyboard.addKey("ENTER");
}
update(time: number) {
// キーボードによる入力の例
if (this.enter.isDown) {
this.scene.start("MainScene");
}
}
}
ENTERキーを押したらスタートする.
次に,終了シーンとしてscoreScene.tsを作成する
code: scoreScene.ts
import "phaser";
export class ScoreScene extends Phaser.Scene {
score: number;
result: Phaser.GameObjects.Text;
hint: Phaser.GameObjects.Text;
constructor() {
super({
key: "ScoreScene"
});
}
init(params: any) {
// シーンからのパラメータの受け取り方の例
}
create() {
const resultText: string = Your score is ${this.score} !;
this.result = this.add.text(
200, 250, resultText,
{ font: "48px Arial Bold", fill: "#FBFBAC" }
);
const hintText: string = "Enter to restart";
this.result = this.add.text(
300, 350, hintText,
{ font: "24px Arial Bold", fill: "#FBFBAC" }
);
// キーダウンはこういう書き方もできる.-
// -ENTERの部分をなくせば任意キー入力に反応する
this.input.keyboard.on('keydown-ENTER', () => {
this.scene.start("WelcomeScene");
});
}
}
最後に,これらのシーンをmainSceneと組み合わせる.
code: app.ts
...
...
code: mainScene.ts
...
this.time.delayedCall(100, (star: Phaser.Physics.Arcade.Image) => {
star.destroy();
// 3つ星を落としたら終了
if (this.starsFallen > 2) {
const params = {starsCaught: this.starsCaught};
// シーンへのパラメータの渡し方の例
this.scene.start("ScoreScene", params);
}
...
以上を実装することで,開始画面と終了画面を持つ星取りゲームを実装することができた.
FPSの表示
Frame Per Secondsのこと.音ゲーの処理とかで使う
次のfps.tsを作成
code: fps.ts
export class FPS {
private _fps: number = 0;
private frames: number = 0;
private timer: NodeJS.Timeout;
public start(): FPS {
this.frames = 0;
this._fps = 0;
// 1000msごとに繰り返しリセット
this.timer = setInterval(() => {
this._fps = this.frames;
this.frames = 0;
}, 1000);
return this;
}
public stop() {
if (this.timer) {
clearTimeout(this.timer);
}
}
public update() {
this.frames++;
}
get fps(): number {
return this._fps
}
}
mainScene.tsにfps.tsを組み込んでいく
code: mainScene.ts
...
fpsInfo: Phaser.GameObjects.Text;
fps: FPS = new FPS();
...
create() {
...
this.fpsInfo = this.add.text(
{font: '24px Ariel Bold', fill: '#FBFBAC' }
)
// シーンのライフサイクルイベントを扱う例
this.events.on("shutdown", () => this.fps.stop());
this.fps.start();
}
...
update(time: number) {
....
this.fps.update();
this.fpsInfo.text = ${this.fps.fps} [fps]
}
...
以上を実装することで右上に1秒ごとにFPSが表示される
https://gyazo.com/4f7734b29306ed546e83bcdf45ce8b3c