独自のランタイムを書く
描画ランタイムが満たすべきモジュール契約と、Studio がそれを検出・検証・パッケージ化する方法
Studio のモデルキャラクターは Live2D や Spine に限定されません。コンテナ内に描画できるレンダラーであれば、既存のレンダラー、パーティクルシステム、スプライトシートプレイヤー、WebGL シーンなど何であってもランタイムとして利用できます。カスタムランタイム としてインストールすると、その挙動は他の二つの名前付き製品とあらゆる点で同じになります。
Studio がモジュールに要求するもの
runtimes/puppet/ 配下の一つのフォルダに、ブラウザで読み込み可能な ES モジュール である index.js を含め、エンジンの PuppetBackend を満たすオブジェクトを産出させます。
export default function createPuppetBackends({ game, resolveFile, log }) {
log("info", "my-renderer registered");
return {
name: "my-renderer",
mount(container, ctx) {
// Draw inside `container`. Return the instance the engine drives.
return {
ready: () => loadedPromise,
apply: (state) => { /* a COMPLETE state, never a diff */ },
command: (name, payload) => { /* one-shot things */ },
describe: () => ({ motions: [], expressions: [], skins: [], params: [] }),
resize: (size) => { /* the box changed */ },
dispose: () => { /* container is emptied by Studio */ },
};
},
};
}必須なのは name と mount のみです。name は空であってはならず、キャラクターが参照する際の名前になります。命名についての注意は後述します。
エンジンの Puppet ページが契約の全体です。各メソッドが受け取るもの、呼び出されるタイミング、apply が変化量ではなく状態全体を受け取る理由、そしてセーブデータの復元がリプレイではなく一度の apply で行われる理由が書かれています。
受け付けるエクスポート形式
Studio は複数のエクスポート形式を受け付けます。これにより、モジュールが他のものも同時にエクスポートできます。
| エクスポート | 扱い |
|---|---|
export default function (ctx) | ファクトリ。ホストのコンテキストで呼び出され、バックエンド一つ、その配列、または promise を返せます。 |
export default backend | バックエンドオブジェクトそのもの。 |
export default [a, b] | 一つのモジュールから複数のバックエンド。 |
export const createPuppetBackends / puppetBackends / puppetBackend | 上記のいずれかを名前付きで、default のスロットは空けておく。 |
ファクトリが何も返さなくてもエラーにはなりません。
ホストコンテキスト
ファクトリは以下を受け取ります。
| フィールド | 内容 |
|---|---|
game | これらのバックエンドが登録される対象の Game。 |
resolveFile(path) | モジュール自身のフォルダ内にあるファイルの URL。wasm コア、シェーダー、参照テーブルなど、独自の付属ファイルを持つランタイムのためのもの。アクセスはそのフォルダ内に限定されます。 |
log(level, message) | ホストのコンソールに、モジュール名を前置して報告します。 |
resolveFile はモジュールの隣にあるファイルを解決し、マウントコンテキストの ctx.resolveSibling はモデルの隣にあるファイルを解決します。ランタイムとそれが描画するモデルは別々のバンドルであるため、両方が存在します。
インストール
プロジェクト → ランタイム → カスタムランタイム を使い、名前を入力してから、バンドル済みの index.js 一つ、またはまるごとコピーするフォルダを Studio に指定します。
Studio はそのモジュールをゲームと全く同じ方法で読み込み、バックエンドを産出することを確認します。何も登録しないモジュールは拒否され、コピーはロールバックされます。
フォルダ名はモジュールが登録するバックエンドの名前になります。インストール時に入力した名前ではありません。エンジンはキャラクターのランタイムを登録名で解決する一方、エディタが一覧表示するのはフォルダ名であるため、両者が食い違うとキャラクターがステージ上に描画されないまま残ります。Studio はフォルダ名を一致するようリネームし、使用した名前を報告します。
ビルド時に起きること
runtimes/puppet/ 配下で index.js を持つすべてのフォルダはビルド出力にコピーされ、ゲームは最初のシーンがマウントされる前にそれを読み込みます。index.js を持たないフォルダは警告とともにスキップされ、ビルドが失敗することはありません。
バックエンドが最後まで届かない puppet があっても、ゲームはクラッシュしません。エンジンはその領域を、位置・トランスフォーム・保存された状態も含めて保持し、内部には何も描画しません。
実践上の注意
- 可能な限り一つのファイルにまとめてください。 単一の自己完結型 ES モジュールであれば解決の問題は生じません。フォルダ形式を使うのは、ランタイムが実行時に本当に付属ファイルを読み込む場合だけにしてください。
- モジュールはゲームごとに一度だけ読み込まれます。そのため、実装は自分がどのゲームのために描画しているかを気にする必要がありません。
- 可能な限り
describe()を実装してください。 これにより Studio のモーションと表情のフィールドがテキストボックスからリストに変わり、キャラクターエディタでプレビューが描画されるようになります。これは任意であり、エディタはこれがなくても動作します。 dispose()はコンテナを空にする必要はありません。 それは Studio が行います。mount が例外を投げた場合も含めてです。- オプションはランタイムが解釈します。 Studio はキャラクターのオプションマップを、そのキーを一切読まずにそのまま渡します。エンジンの三つの状態チャンネルに含まれない、ランタイムが必要とする何にでも使ってください。