Phaser + Viteメモ
プロジェクトの作成:
npm create vite@latest phaser-game -- --template vanilla-ts
npm install phaser --save
assetsをインライン化(Data URL化)させないようにする:
import { defineConfig } from 'vite'
export default defineConfig({
build: {
assetsInlineLimit: 0,
}
})
ノベルゲームを作ることになったので急遽Phaserを勉強している。8月いっぱいくらいでPhaser 3の概要がつかめればいいと思う。
- TypeScriptを使ってノベルゲームを作ろう - Qiita を写経させてもらってる
-
Scene
の扱いが複雑? - 全体としては
new Phaser.Game(config);
を起点に動いてるっぽい(そこから)
canvas
要素をウィンドウに合わせる方法
config.scale.mode
に Phaser.Scale.FIT
を指定する。
const config: Phaser.Types.Core.GameConfig = {
// (中略)
pixelArt: false,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_HORIZONTALLY,
}
}
new Phaser.Game(config);
pixelArt の値 |
拡大時のスムージング | 意訳 |
---|---|---|
true | 掛からない | ドット絵の雰囲気を生かすためにスムージングしないでね |
false | 掛かる | ドット絵じゃないからスムージングしてね |
このとき、body { margin: 0; }
も合わせて指定しないとスクロールバーが出てしまう。
ユーザーからのインプットイベントの管理
キー入力とマウス入力を一括で処理する専用のSceneを作ると良さそう。
export class InputManager extends Phaser.Scene {
private keyEnter!: Phaser.Input.Keyboard.Key;
constructor() {
super({ key: "inputManager", active: false });
}
create(): void {
this.keyEnter = this.input.keyboard.addKey(
Phaser.Input.Keyboard.KeyCodes.ENTER
);
const { width, height } = this.game.canvas;
const zone = this.add.zone(width / 2, height / 2, width, height);
zone.setInteractive({ useHandCursor: true });
zone.on("pointerdown", () => this.moveNextScene());
}
update(): void {
if (this.keyEnter.isDown) this.moveNextScene();
}
moveNextScene(): void {
if (this.scene.isVisible("title")) {
this.scene.start("ending");
this.scene.stop("title");
} else if (this.scene.isVisible("ending")) {
this.scene.start("title");
this.scene.stop("ending");
}
}
}
参考
EventEmitterとDataManagerの使い方をきちんと調べたほうが良さそう。
キー入力とマウス入力を補足するのはSceneクラスにくくらないほうが使い勝手がいいのかも。
もうちょっと汎用的に使えるようにするにしても。
export const useInput = (scene: Phaser.Scene) => {
const self = scene
const { width, height } = self.game.canvas;
const zone = self.add.zone(width / 2, height / 2, width, height)
zone.setInteractive({
useHandCursor: true,
})
const space = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE)
const setEventHandler = (handler: Function) => {
zone.on('pointerdown', handler, self)
space.on('down', handler, self)
}
return {
zone,
setEventHandler,
}
}
画面全体をフェードイン/アウトさせるには this.cameras.main.fadeOut(duration)
を使うらしい。しかし、フェードしながら次の処理が走ってしまう。
フェードが終わるまで待機させるのに await
を使うことにしたんだけど正しいのかはわからない(とりあえず動いてはいる)。
await new Promise((resolve) => this.cameras.main.fadeOut(500, 0, 0, 0, resolve));
ってすれば良さそうだと思ったのだけど、fadeOut → fadeInをするとfadeOutし終わるまで待たずにfadeInしてしまうのであきらめて普通のwaitを書きました。
await new Promise((resolve) => setTimeout(resolve, time));
どうやら fadeOut
の引数のコールバック関数が呼ばれるタイミングが思ってたのと違うみたい。
ゲームを通してのグローバルな状態管理に Nano Stores が使えそう。
Phaser にも状態管理の仕組みがあるのかもしれないんだけど、見つけられなかったんだ。
イベントリスナの登録
Scene
や GameObjects
などにはそれぞれイベントが用意されている。独自のイベントも発火させられる。
BaseSound の停止を検知する
Phaser.Sound.BaseSound
で音楽を流す処理で、最後まで流れ終わったときのイベントと途中での停止イベントを拾いたいとき。
this.bgm = this.game.sound.add(name, this.bgmConfig);
this.bgm.on("complete", onStop);
this.bgm.on("stop", onStop);
this.bgm.play();
complete
と stop
が同時に発火することはなさそう。
on(event, fn, context)
と書くと、fn
が呼び出されるときに context
が渡されるはず。なのだけど、this
(無指定の場合のデフォルト)が渡ってきた。fn
をアロー関数で書いたせいかも?
参考にしたサイト
-
[Phaser3]イベントに関するTips - ゲンツキ趣味ブログ
イベントリスナの扱いについてきれいにまとめてくださっている。 -
Events - Phaser 3 API Documentation (beta)
Phaserに用意されているイベントが一覧できる。
Tauriで実行形式にパッケージングする方法
Tauriの公式ガイドの Integrate into Existing Project の方法でプロジェクトにTauriを追加すると、Windows用の実行ファイルにパッケージングできる。
同ガイドのQuick Start > Viteはプロジェクトを作成するところからの手順なので、今回は使わない。
Tauriのセットアップ
npm install --save-dev @tauri-apps/cli
npm run tauri init
package.json
scripts
に"tauri": "tauri",
を追加する(tauri initするより前に)。
vite.config.tsの設定
vite.config.tsでbase
(ベースパス)を指定している場合は消す。開発中は良いけどbuildした実行ファイルではウィンドウ内が真っ白になる。Tauri内部でbaseのない/
がルートになっているんじゃなかろうか。
tauri.conf.jsonの設定
上記の設定に合わせ、build.devPath
のベースパスも修正しておくこと。npm run dev
でViteを立ち上げた時の開発用URLを指定する。
tauri.bundle.identifier
をユニークな値に変更する。
開発用コマンドなど
-
npm run tauri dev
で開発用アプリが立ち上がる- ウィンドウの右クリックメニューからDevToolsが表示できる
-
npm run tauri build
でmsi形式インストーラなどが生成される- 各種ファイルはsrc-tauriの下
- setup.exe形式のインストーラも生成される
- exeファイルを直接起動することもできた
背景・立ち絵・テキストなどの種類ごとにSceneを分けて、統括するScene(ここではSkeleton
)からlaunchしちゃうと責務を分けやすいかもしれない?
export class Skeleton extends Phaser.Scene {
constructor() {
super("skeleton");
}
create() {
this.scene.launch("background");
this.scene.launch("messagewindow");
}
}
Sceneの重なり順はPhaser.Types.Core.GameConfig
に渡した配列の順番(後ろの方が上に重なる)になるらしい。