こんにちは。エンジニアの辻です。
だいぶ久々の更新ですね。前回に何を書いたのか忘れてしまいました。。
さて、気を取り直して、今回のテーマは2Dアニメーション製作ツール SpineとJavaScriptです。
この2つを使って、Webページのcanvas上に2Dアニメーションを表示してみたいと思います。
canvas上でのspineアニメーションのデモ
さて、今回の成果物ですが…、本ページ右下をご覧ください。
東北ずん子ちゃんのアニメーションが表示されているのではないでしょうか。
(IE11等の古いブラウザでは、表示されないかもしれません…。ごめんなさい。)
このアニメーションは東北ずん子 公式HPにあったイラスト素材を元に、Spineでアニメーションをつけたモノです。
使った素材はコチラ↓
このアニメーションは、gifアニメでもなく、videoタグの動画でもなく、cssスプライトアニメーションでもありません。
canvas上で動いているアニメーションです!
canvas上での表示ですので、もちろん、JavaScriptで制御できます。
試しに、ずん子ちゃんをクリック(スマホでご覧の方はタップ)してみてください。アニメーションが切り替わります。
さらにもう一度クリックすると、元のアニメーションに戻ります。
このように任意のタイミングでアニメーションを制御できるので、SpineアニメーションとJavaScriptを使えば、Webページのリッチアニメーションやブラウザゲームなどの演出に大きな花を添えられます。
夢が広がりますね!
このアニメーションのサンプルコードはコチラです。
PixiJS サンプルのSpineBoyのコードを参考にコーディングしています。
Spineとは
Spineは、Esoteric Software社製の2Dアニメーション製作ソフトで、主にゲーム用2Dアニメーションに特化しています。
キャラクターの骨組みとなる“ボーン”をイラストに組み込み、ボーンを動かすことでアニメーションを製作します。
以下の動画の矢印1つ1つがボーンです。(選択中のボーンは水色で表示されます。)
動画でやっているように、ボーンを動かすとボーンに紐付いたイラストも一緒に動きます。
こうしてキーフレームごとに各ボーンを設定していき、アニメーションを製作していくわけです。
この他にも、メッシュ機能やインバースキネマティクスなど、Spineには様々な機能が備わっています。
また、Spineは公式より様々なプラットフォーム向けのランタイムが提供されています。
Spineランタイム一覧
今回のずん子ちゃんのアニメーションには、PixiJS用のサードパーティ製Spineランタイムを利用しています。
他にもthree.jsやPhaser向けのJavaScriptランタイムも揃っています。
Spine製アニメーションをWebページへ組み込む方法
Spineの使い方については、先達の良質な解説記事がありますので、そちらに任せる事にして…。
今回は、Spine製アニメーションをWebページへ組み込む方法を紹介します。
具体的には、Spineでアニメーションを製作した後にエクスポート→Spine JavaScriptランタイムを使って、canvasに組み込んで表示するところまでです。
※Spineはバージョン4.0.37を使用しています。
詳細な手順は以下になります。
- Spineアニメーションをエクスポート
- Spine JavaScriptランタイムをインストール
- Spineのエクスポートファイルを格納する
- JavaScript(TypeScript)でアニメーションの処理を記述
- 実際に表示してみる
Spineアニメーションをエクスポート
Spineでのアニメーション製作が完了しましたら、製作したアニメーションをエクスポートしましょう。
Spine編集画面左上の「Spine PRO」のロゴを押下します。
メニューが展開されますので、その中の「エクスポート」を選択します。すると、以下のようなパネルが表示されます。
上記キャプチャの通り、「データ」は「JSON」を選択してください。
「JSONエクスポート」の項目も同じようにチェックを入れてください。
チェックを入れ終えたら、パネル右下の「エクスポート」ボタンを押下します。
ボタン押下後に、.json、.png、.atlasの3つのファイルが生成されます。
これらがSpineアニメーションのエクスポートファイルです。
3つで1セットですので、削除したりせずに一つにまとめて管理しておきましょう。
Spine JavaScriptランタイムをインストール
次にSpine JavaScriptランタイムをnpm(yarn)経由でインストールしていきます。
最終的にJSをバンドルする必要がありますので、JavaScriptのバンドルツールもインストールして、セットアップしておきましょう。
プロジェクトのセットアップ例として、サンプルコードを参考にしてみてください。
上記では、webpackを利用しています。
さて、今回はPixiJS用のサードパーティ製Spineランタイムを使っていこうと思います。
プロジェクトの用意ができましたら、以下のコマンドを実行してください。
1 |
yarn add pixi.js pixi-spine |
ちょっと寄り道…。PixiJSとは?
先に進む前に…、今インストールしたPixiJSについて、サラッと解説します。
PixiJSは、JavaScript 製の2D描画ライブラリで、内部ではWebGLが使われています。
公式サイトで Fast と謳っているように、軽量な動作が魅力的なライブラリです。
タッチやクリック操作のイベントを比較的簡単に扱える点が優秀でして、個人的にも重宝しています。
例えば、ページ右下のずん子ちゃんアニメーションですが、服や髪が動いた後の何もない空白部分をクリックしても反応しません。
しっかりとずん子ちゃんの画像をクリックしないとアニメーションが切り替わらないようになっています。
こういった厳密なクリック判定ができるライブラリですので、大変便利です。
今回は、PixiJSとPixiJS用Spine JavaScriptランタイムを使用しますが、別にPixiJSを使わないと動作しない…なんて事はありません。
ただ単純に、私がPixiJSを触った事があり、ちょうどJavaScriptランタイムがあったので使っただけです。
素のSpine JavaScriptランタイムを使って動作させる事は可能ですので、お使いの環境に合わせてカスタマイズしてみてください。
Spineのエクスポートファイルを格納する
エクスポートしていた3ファイルを、プロジェクトに組み込みましょう。
この後に、jsonをJavaScriptで読み込むので、分かりやすい場所に格納しておきましょう。
サンプルプロジェクトでは、出力先ディレクトリ(out)に格納しています。
注意点として.json、.png、.atlasは、必ず同じディレクトリに格納するようにしてください。
どれか1つでも欠けてしまったり、パスが間違っていたりすると、アニメーションが正しく表示されません。
JavaScript(TypeScript)でアニメーションの処理を記述
では、Spineアニメーションを制御するJavaScript(TypeScript)を用意します。
以下が、メインとなるコード全文です。
サンプルプロジェクトでは、TypeScriptを利用していますが、JavaScriptでも問題ありません。
最終的に、pixi.jsとpixi-spineをバンドルできればOKです。
▼/canvas07_spine/src/main.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import * as PIXI from 'pixi.js' import { Spine } from 'pixi-spine' const app = new PIXI.Application({ … ① backgroundAlpha: 0, height: 600, width: 600 }) document.body.appendChild(app.view) … ② // ずん子のspineデータを読み込んだ後のコールバック関数 const onAssetsLoaded = (loader: any, res: any) => { // Spine インスタンスを生成 const zunko = new Spine(res.zunko.spineData) // ずん子の位置をセット zunko.x = app.screen.width / 2 zunko.y = app.screen.height // ずん子の大きさをセット zunko.scale.set(0.5) // stage にずん子を追加 app.stage.addChild(zunko) // 初期アニメーションとして idle をセット let currentAnimation = 'idle' let isCurrentAnimationIdle = currentAnimation === 'idle' zunko.state.setAnimation(0, currentAnimation, isCurrentAnimationIdle) // ずん子を押下時の処理 app.stage.on('pointerdown', () => { isCurrentAnimationIdle = currentAnimation === 'idle' currentAnimation = isCurrentAnimationIdle === true ? 'pose' : 'idle' zunko.state.setAnimation(0, currentAnimation, !isCurrentAnimationIdle) }) } // ずん子のspineデータを読み込む app.loader.add('zunko', '/zunko.json').load(onAssetsLoaded) … ③〜④ // クリック・タップをONにする app.stage.interactive = true … ⑤ |
処理の全体的な流れは、以下にようになります。
- PIXIアプリケーションを生成
- bodyにPIXIアプリケーションのビュー(app.view)を追加
- ずん子ちゃんのspineデータ(zunko.json)を読み込む
- ずん子ちゃんのspineデータを読み込んだ後に、コールバック(onAssetsLoaded)を発火させる
- PIXIアプリケーションのクリック・タップをONにする
では、細かい点を見ていきましょう。
1 2 3 4 5 6 7 8 9 10 |
import * as PIXI from 'pixi.js' import { Spine } from 'pixi-spine' const app = new PIXI.Application({ backgroundAlpha: 0, height: 600, width: 600 }) document.body.appendChild(app.view) |
まず、npm(yarn)経由でインストールしたpixi.jsとpixi-spineを読み込んでおきます。
そして、読み込んだPIXIを使って、PIXIアプリケーション(const app = new PIXI.Application())を作成します。
PIXIアプリケーションとは、PixiJSの機能を詰め込んだアプリの基礎となるオブジェクトです。
基本的にPixiJSでは、はじめにPIXIアプリケーションを作成し、PIXIアプリケーションを使って描画や状態などを管理・更新していきます。
Vue.jsでいう new Vue({…})と似たようなモノ…と捉えていただければ、OKです。
PIXIアプリケーションを作成後に、Webページに表示されるようにhtmlのbodyにcanvasを追加します。
追加するのは、PIXIアプリケーションそのもの(app)ではなく、app.viewです。
viewは文字通り、PIXIの表示を担うプロパティで、その実態はHTMLCanvasElementです。
したがって、app.viewを追加すると、bodyタグ直下にcanvasタグが追加されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// ずん子のspineデータを読み込んだ後のコールバック関数 const onAssetsLoaded = (loader: any, res: any) => { // Spine インスタンスを生成 const zunko = new Spine(res.zunko.spineData) // ずん子の位置をセット zunko.x = app.screen.width / 2 zunko.y = app.screen.height // ずん子の大きさをセット zunko.scale.set(0.5) // stage にずん子を追加 app.stage.addChild(zunko) // 初期アニメーションとして idle をセット let currentAnimation = 'idle' let isCurrentAnimationIdle = currentAnimation === 'idle' zunko.state.setAnimation(0, currentAnimation, isCurrentAnimationIdle) // ずん子を押下時の処理 app.stage.on('pointerdown', () => { isCurrentAnimationIdle = currentAnimation === 'idle' currentAnimation = isCurrentAnimationIdle === true ? 'pose' : 'idle' zunko.state.setAnimation(0, currentAnimation, !isCurrentAnimationIdle) }) } |
次は、ずん子ちゃんのjsonデータを読み込んだ後のコールバック関数を見ていきます。
引数のresの中にspineデータ(res.zunko.spineData)が入ってくるので、そのデータをzunkoとして取得します。
その後は、ずん子ちゃんのプロパティをセットしていきます。
zunko.xとzunko.yは、それぞれずん子ちゃんのx軸、y軸の位置です。
PIXIアプリケーションの高さと横幅は、app.screenから取得できるので、これらを用いて、zunko.xとzunko.y をセットしていきます。
zunko.xには、表示領域 横幅の半分の位置(app.screen.width / 2)をセット。
zunko.yには、表示領域 縦幅の最下端の位置(app.screen.height)をセットします。
(canvasのy軸は、基本的に最上端が0です。最下端に要素を表示するには表示領域の高さ分をセットする必要があります。)
zunko.scaleは、大きさです。
元アニメーションのサイズを大きめに作ってしまったので、少し小さく表示するために、0.5でセットします。
ずん子ちゃんのプロパティのセットが終わりましたら、app.stage.addChild(zunko)でずん子ちゃんのアニメーションを、PIXIアプリケーションのstageに追加します。
stageとは文字通り、舞台のようなものです。
app.stageに追加された要素は、app.viewに追加されます。つまり、実際のcanvas上に描画されます。
さて、次にアニメーションをセットしていきます。
まずは初期アニメーションからです。
1 2 3 |
let currentAnimation = 'idle' let isCurrentAnimationIdle = currentAnimation === 'idle' // true zunko.state.setAnimation(0, currentAnimation, isCurrentAnimationIdle) // 0, 'idle', true |
「現在のアニメーション」を保持するcurrentAnimationと、
「現在のアニメーションがidleか」を保持するisCurrentAnimationIdleを用意します。
そして、zunko.state.setAnimationで初期アニメーションとしてセットします。
setAnimationは、spineデータに対してアニメーションをセットする関数です。
引数は第1引数から順に、トラック番号、アニメーション名、ループするか を指します。
今回は、トラック番号 = 0、アニメーション名 = idle、ループするか = true でセットし、初期アニメーションを実行します。
ところで、spineのアニメーション名って、どこから分かるの?って話ですが…、
Spineのツリーパネルのアニメーション欄から確認できます。以下のキャプチャの赤枠です。
画像からも分かる通り、今回はidleとposeの2つのアニメーションを用意しています。
初期表示に再生するアニメーションがidleで、クリックした時に再生するアニメーションがposeです。
初期アニメーションの組み込みが終わりましたら、ずん子ちゃんをクリック(タップ)した時に、アニメーションが切り替わる処理を実装していきます。
…といっても複雑な事はやりません。
idleとposeを切り替えて、zunko.state.setAnimationを実行すればいいだけですね。
1 2 3 4 5 6 |
// ずん子を押下時の処理 app.stage.on('pointerdown', () => { isCurrentAnimationIdle = currentAnimation === 'idle' currentAnimation = isCurrentAnimationIdle === true ? 'pose' : 'idle' zunko.state.setAnimation(0, currentAnimation, !isCurrentAnimationIdle) }) |
まずapp.stage.on(‘pointerdown’, …)で、クリック時のイベントを用意します。
stage.onの第2引数に、イベント時に発火させる関数を入れていきます。
関数の内容は、直前のアニメーション名がidleかを判断して、idleであれば、poseに切り替え。poseであれば、idleに切り替え。…といったものです。
注意点として、idleはループアニメーションですが、poseはループアニメーションではないため、zunko.state.setAnimationの第3引数のtrue/falseが逆にしない事ですね。
(やろうと思えば、poseもループできますが、最後のフレームから最初のフレームにつながるため、ガタついたアニメーションになってしまいます。。)
1 2 3 4 |
// ずん子のspineデータを読み込む app.loader.add('zunko', '/zunko.json').load(onAssetsLoaded) … ③〜④ // クリック・タップをONにする app.stage.interactive = true |
loader.addを使って、ずん子ちゃんのjsonデータを読み込むようにします。
このjsonデータは、もちろん先程にSpineからエクスポートしたjsonです。環境に合わせてパスを書き換えてください。
このパスを間違ってしまうと、アニメーションが表示されないので注意しましょう。
ちなみに、add関数の第1引数に、’zunko’と入れていますが、この名称を変更すると、onAssetsLoaded関数内のres.zunko.spineDataの「zunko」の名称が変わります。
例えば、app.loader.add(‘kiritan’, ‘/zunko.json’)とすれば、res.kiritan.spineDataとなります。
この名称ですが、Spineデータのスケルトン名を入れた方が無難です。以下のキャプチャの赤枠内の名称です。
スケルトン名でないとダメ…といったルールはありませんが、管理上の観点からSpineデータのスケルトン名に合わせておくと、あとあと楽になります。
add関数の後に、.load関数をくっつけて、先程用意したonAssetsLoaded関数を入れましょう。
これで、ずん子ちゃんのjsonデータを読み込んだ後に、onAssetsLoaded関数が発火します。
最後に、stage.interactiveにtrueを入れて、クリック・タップ操作をONにします。
PIXIアプリケーションでは、stage.interactiveプロパティをtrueにしないと、ユーザーからの操作を受け付けないので、注意しましょう。
実際に表示してみる
さて、ここまで用意できましたら、実際にビルドしてみましょう。
コードなんて書くの面倒くさい!…という方は、サンプルコードをダウンロードして、ビルドしてみてください。
正しく組めていれば、とってもカワイイずん子ちゃんのアニメーションが再生されると思います。
まとめ
Spine製アニメーションとJavaScriptを使って、Webページに2Dアニメーションを表示する方法を紹介してきました。
今回のずん子ちゃんアニメーションは、非常に簡素なものでしたが、Spineを極めるとハイエンドクラスのアニメーションを作る事が可能です。
それをWebページにも表示できるので、ロマンあふれる技術ですね!
良い時代になったものです。
では、また!