spine-canvaskitランタイム ドキュメント
ライセンスについて
Spineランタイムをアプリケーションに組み込む前に、必ずSpine Runtimes Licenseを確認してください。
はじめに
spine-canvaskitはTypeScriptベースのランタイムで、ブラウザやNode.js環境でCanvasKitを使ってSpineのスケルトンをロード、操作、レンダリングします。spine-canvaskitはフロントエンド(UI要素のレンダリング)とバックエンド(ヘッドレスでスケルトンをレンダリング)の両方で使用できます。
spine-canvaskitはCanvasKit +0.39.1 を必要とし、2色ティントを除くすべてのSpine機能をサポートしています。
spine-canvaskitはSpineランタイム コアAPIのTypeScript実装である spine-core 上に構築されています。コアAPIの詳細についてはSpineランタイムガイドを参照してください。
インストール方法
注意: spine-canvaskitの
major.minor
バージョンがエクスポートを行ったSpineエディターのmajor.minor
バージョンと一致していることを確認してください。詳しくは「Spineエディターとランタイムのバージョン管理に関するガイド」を参照してください。
NPM または Yarn の場合
spine-canvaskitはNPMまたはYarn経由でプロジェクトに追加できます:
yarn add @esotericsoftware/spine-canvaskit@^4.2.0
spine-canvaskitはECMAScriptモジュールで、Node.jsやすべてのモダンブラウザでネイティブに使用したり、webpack、rollup、esbuild などのツールでバンドルしたりできます。また、デバッグしやすいようにソースマップも含まれています。
補足: spine-canvaskitモジュールからクラス、列挙型(enum)、または関数にアクセスするには、
import { loadTextureAtlas } from "@esotericsoftware/spine-canvaskit"
のように単純にそれらをインポートします。
バニラJavaScriptの場合
バニラJavaScript(カスタマイズされていないJavaScript)プロジェクトでは、script
タグを使ってunpkg CDNからspine-canvasを追加できます:
<script src="https://unpkg.com/@esotericsoftware/spine-canvaskit@4.2.*/dist/iife/spine-canvaskit.js"></script>
ソースマップを有効にすると、ランタイムのオリジナルのTypeScriptソースコードをデバッグできます。
また、spine-canvaskitのminified(縮小)バージョンも提供しており、unkgのURLで接尾辞 .js
を min.js
に置き換えることで使用できます。
<script src="https://unpkg.com/@esotericsoftware/spine-canvaskit@4.2.*/dist/iife/spine-canvaskit.js"></script>
注意: バニラJavaScriptプロジェクトにspine-canvaskitをインクルードする場合、すべてのクラス、列挙型、および関数にグローバルspineオブジェクト (
spine.loadTextureAtlas()
やspine.SkeletonData
など) を介してアクセスする必要があります。以下のコードサンプルではspineオブジェクトは省略されているので注意してください。
サンプル
spine-canvaskitランタイムには、利用できる機能セットを実演しているサンプルがいくつか含まれています。
サンプルを実行するには:
- Node.jsをインストールします
- spine-runtimesリポジトリをクローンします
- ターミナルで以下を実行します:cd path/to/spine-runtimes/spine-ts
npm run dev
するとブラウザウィンドウが開き、すべてのspine-tsランタイムの例が表示されます。spine-canvaskit関連のサンプルは CanvasKit
の見出しの下にあります。
以下のサンプルが含まれています:
spine-canvaskit/example/headless.js
: Node.jsコマンドラインアプリで、Spineboyのスケルトンとアトラスをロードし、portal
アニメーションをアニメーションPNGファイルにレンダリングします。npm run dev
を起動したら、ターミナルでnode spine-canvaskit/example/headless.js
を実行すると、アニメーションを含むoutput.png
というファイルが生成されます。spine-canvaskit/example/index.html
: ブラウザでCanvasKitを介したSpineスケルトンのロードとレンダリングを実演するウェブアプリです。spine-canvaskit/example/animation-state-events.html
: アニメーションステートイベントのリスナーの設定を実演するウェブアプリです。spine-canvaskit/example/mix-and-match.html
: 複数スキンを組み合わせる方法を実演するウェブアプリです。
spine-canvaskitランタイムの更新
プロジェクトの spine-canvaskit ランタイムを更新する前に、Spineエディターとランタイムのバージョン管理に関するガイドを参照してください。
spine-canvaskitランタイムの更新は、package.json
ファイル内の spine-canvaskit
パッケージのバージョン文字列を変更して、再度 npm install
を実行するだけでできます。バニラJavaScriptの場合は、scriptタグ内のunpkg URLを更新してください。
注意:
spine-canvaskit
パッケージのmajor.minor
バージョンを変える場合は、必ずSpineスケルトンも同じmajor.minor
バージョンのSpineエディターで再エクスポートする必要はあります!
spine-canvaskitを使用する
アセットのマネージメント
spine-canvaskit用にエクスポートする
以下の実行方法については、Spineユーザーガイド内で紹介されています :
注意: spine-canvaskitはアトラスイメージに自動的に乗算済みアルファ(premultiplied alpha)を適用します。そのため、アトラスをエクスポートするときは乗算済みアルファを使用しないでください!
スケルトンのデータとテクスチャアトラスをエクスポートすると、以下のファイルが得られます:
skeleton-name.json
またはskeleton-name.skel
: これはスケルトンとアニメーションのデータを含んでいます。skeleton-name.atlas
: これはテクスチャアトラスの情報を含んでいます。- 1つまたは複数の
.png
ファイル: これはテクスチャアトラスの各ページで、スケルトンが使用するイメージを含んでいます。
補足: JSONエクスポートよりもバイナリ形式でのスケルトンエクスポートの方がサイズが小さく、読み込みが速いので、基本的にはそちらを選択したほうが良いでしょう。
これらはアプリに同梱することになるファイルです。
Spineアセットの更新
開発中にスケルトンデータやテクスチャアトラスファイルを更新したい時は、単純にSpineエディターから再エクスポートを行なって、これらのソースファイル(.json
, .skel
, .atlas
, .png
)を置き換えるだけで簡単に更新できます。
その際、spine-canvaskitの major.minor
バージョンが、エクスポートを行ったSpineエディターのmajor.minor
が一致していることを確認してください。詳しくは「バージョンの同期」を参照してください。
CanvasKitの初期化
spine-canvaskitはSpineスケルトンの読み込みとレンダリングをCanvasKitに依存しています。spine-canvaskitを使用する前に、CanvasKitを初期化する必要があります。
NodeJSまたはES6対応のブラウザプロジェクトする場合:
const ck = await CanvasKitInit();
ブラウザのscriptタグでバニラJavaScriptを使用する場合:
<script type="module">
const ck = await CanvasKitInit();
</script>
以降のコード例では、ck
が初期化された CanvasKit
オブジェクトへの参照を保持していると仮定します。
コアクラス
spine-canvaskit APIはTypeScriptベースの汎用spine-coreランタイムの上に構築されており、プラットフォームに依存しないコアクラスとSpineスケルトンのロード、クエリ、変更、アニメーションを行うアルゴリズムを提供します。コアクラスもspine-canvaskitの一部です。
ここでは、pine-canvaskitを使用する際によく見ることになる最も重要なコアクラスについてのみ簡単に説明しています。Spineランタイムのアーキテクチャ、コアクラス、APIの使用法の詳細については、Spineランタイムガイドを参照してください。
Atlas
クラスは、.atlas
ファイルとそれに対応する .png
画像ファイルからロードしたデータを保管します。
SkeletonData
クラスは、.json
または .skel
ファイルからロードされたデータを保管します。このスケルトンデータには、ボーン階層、スロット、アタッチメント、コンストレイント、スキン、アニメーションに関する情報が含まれます。 SkeletonData
インスタンスは、通常、それが表すスケルトンで使用されるイメージをソースとする TextureAtlas
も一緒に提供することによってロードされます。これは、Skeleton
インスタンスを作成するための設計図として機能します。複数のスケルトンを同じアトラスとスケルトンデータからインスタンス化し、ロードされたデータを共有することで、ロード時間と実行時のメモリ消費を最小限に抑えることができます。
Skeleton
クラスは、SkeletonData
インスタンスから作成されたスケルトンのインスタンスを格納します。スケルトンは現在のポーズを保管します。つまり、ボーンの位置、スロット、アタッチメント、アクティブなスキンの現在の構成を保管します。現在のポーズは、手動でボーンのトランスフォームを変更するか、より一般的には、AnimationState
を介してアニメーションを適用することで計算されます。
AnimationState
クラスは、 スケルトンに適用する(単数または複数の)アニメーションを追跡し、最後のレンダリングフレームと現在のレンダリングフレームの間の経過時間に基づいてそれらのアニメーションを進め、ミックスを行い、スケルトンインスタンスにアニメーションを適用して現在のポーズを設定します。AnimationState
は AnimationStateData
インスタンスに問い合わせ(処理要求)をして、アニメーション間のミキシング時間を取得します。特定のアニメーション間に使用するミキシング時間が無ければデフォルトミックスタイムを取得します。
spine-canvaskit は spine-core の上に機能を追加することで、Spineスケルトンの読み込み、変更、レンダリングを簡単にしています。
アセットのロード
Spineアトラスおよびスケルトンデータファイルは、それぞれ loadTextureAtlas()
関数およびloadSkeletonData()
関数でロードできます。
spine-canvaskitは、NodeJSやブラウザなど、CanvasKitが利用可能なあらゆるJavaScript環境で動作します。そのため、ローダー関数はプラットフォームに依存せず、絶対パスまたは相対パスを受け取り、ファイルの生(raw)のバイナリコンテンツを含む Buffer
(NodeJS)またはArrayBuffer
(ブラウザ)を返す関数を提供する必要があります。
この関数の TypeScript
シグネチャは以下のとおりです:
NodeJSの場合、この関数は以下のように実装できます:
async function readFile(path) {
return fs.readFileSync(path)
}
ブラウザ環境では、この関数は次のように実装できます:
const response = await fetch(path);
if (!response.ok) throw new Error("Could not load file " + path);
return await response.arrayBuffer();
}
loadTextureAtlas()
関数を使用して、.atlas
ファイルとその .png
ページ画像ファイルを読み込みます:
アトラスの .png
ファイルは、.atlas
ファイルが存在するディレクトリに対して相対的に解決されます。
同様に、loadSkeletonData()
関数を使用して、スケルトンデータの .json
または .skel
ファイルをロードします:
loadSkeletonData()
は、ファイル名のそれぞれの拡張子に基づいて、.json
ファイルと .skel
ファイルの両方をロードできます。atlas
は、スケルトンデータから派生したスケルトンをレンダリングするために必要な画像を取得するために使用されます。
SkeletonDrawable
SkeletonDrawable
は、スケルトンの現在のポーズとスキンを保存する Skeleton
と、アニメーションの追跡と適用を行う AnimationState
をカプセル化します。
スケルトンのアトラスとスケルトンデータファイルをロードしたら、そこから1つ以上のSkeletonDrawable
インスタンスを作成できます。SkeletonData
は暗黙的にTextureAtlas
を参照していることに注意してください。
SkeletonDrawable
コンストラクタを呼び出して、新しいインスタンスを作成します:
SkeletonDrawable
内の Skeleton
と AnimationState
には、それぞれのフィールドからアクセスできます:
const skeleton = drawable.skeleton
skeleton.x = 300;
skeleton.y = 380;
skeleton.scaleX = skeleton.scaleY = 0.5;
// AnimationStateにアニメーションをセットする
const animationState = drawable.animationState;
animationState.setAnimation(0, "walk", true);
Skeleton
および AnimationState
APIの包括的な説明については、Spineランタイムガイドを参照してください。以下に、API の最低限の使用例を示します。
アニメーションの適用
補足: より詳しい情報についてはSpineランタイムガイドの アニメーションの適用セクションを参照してください。
AnimationState
を使用すると、複数のトラックに1つ以上のアニメーションをキューに入れることができます。トラックはインデックス0から始まります。上位トラックのアニメーションは、下位トラックのアニメーションでキーになっているプロパティを上書きします。このトラックの概念により、複数のアニメーションを一度に再生したり、ミックスしたりすることができます。
トラック0に特定のアニメーションを設定するには、AnimationState.setAnimation()
を呼び出します:
最初のパラメーターはトラック、2番目のパラメータはアニメーションの名前、3番目のパラメータはアニメーションをループさせるかどうかを指定します。
複数のアニメーションをキューに入れることもできます:
animationState.setAnimation(0, "walk", true);
animationState.addAnimation(0, "jump", false, 2);
animationState.addAnimation(0, "run", true, 0);
addAnimationByName()
の最初のパラメーターはトラックです。2番目のパラメーターはアニメーションの名前です。3番目のパラメーターはアニメーションをループさせるかどうかを指定します。最後のパラメーターは、このアニメーションがトラック上の前のアニメーションと置き換わるまでの時間(ディレイ)を秒単位で指定します。
上の例では、まず "walk"
アニメーションが再生されます。その2秒後に "jump"
アニメーションが一度再生され、続いて "run"
アニメーションに移行し、ループします。
あるアニメーションから別のアニメーションに遷移するとき、AnimationState
は指定された時間の間、アニメーションをミックスします。これらのミックスタイムは AnimationStateData
インスタンスで定義され、AnimationState
はそこからミックスタイムを取得します。
AnimationStateData
インスタンスは、AnimationStateからも利用できます。デフォルトのミックスタイムや、特定のアニメーションのペアのミックスタイムを設定できます:
animationState.data.setMix("walk", "jump", 0.1);
アニメーションを設定または追加すると、TrackEntry
オブジェクトが返されます。これを利用してアニメーションの再生をさらに変更できます。例えば、アニメーションを逆再生するようにTrackEntryを設定することができます:
entry.reverse = true;
利用できるオプションについて詳しくは TrackEntry
クラスのドキュメントをご覧ください。
注意:
TrackEntry
インスタンスを使用している関数の外部で保持しないでください。TrackEntryは内部で再利用されるため、それが表すアニメーションが完了すると無効になります。
スケルトンをスムーズにセットアップポーズに戻したい場合は、アニメーショントラックに空のアニメーションをセットまたはキューに追加します:
controller.animationState.addEmptyAnimation(0, 0.5, 0.5);
setEmptyAnimation()
の最初のパラメーターはトラックを指定します。2番目のパラメーターは、前のアニメーションをミックスアウトし、"空の"アニメーションをミックスするために使用するミックスタイムを秒単位で指定します。
addEmptyAnimation()
の最初のパラメーターはトラックを指定します。2番目のパラメーターはミックス時間を指定します。3番目のパラメーターはディレイ(秒単位)で、このディレイの後に空のアニメーションがミキシングされてトラック上の前のアニメーションと置き換わります。
AnimationState.clearTrack()
を使えばトラック上のすべてのアニメーションを即座にクリアすることができます。すべてのトラックを一度にクリアするには AnimationState.clearTracks()
を使います。しかしこれはスケルトンを最後に適用されたポーズのままにする点に注意してください。
スケルトンのポーズをセットアップポーズに戻すには、Skeleton.setToSetupPose()
を使います:
これはボーンとスロットの両方をセットアップポーズの設定にリセットします。スロットだけをセットアップポーズの設定にリセットしたい場合は Skeleton.setSlotsToSetupPose()
を使用してください。
AnimationStateイベント
AnimationState
は、再生中のアニメーションのライフサイクル中にイベントを発行します。必要に応じてこのイベントをリッスンすることで、それらに反応させることができます。SpineランタイムのAPI では、以下のイベントタイプを定義しています:
Start
: アニメーションが開始された時に発されます。Interrupted
: アニメーションのトラックがクリアされた、または新しいアニメーションが設定されたなどにより中断された時に発されます。Completed
: アニメーションが1ループを完了するごとに発されます。Ended
: アニメーションが二度と適用されない時に発されます。Disposed
: アニメーションのTrackEntryが破棄された時に発されます。Event
: ユーザーが定義したイベントが発生した時に発されます。
イベントを受け取るには、AnimationStateListener
コールバックを、すべてのアニメーションでイベントを受信する AnimationState
か、キューされた特定のアニメーションの TrackEntry
に登録します:
entry.listener = {
event: (entry, event) => console.log(`User defined event: ${event.data.name}`),
complete: (entry) => console.log(`Animation loop completed.`)
}
drawable.animationState.setListener({
end: (entry) => console.log(`Animation ${entry.data.name} has ended and will not be applied again.`
});
詳しくは example/animation-state-events.html
をご覧ください。
スキン
多くのアプリケーションやゲームでは、髪や目、ズボン、イヤリングやバッグなどのアクセサリーなど、さまざまなアイテムを組み合わせてカスタムアバターを作ることができます。Spineでは、複数スキンを組み合わせることでこれを実現することができます。
以下のようにして、他のスキンからカスタムスキンを作成することができます:
const skin = new spine.Skin("custom");
// 作成したカスタムスキンに他のスキンを追加
skin.addSkin(skeletonData.findSkin("skin-base"));
skin.addSkin(skeletonData.findSkin("nose/short"));
skin.addSkin(skeletonData.findSkin("eyelids/girly"));
skin.addSkin(skeletonData.findSkin("eyes/violet"));
skin.addSkin(skeletonData.findSkin("hair/brown"));
skin.addSkin(skeletonData.findSkin("clothes/hoodie-orange"));
skin.addSkin(skeletonData.findSkin("legs/pants-jeans"));
skin.addSkin(skeletonData.findSkin("accessories/bag"));
skin.addSkin(skeletonData.findSkin("accessories/hat-red-yellow"));
skeleton.setSkin(skin);
skeleton.setSlotsToSetupPose();
まず、コンストラクタ Skin()
でカスタムスキンを作成します。
次に、スケルトンから SkeletonData
を取得します。これは SkeletonData.findSkin()
でスキンを名前から探すのに使用します。
Skin.addSkin()
で新しいカスタムスキンにまとめたいスキンをすべて追加します。
最後に、Skeleton.setSkin()
で出来上がった新しいスキンを Skeleton
にセットし、Skeleton.setSlotsToSetupPose()
を呼び出して、以前のスキンやアニメーションのアタッチメントが残らないようにします。
詳しくは example/mix-and-match.html
をご覧ください。
ボーンのトランスフォームの設定
Spineエディターでスケルトンを構築する際、スケルトンは「スケルトン座標系」と呼ばれるもので定義されます。そのため、キャンバスに対するタッチ座標やマウス座標をボーンの座標系に変換するには、Bone.worldToLocal()
メソッドを使用します。
これは、ユーザー入力に基づいてボーンの位置を駆動する場合に便利です。
詳しくは example/ik-following.html
をご覧ください。
パフォーマンス
spine-canvaskitは CavansKit.MakeVertices()
と Canvas.drawVertices()
を使用して、個々のスケルトンアタッチメントのメッシュを描画します。Skiaはこれらのメッシュをバックグラウンドでバッチ処理しているように見えますが、spine-canvaskit自体でアタッチメントのメッシュのバッチ処理を行うことで、さらなるパフォーマンスの向上が期待できます。
詳しくは example/micro-benchmark.html
をご覧ください。