Phaser3 + TypeScript + Spine で 2D 横スクロールゲームのサンプルを作ってみる
※本記事は個人の備忘録です。
完全な解決に至らずに妥協している点もありますので、ご了承ください。
本記事を書くにあたり、コードを書いて横スクロールで移動する画面を作る【Phaser3】を 丸パクリ めちゃめちゃ参考にさせていただきました!
Phaser3 で横スクロールゲームを作成したい時の入門として、超々々有用記事です。
Phaser3 + TypeScript + Spine で 2D 横スクロールゲームを作ってみる
表題通り、Phaser3 と Spine を組み合わせて、2D 横スクロールを作ってみたいと思い立ちました。
ある程度のカタチにはなったので、備忘録としてまとめようと思った次第です。
spine-phaser runtime released にもある通り、Phaser3 用の Spine ランタイムは公式で提供されるようになりました。
spine-phaser Runtime Documentation にドキュメントがありますので、より詳しい情報を知りたい方は、本記事よりもそちらを参照すると良いでしょう。
2D 横スクロールゲーム サンプルページ
> 2D 横スクロールゲーム サンプルページ URL
※PCで閲覧する想定で作成しています。
スマホでは正しく表示できない可能性がありますので、ご注意ください。
ページが表示後に、少し待機すると 2D 横スクロールゲームのステージが表示されます。
ステージ左端の立っている Spine Boy が操作可能プレイヤーです。
左右の矢印キーを押下すると、それぞれの方向へ移動します。また、Space キーを押下すると shoot
アニメーションが発火します。
2D横スクロールゲームのサンプル
リポジトリはコチラです。
> t-tonyo-maru/pub_web_phaser3-spine_2D-platform-game_sample
主となる TypeScript コードはコチラの 2 ファイルです。
主なパッケージのバージョン
Package | Version |
---|---|
@esotericsoftware/spine-phaser | ^4.2.34 |
phaser | ^3.70.0 |
typescript | ^5.2.2 |
vite | ^5.0.8 |
Phaser3 + TypeScript + Spine の勘所
2D 横スクロールゲームの実装方法は先達の素晴らしい解説記事がありますので、本記事では主に Phaser3 に Spine アニメーションを導入するところを解説していきます。
Phaser のシーンに Spine アニメーションを追加する
Phaser のシーンに Spine アニメーションを追加するおおまかな流れは、下記の通りです。
-
@esotericsoftware/spine-phaser
をインストールする - Phaser の
GameConfig
のplugins
にSpinePlugin
をセットする - Spine のエクスポートファイルを読み込む
-
spine
関数で Spine アニメーションをステージに追加する
@esotericsoftware/spine-phaser
をインストールする
1. まずは何がともあれ @esotericsoftware/spine-phaser
の導入ですね。
npm i @esotericsoftware/spine-phaser
を実行します。
GameConfig
の plugins
に SpinePlugin
をセットする
2. Phaser の src/main.ts にある通り、config(Phaser の GameConfig
オブジェクト) の plugins.scene
配列に SpinePlugin
を追加します。
import { SpinePlugin } from '@esotericsoftware/spine-phaser'
const config: Phaser.Types.Core.GameConfig = {
// …略…
plugins: {
scene: [
{ key: 'spine.SpinePlugin', plugin: SpinePlugin, mapping: 'spine' }
]
},
// …略…
}
3. Spine のエクスポートファイルを読み込む
次に Phaser の Scene
クラスを編集していきます。
Phaser の Scene
クラスのライフサイクルは下記の通りです。
-
preload
関数でシーンに必要な画像・音声などを読み込む -
create
関数でシーンの初期設定を行う -
update
関数でシーンの更新を行う
では、はじめに preload
関数で Spine のエクスポートファイルを読み込むようにします。
preload = () => {
// …略…
this.load.spineJson(
'spine-ghost-model',
`${ASSETS_URL}/spine/ghost/model.json`
)
this.load.spineAtlas(
'spine-ghost-atlas',
`${ASSETS_URL}/spine/ghost/model.atlas`
)
}
spine
関数で Spine アニメーションをステージに追加する
4. preload
関数にて必要な素材がすべて読み込まれると、create
関数が発火します。
この create
関数にて、読み込んだ Spine エクスポートファイルを調整した後にステージに追加します。
create = () => {
// …略…
// Spine お化け キャラクターの追加
this.spineGhost = this.add.spine(
500, // x
550, // y
'spine-ghost-model', // json
'spine-ghost-atlas' // atlas
)
this.spineGhost.scale = 0.5 // スケール
this.spineGhost.setInteractive() // インタラクション可
this.physics.add.existing(this.spineGhost) // 当たり判定を付与
if (this.spineGhost.body instanceof Phaser.Physics.Arcade.Body) {
// 弾性を調整
this.spineGhost.body.setBounce(0.2)
this.spineGhost.body.setCollideWorldBounds(true)
this.spineGhost.body.setOffset(0, 0)
}
this.input.enableDebug(this.spineGhost, 0xff00ff) // デバッグON
this.spineGhost.animationState.setAnimation(0, 'idle', true) // idle アニメーションをセット
// …略…
}
ここまで正しく実装できていれば、ステージ上に Spine アニメーションが表示されると思います。
Spine アニメーションに当たり判定を付与する
Spine アニメーションに当たり判定を付与するには、physics.add.collider
関数を使います。
Phaser の通常の当たり判定付与の処理と同様ですね。
今回のサンプルでは create
関数にて、当たり判定を付与しています。
create = () => {
// …略…
// 当たり判定を設定
this.physics.add.collider(
[this.spineBoyPlayer, this.spineGhost],
this.platforms
)
this.physics.add.collider([this.spineGhost], this.spineBoyPlayer)
// …略…
}
上記の当たり判定の処理があるおかげで、プレイヤーである Spine Boy が地面に立つことができますし、Spine アニメーション同士で衝突するようにもなっています。
Spine アニメーションにコールバック関数を設定する
Spine アニメーションの任意のタイミングに合わせて、コールバック関数を発火するには animationState
の addListener
関数を利用します。
2D 横スクロールゲーム サンプルページでは、create
関数にて、アニメーションが完了した時 (complete
) にのみコールバック関数を設定しています。
create = () => {
// …略…
// Spine アニメーションにコールバックを設定する
this.spineBoyPlayer.animationState.addListener({
start: (entry) => {},
end: (entry) => {},
interrupt: (entry) => {},
dispose: (entry) => {},
complete: (entry) => {
// トラック:1 の shoot が再生された後に空アニメーションにする
if (entry.animation?.name === 'shoot') {
this.spineBoyPlayer!.animationState.setEmptyAnimation(1)
}
}
event: (entry) => {}
})
// …略…
}
上記では shoot
アニメーション完了時にトラック 1 に空アニメーションをセットしています。
Spine アニメーションはループを指定しない限り、一度再生したら、再生されたきりで止まってしまいます。
shoot
アニメーションが再生されたきりの状態を解消するために、shoot
アニメーションがセットされているトラック1を空アニメーションにすることで、一度再生した shoot
アニメーションを初期化しています。
このコールバック関数のおかげで、下記のアニメーションサイクルが実現できています。
- Space キーを押下する
-
shoot
アニメーション発火 -
shoot
アニメーション完了 - トラック1を空アニメーションにして、
shoot
アニメーションを初期化する - 再び Space キーを押下すると、再度
shoot
アニメーションが発火する。以降ループする
他にも start
や end
など、様々なタイミングでコールバック関数を設定できます。
Spine アニメーションを反転させる(※妥協した点)
2D 横スクロールゲーム サンプルページでは、左右の矢印キーを押下すると、押下した方向へプレイヤーが体を向けるようになっています。
この処理は、かなり強引な手段で実現しています。
update = () => {
// 左矢印キー押下:
if (this.cursors.left.isDown) {
if (this.spineBoyPlayer.body instanceof Phaser.Physics.Arcade.Body) {
// …
}
if (
this.spineBoyPlayer.animationState.getCurrent(0)?.animation?.name !==
'run'
) {
// Spine boy を反転
this.spineBoyPlayer.scaleX = // ←ココ
this.spineBoyPlayer.scaleX > 0
? this.spineBoyPlayer.scaleX * -1
: this.spineBoyPlayer.scaleX * 1
this.spineBoyPlayer.animationState.setAnimation(0, 'run', true)
}
} // …略…
}
はじめは this.spineObject.setFlipX(true)
で簡単に実現できるかと思いましたが、うまくいかず。。
ドキュメントを軽く見てみましたが、パッと解決できる案が見つからなかったため、妥協案として現在のカタチになっています。
しかしながら、この強引な反転処理のせいでプレイヤーが左を向くと当たり判定からはみ出してしまいます。
これはゲームとしては致命的ですね。。
ピンクが当たり判定です。当たり判定からプレイヤーがはみ出しています
左右の矢印キー押下時に、scaleX
を変更するのではなく、それぞれの向きのアニメーションを適用するカタチにすれば、この不具合は解消できると思います。
(左向きのアニメーションを新しく作成するのが面倒くさいので、とりあえず現在のカタチにしています。)
Spine アニメーションの黒い線を取り除く
Spine エクスポートファイルは、テクスチャパッキング設定によっては黒い線が発生する時があります。
よく見ると黒い線が口周りに入ってます
この現象をパッと解決するには、テクスチャパッキング設定の「出力」の「乗算済みアルファ」のチェックを外せば OK です。
赤枠が「乗算済みアルファ」です
「乗算済みアルファ」をチェックするかしないかは、ランタイム上でどのようにレンダリングされるかに依拠するようですので、お使いに環境に合わせるのが無難そうです。
(正直、「乗算済みアルファ」なるモノが何なのか正しく理解できていないので、時期を見て調べてみようと思います。)
まとめ
以上。
「Phaser3 + TypeScript + Spine で 2D 横スクロールゲームのサンプルを作ってみる」でした。
次回も Spine の記事を書いてみようと思います。
では、また!
Discussion