Open11

Phaser + Viteメモ

idomshiidomshi

プロジェクトの作成:

npm create vite@latest phaser-game -- --template vanilla-ts
npm install phaser --save

assetsをインライン化(Data URL化)させないようにする:

vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
    build: {
        assetsInlineLimit: 0,
    }
})
idomshiidomshi

ノベルゲームを作ることになったので急遽Phaserを勉強している。8月いっぱいくらいでPhaser 3の概要がつかめればいいと思う。

idomshiidomshi

canvas 要素をウィンドウに合わせる方法

config.scale.modePhaser.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; } も合わせて指定しないとスクロールバーが出てしまう。

idomshiidomshi

ユーザーからのインプットイベントの管理

キー入力とマウス入力を一括で処理する専用のSceneを作ると良さそう。

InputManager.ts
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");
    }
  }
}

参考

idomshiidomshi

キー入力とマウス入力を補足するのはSceneクラスにくくらないほうが使い勝手がいいのかも。
もうちょっと汎用的に使えるようにするにしても。

useInput.ts
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,
  }
}
idomshiidomshi

画面全体をフェードイン/アウトさせるには 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 の引数のコールバック関数が呼ばれるタイミングが思ってたのと違うみたい。

idomshiidomshi

イベントリスナの登録

SceneGameObjects などにはそれぞれイベントが用意されている。独自のイベントも発火させられる。

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();

completestop が同時に発火することはなさそう。

on(event, fn, context) と書くと、fn が呼び出されるときに context が渡されるはず。なのだけど、this (無指定の場合のデフォルト)が渡ってきた。fn をアロー関数で書いたせいかも?

参考にしたサイト

idomshiidomshi

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ファイルを直接起動することもできた
idomshiidomshi

背景・立ち絵・テキストなどの種類ごとに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に渡した配列の順番(後ろの方が上に重なる)になるらしい。