Spine アニメーションを Web ページで表示してみる
Spine とは
2D ゲームに特化したアニメーション制作ツールです。
Spine ギャラリーにもある通り、様々なゲームで利用されています。
公式サイトはコチラ
Spine の利用例として、特に有名なのはアイドルマスター シャイニーカラーズでしょうか。
コンピュータ・エンターテインメント・デベロッパーズ・カンファレンス 2018 にて、Spine を採用した件に触れていて、話題になっていましたね。
Spine ランタイム
Spine 公式が様々なプラットフォーム向けにランタイムを提供しています。
> Spine ランタイム
その中で、HTML5 WebGL をはじめとして Web 用のランタイムも用意されています。
今回は HTML5 WebGL – JavaScript/TypeScript を利用していきます。
余談ですが、公式ランタイムの他にサードパーティ製ランタイムもあります。
Web 系で言えば、Three.js や PixiJS 向けランタイムが有志によって提供されています。
公式汎用ランタイムを一から導入するよりも、サードパーティ製ランタイムを使った方が早く構築できる場合もあるため、プロジェクトに合わせて検討してみると良いでしょう。
サンプルページとリポジトリ
さっそくですが、下記が Spine アニメーション実装のサンプルページとリポジトリになります。
> Spine web-gl サンプルページ
> Spine web-gl リポジトリ
サンプルページの画面スクショはこんな感じです。
サンプルページを表示するとオバケ 👻(誰がなんと言おうとオバケです)がクネクネと動いていると思います。
Spine web-gl ランタイムを利用して、Spine エクスポートデータを読み込み、Canvas 上でアニメーションをループ再生しています。
アニメーションは【Spine】ゲーム制作におけるアニメーション基礎を参考に製作しました。
Spine の使い方をとても丁寧に解説されており、本当に良きチュートリアル動画です。
ソースコード周りの解説
Spine 自体の使い方や、データのエクスポート方法等は、公式サイトを初めとして数々の解説記事がありますので、そちらに譲ります。
本記事では Spine アニメーションを Web ページ上で動かすための開発環境、構築方法、ソースコード周りを書き留めていきます。
実装の流れは以下の通りです
- 開発環境の用意
- @esotericsoftware/spine-webgl をインストール
- Spine エクスポートデータを開発環境へ格納
- Spine アニメーション実行のソースコード実装
開発環境の用意
まずは開発環境ですが TypeScript x 何かしらの JS バンドルツールがあれば OK です。
今回構築した Spine web-gl リポジトリ は、ベースが私個人のフロントエンド開発スターターキットでして、Pug x Scss x TypeScript x Webpack の構成になっています。
単純に私がいつも使っている汎用スターターキットなだけで、Pug や Scss が入っている事に深い意味はありません。TypeScript と Webpack があれば十分です。
TypeScript のコンパイルとバンドルができれば、rollup でも vite でも良いと思います。
ちなみに、今回の開発環境の概要は下記の通りです。
バージョン | |
---|---|
Node.js | v16.14.0 |
yarn | 1.22.17 |
typescript | 4.4.2 |
webpack | 5.51.1 |
@esotericsoftware/spine-webgl をインストール
開発環境が用意できましたら @esotericsoftware/spine-webgl
のインストールしていきます。
Web 系のランタイムは、2021 年 9 月に npm レジストリにて公開されたため、npm
や yarn
でインストールできます。
以下のコマンドを実行して、@esotericsoftware/spine-webgl
をインストールします。
yarn add @esotericsoftware/spine-webgl
もしくは
npm install @esotericsoftware/spine-webgl
Spine ランタイムのバージョンについて
Spine ランタイムアーキテクチャ にて言及されている通り、Spine はエディタとランタイムのバージョンを同期させるよう推奨しています。
もし、Spine アニメーションが正しく再生されない場合は、Spine エディタのバージョン、エクスポート時のバージョン、Spine ランタイムのバージョンを確認しましょう。
今回の Spine 関連のバージョンは、以下の通りです。
バージョン | |
---|---|
Spine エディタ | 4.1.06 |
Spine エクスポートデータ | 4.1 |
Spine ランタイム (@esotericsoftware/spine-webgl) |
4.1.19 |
Spine エクスポートデータを開発環境へ格納
今回は下記スクショの形式で Spine データをエクスポートしました。
「パック設定」はデフォルトのママです。
よりエクスポートデータのサイズを減らしたい場合は json ではなく、バイナリを選択すると良いです。
Spine データをエクスポートすると .atlas
、.json
、.png
の 3 点が出力されます。
この 3 点セットは、後ほど Ajax で取得することになりますので、開発環境の静的ファイル格納先にまとめておきましょう。
3 点のうち、いずれか 1 つでも欠けてしまうと Spine アニメーションが正しく表示されませんので、ご注意ください。
Spine アニメーション実行のソースコード実装
今回実装したソースコードは、Spine web-gl 公式サンプル | mix-and-match を参考にしています。
(…というか、公式サンプルを TypeScript に書き直して、少し手直ししただけです。)
では、肝心のソースコードを見ていきます。
主要な処理は下記の 2 ファイルに記述しています。
spineApp.ts
/src/ts/modules/spineApp.ts
では SpineCanvasApp
インターフェースを利用して、SpineApp
クラスを定義してます。
この SpineApp
クラスが、今回のソースコードの最も重要な点です。
SpineApp
クラスのインスタンス(以降、Spine アプリとします)が、アニメーション描画の核のような存在でして、Spine エクスポートデータのロード、状態の更新、描画等を担います。
Spine アプリにはライフサイクルが存在し、発火順に loadAssets
→ initialize
→ update
→ render
となっています。
詳細は以下の通りです。
-
loadAssets
- 一番初めに発火します。
assetManager を介して、Spine エクスポートデータを読み込みます。
- 一番初めに発火します。
-
initialize
- すべての Spine エクスポートデータが読み込まれた時点で発火します。
主に読み込んだデータの初期設定を行います。
- すべての Spine エクスポートデータが読み込まれた時点で発火します。
-
update
- 定期的な画面更新時(
requestAnimationFrame
)に発火します。
画面描画以外の処理(Spine アプリの状態更新など…)を行います。
画面描画処理は、下記のrender
が担います。
- 定期的な画面更新時(
-
render
- 定期的な画面更新時(
requestAnimationFrame
)に発火します。
画面描画処理を行います。
- 定期的な画面更新時(
-
error
- Spine エクスポートデータが正しく読み込まれなかった場合にのみ発火します。
SpineApp
クラスには、それぞれのライフライクルにあった処理を記述していけば OK です。
下記がソースコード全文です。
// spine
import * as spine from '@esotericsoftware/spine-webgl'
export class SpineApp implements spine.SpineCanvasApp {
private skeleton: unknown // type: spine.Skeleton
private state: unknown // type: spine.AnimationState
loadAssets = (canvas: spine.SpineCanvas) => {
// atlas ファイルをロード
canvas.assetManager.loadTextureAtlas('model.atlas')
// skeleton(json 形式) をロード
canvas.assetManager.loadJson('model.json')
}
initialize = (canvas: spine.SpineCanvas) => {
// spine のアセットマネージャーを取得
const assetManager = canvas.assetManager
// テクスチャアトラスを生成
const atlas = canvas.assetManager.require('model.atlas')
// AtlasAttachmentLoader(リージョン、メッシュ、バウンディングボックス、パスのアタッチメントを解決するための要素)のインスタンスを生成
const atlasLoader = new spine.AtlasAttachmentLoader(atlas)
// skeleton(json 形式) を読み込むためのオブジェクトを生成
const skeltonJson = new spine.SkeletonJson(atlasLoader)
// skeleton 情報を読み込み
const skeltonData = skeltonJson.readSkeletonData(
assetManager.require('model.json')
)
// skeleton インスタンスを生成して、メンバにセット
this.skeleton = new spine.Skeleton(skeltonData)
if (this.skeleton instanceof spine.Skeleton) {
// skeleton の位置を画面中央にセット
this.skeleton.x = 0
this.skeleton.y = (-1 * Math.floor(this.skeleton.data.height)) / 2
// skeleton の大きさを等倍にセット
this.skeleton.scaleX = 1
this.skeleton.scaleY = 1
}
// skeleton 情報からアニメーション情報を取得
const stateData = new spine.AnimationStateData(skeltonData)
// アニメーションをセット
this.state = new spine.AnimationState(stateData)
if (this.state instanceof spine.AnimationState) {
this.state.setAnimation(0, 'animation', true)
}
}
update = (canvas: spine.SpineCanvas, delta: number) => {
if (!(this.skeleton instanceof spine.Skeleton)) return
if (!(this.state instanceof spine.AnimationState)) return
// アニメーションを更新
this.state.update(delta)
this.state.apply(this.skeleton)
this.skeleton.updateWorldTransform()
}
render = (canvas: spine.SpineCanvas) => {
if (!(this.skeleton instanceof spine.Skeleton)) return
// レンダラー取得
const renderer = canvas.renderer
// 画面リサイズ(ブラウザサイズが変更された時の対応)
renderer.resize(spine.ResizeMode.Expand)
// 画面クリア
canvas.clear(0.2, 0.2, 0.2, 1)
// 描画開始
renderer.begin()
// skeleton を描画
renderer.drawSkeleton(this.skeleton)
// 描画終了
renderer.end()
}
error = (canvas: spine.SpineCanvas) => {
// エラーがあれば、以降が発火する
console.log('error!!')
console.log(canvas)
}
}
main.ts
/src/ts/main.ts
では SpineCanvas
クラスのインスタンスを生成しています
SpineCanvas
のコンストラクタの第 1 引数には canvas 要素を。 第 2 引数には先程の Spine アプリを入れます。
コードを見て察しがつくと思いますが、SpineCanvas
クラスの役割は、描画先の canvas 要素と Spine アプリの紐付けです。
// spine
import * as spine from '@esotericsoftware/spine-webgl'
// modules
import { SpineApp } from './modules/spineApp'
window.onload = () => {
// canvas 要素
const canvasEl = document.getElementById('canvas') as HTMLCanvasElement
// canvas 要素と SpineApp インスタンスを紐付ける
new spine.SpineCanvas(canvasEl, {
pathPrefix: 'assets/spine-data/', // Spine エクスポートデータ 3 点セットの格納先
app: new SpineApp()
})
}
たったこれだけのコードで Web ページ上で Spine アニメーションを再生できます。
意外とカンタンですね。
最後に
今回はシンプルなループアニメーション表示だけでしたが、Spine デモ にもある通り、パス・コンストレイントやインバース・キネマティクス等のリッチなアニメーションも Web ページ上で再生できます。
また、JavaScript でのアニメーション制御も可能なため、単純なアニメーション再生だけでなく、他の Canvas 系の JS ライブラリと組み合わせる事も可能です。
キャンペーンサイトやプロモーションサイトに組み込むと、中々面白そうな演出が作れそうですね。
私もクリエイティブコーディング x Spine アニメーションで、何か作品を作ってみたいと画策しているところです。
また、Spine の知見が貯まりましたら、記事にしてみたいと思います。
Discussion