コンテンツにスキップ

Vega ストーリー埋め込み

テキストファイルでストーリーを作り、自分のウェブページで再生します。まず 2 行の会話から始め、背景、サウンド、モデルを加えていきます。この手順はホストされた Vega ブラウザーモジュールを使うので、npm パッケージをインストールせずに進められます。

完全なオリジナル例 を選んだまま 選んだ内容を読み込む、続けて 再生 を押します。庭とミラの最初の台詞が表示されます。次の台詞 で進み、一時停止 で再生を止め、スライダーでストーリー内を移動します。プレーヤーを終了 はプレーヤーを閉じるので、その後別のストーリーを読み込めます。

1. ストーリーを選ぶ

完全なオリジナルファイルから始めます。読み込んで再生を押すと最初の行を読めます。

完全な JSON を開く · JSON をダウンロード

例を準備しています…

高度なパラメーターと完全な入力(再読み込みで反映)
コンテンツの言語
読み込み記録とエラーの詳細
この例のコードを見る

オリジナル例はこの完全な story.json と garden.svg を使います。Our Notes: Treasure は、モデルとサウンドリソースを含む公開済みのゲームストーリーを読み込みます。高度なパラメーター の設定は次の読み込みで反映されます。言語の選択は作成済みのテキストから選ぶもので、単一言語のストーリーを翻訳するものではありません。

2. 最初のストーリーを保存する

Section titled “2. 最初のストーリーを保存する”

my-story というフォルダーを作ります。テキストエディターに次の完全な JSON を貼り付け、そのフォルダーに UTF-8 で story.json として保存します。ファイル名が story.json.txt ではなく story.json であることを確認してください。JSON はデータです。引用符とカンマはそのまま残し、const や import のような JavaScript の語は書きません。

{"localization":{"locales":["en"],"defaultLocale":"en"},"commands":[
{"command":2,"targetName":"Mira","text":"This is my first story."},
{"command":2,"targetName":"Mira","text":"Press Next line to continue."}
]}

この完全な会話 JSON を開いて story.json として保存することもできます。commands はストーリーの動作を順に並べたリストです。ここでの 2 は Talk、targetName は話者、text は画面に表示される台詞です。まず 2 行の台詞を編集し、ほかのフィールドはそのままにします。

完全なスターター HTML を開き、index.html として story.json の隣に保存します。スターターフォルダー全体の ZIP をダウンロードして展開し、このディレクトリを得ることもできます。コピーできる完全なソースは下にあります。

my-story/
index.html
story.json

フォルダーを HTTP で配信します。Python がなければ Python.org からインストールしてください。Python 3 があれば my-story でターミナルを開いて次を実行します。

Terminal window
python3 -m http.server 8000

Windows では py -m http.server 8000 を使います。ブラウザーで http://localhost:8000/ を開きます。エディターのローカル HTTP プレビューでも構いません。ファイルをダブルクリックすると file:// アドレスで開きますが、JSON の読み込みには HTTP アドレスが必要です。終わったら Ctrl+C でサーバーを止めます。

Load / retry を押して Loaded を待ち、Play を押します。最初の台詞が表示され、Next line で台詞の表示や次への進行を行います。この最初のストーリーには画像がないので、背景は無地です。プレーヤーを閉じるとリソースが解放され、再度読み込めます。

完全な index.html:すべてを 1 つのファイルにコピー
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>My first story</title>
<style>
body { max-width: 60rem; margin: auto; padding: 1rem; font: 1rem/1.5 sans-serif; color: CanvasText; background: Canvas; }
#stage { height: 60vh; min-height: 320px; position: relative; }
button { font: inherit; min-height: 48px; margin: .25rem; }
</style>
<h1>My first story</h1>
<p id="status" role="status" aria-live="polite">Press Load to read story.json.</p>
<div id="stage"></div>
<button id="load">Load / retry</button>
<button id="play" disabled>Play</button>
<button id="pause" disabled>Pause</button>
<button id="next" disabled>Next line</button>
<button id="close">Close</button>
<script type="module">
const button = id => document.getElementById(id);
const status = document.getElementById("status");
let handle, controller, generation = 0;
function controls(enabled) {
for (const id of ["play", "pause", "next"]) button(id).disabled = !enabled;
}
async function close() {
generation++; controller?.abort(); controls(false);
const old = handle; handle = undefined;
await old?.dispose();
}
button("load").onclick = async () => {
button("load").disabled = true;
let current;
try {
await close(); current = ++generation;
controller = new AbortController(); const signal = controller.signal;
status.textContent = "Loading…";
const { mountStory } = await import("https://haneoka.org/embed/vega.js");
const response = await fetch("./story.json", { signal });
if (!response.ok) throw new Error(`story.json: HTTP ${response.status}`);
const storyData = await response.json();
signal.throwIfAborted(); if (current !== generation) return;
handle = mountStory(document.getElementById("stage"), {
document: storyData,
locale: storyData.localization?.defaultLocale ?? "en",
assetsBase: new URL("./", response.url).href,
signal,
onEvent(event) { if (event.type === "error" && current === generation) status.textContent = event.error.message; }
});
await handle.ready;
if (current !== generation) return;
controls(true); status.textContent = "Loaded. Press Play, then Next line.";
} catch (error) {
if (current === generation) {
const failed = handle; handle = undefined; await failed?.dispose().catch(() => {});
if (current === generation) status.textContent = `Loading failed: ${error.message}. Check the file, then retry.`;
}
} finally { button("load").disabled = false; }
};
button("play").onclick = () => handle?.play();
button("pause").onclick = () => handle?.pause();
button("next").onclick = () => handle?.next();
button("close").onclick = async () => { await close(); status.textContent = "Closed. Press Load to try again."; };
window.addEventListener("pagehide", () => { void close(); }, { once: true });
</script>
</html>

スクリプトは 3 つの動作を別々に行います。

const response = await fetch("./story.json"); // request the saved file
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const storyData = await response.json(); // turn JSON text into a JavaScript object
const player = mountStory(document.getElementById("stage"), {
document: storyData, // pass that object to the player
locale: "en",
assetsBase: new URL("./", response.url).href,
});
await player.ready; // the page enables Play only when loading succeeds

この抜粋は完全な HTML の読み込み部分の説明です。ファイル名、URL、パース済みオブジェクトはそれぞれ別の入力です。

手元にあるもの使い方
JavaScript で作成済みのストーリーオブジェクトmountStory(container, { document: storyData })
ストーリー JSON ファイルを指す HTTP アドレス上のように取得してパースするか、手順 6 の httpDataSource を使う
ファイル入力で選んだ JSON ファイルfileDataSource を使い、画像と音声の取得元を選ぶ
公開 Haneoka ゲームストーリー ID手順 7 の haneokaStorySource を使う

garden.svg を同じ場所に保存します。story.json を背景付きの完全なストーリーに置き換えるか、次の動作を commands 配列の先頭、最初の Talk の前に挿入します。

{"command":25,"background":{"url":"garden.svg"}}

25 は Stage です。background.url が画像ファイルを指します。この動作と続く Talk の間にカンマを残してください。フォルダーには index.html、story.json、garden.svg があります。プレーヤーを読み込み直すと、会話の後ろに庭が表示されます。スターターの assetsBase はこのフォルダーを指すので、garden.svg は story.json の隣で解決されます。

5. サウンドを 1 つ加え、次に複数のキューを対応付ける

Section titled “5. サウンドを 1 つ加え、次に複数のキューを対応付ける”

cue-tone.wav を story.json の隣に保存し、JSON をサウンド付きの完全なストーリーに置き換えます。最初の台詞と一緒に小さなオリジナルのテストトーンが再生されます。その Talk 動作に加えたフィールドは次のとおりです。

"voices": [{"soundId":10000001,"categoryName":"Voice","playableUrl":"cue-tone.wav","volume":0.5}]

音声付きの台詞にするには、WAV を自分の録音に置き換え、playableUrl をそのファイル名どおりに変更します。soundId はキューの識別、playableUrl は再生可能な音声の場所です。ブラウザーが音声を許可できるよう、Play はクリックやタップで押してください。SDK は宣言された音声を共有リソース転送で読み込み、再生用 URL を自動で準備します。リソース要求には設定したバイト予算と期限が適用され、破棄すると所有する再生用 URL が解放されます。

既存のストーリーにキュー ID があるなら、アドレスを JavaScript のマップ 1 つにまとめ、マウント前に音声レコードを埋めます。スターター HTML では const storyData = await response.json(); の直後に次を加えます。

const sounds = { 10000001: "./cue-tone.wav" };
for (const command of storyData.commands) {
for (const voice of command.voices ?? []) {
const key = voice.soundId ?? voice.id;
if (Object.hasOwn(sounds, key)) voice.playableUrl = sounds[key];
}
}

これはサンプルの commands[].voices に対するホスト側の準備で、sounds は mountStory のオプションではありません。BGM と効果音の動作は bgm.playableUrl と se.playableUrl レコードを使います。公開 Haneoka ストーリーでは voiceRefs、bgmRef、seRef がサウンドレジストリで解決済みです。ほかの形式には、タイムラインやコマンドグループ内のキューも含めて、キューフィールドをこれらのレコードに変換するアダプターが必要です。

6. 別の URL や選んだファイルを読み込む

Section titled “6. 別の URL や選んだファイルを読み込む”

ソース選択付きの完全な HTML には、4 つの方法すべて、読み込みエラーの表示、キャンセル、再生、進行スライダーが含まれます。同じ JSON とアセットの隣に index.html として保存します。Another JSON address を選んでアドレスを貼り付け、Load を押します。保存した JSON を選ぶには A JSON file on my computer を選びます。

HTTP の場合、より短い SDK の方法ではスターターの fetch/JSON/mount のブロックを次に置き換えます。

const { mountStory } = await import("https://haneoka.org/embed/vega.js");
const { httpDataSource } = await import("https://haneoka.org/embed/core.js");
handle = mountStory(document.getElementById("stage"), {
source: httpDataSource({ url: new URL("./story.json", location.href).href }),
locale: "en",
signal,
});
await handle.ready;

httpDataSource は JSON の取得とパースを内部で行います。相対リソースパスは JSON アドレスのディレクトリを基準にし、絶対 URL は自身のオリジンを保ちます。assetsBase はリソースが別のディレクトリにあるときだけ設定します。

JSON ファイルの選択で許可されるのはそのファイルだけです。隣の画像、モデル、音声へのアクセスは許可されません。完全な HTML は fileDataSource(file) を使い、それらのリソースを HTML のフォルダーから配信します。すべてローカルで選んだリソースを使う場合はローカルファイルと fileResourceResolver を参照してください。モデルの付属ファイルのパスも、そのリソースマップで解決できる必要があります。

7. 公開済みの Our Notes ストーリーを 1 つの入口で読み込む

Section titled “7. 公開済みの Our Notes ストーリーを 1 つの入口で読み込む”

Haneoka のストーリー ID があるときは mountHaneokaStory を使います。1 つのリリースマニフェストから、対応するプレーヤーモジュール、テーマ、Cubism ランタイムを選びます。指定するのはストーリー ID、サーバー、言語です。

短編ストーリーの完全な HTML を開き、index.html として保存して、手順 3 と同様に配信します。Load を押して Ready を待ち、Play を押します。この例は afterlive_10109 を使います。作成済みのコマンドとアセット参照は完全なカタログ JSON で確認できます。

import { mountHaneokaStory } from "https://haneoka.org/embed/vega-haneoka.js";
const player = mountHaneokaStory(document.getElementById("stage"), {
id: "afterlive_10109", server: "intl", locale: "en",
});
await player.ready;
// Call from a button click:
player.play();

完全な HTML は要素、ボタン、読み込み段階、キャンセル、エラー表示、破棄を備えています。ネイティブプレーヤーにはシーン操作もあります。外部ボタンはハンドルのメソッドを示すためのもので、自分のページではレイアウトに合う操作を残せます。

公開短編ストーリーの完全な index.html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Haneoka story</title>
<style>
body{font:16px system-ui;margin:16px;background:Canvas;color:CanvasText}
#stage{height:min(70vh,600px);position:relative;width:100%}
button{font:inherit;margin:4px;min-height:48px;padding:8px 16px}
output{display:block;margin:8px}
</style>
<h1>Haneoka story</h1>
<button id="load" disabled>Load</button>
<button id="cancel" disabled>Cancel loading</button>
<button id="play" disabled>Play</button>
<button id="next" disabled>Next line</button>
<button id="exit" disabled>Dispose</button>
<output id="status" role="status">Preparing player</output>
<div id="stage"></div>
<script type="module">
import {mountHaneokaStory} from "https://haneoka.org/embed/vega-haneoka.js";
let player, generation=0;
const by=id=>document.getElementById(id);
const labels={manifest:"Preparing player",modules:"Preparing player",runtime:"Preparing player",data:"Loading story",resources:"Loading assets",ready:"Ready"};
const controls=ready=>{by("play").disabled=!ready;by("next").disabled=!ready;};
by("load").onclick=async()=>{
const current=++generation;
by("load").disabled=true;controls(false);
try{
await player?.dispose();
if(current!==generation)return;
player=mountHaneokaStory(by("stage"),{
id:"afterlive_10109",server:"intl",locale:"en",
onEvent:event=>{
if(current!==generation)return;
if(event.type==="stage")by("status").textContent=labels[event.stage];
if(event.type==="error"){controls(false);by("status").textContent=String(event.error?.message??event.error);}
},
});
window.storyPlayer=player;
by("exit").disabled=false;by("cancel").disabled=false;
await player.ready;
if(current===generation){controls(true);by("status").textContent="Ready";}
}catch(e){if(current===generation)by("status").textContent=player?.snapshot.phase==="cancelled"?"Cancelled":String(e.message??e);}
finally{if(current===generation){by("load").disabled=false;by("cancel").disabled=true;}}
};
by("cancel").onclick=()=>{player?.cancel();};
by("play").onclick=()=>{player.play();by("status").textContent="Playing";};
by("next").onclick=()=>{player.next();};
by("exit").onclick=async()=>{
++generation;controls(false);by("cancel").disabled=true;by("exit").disabled=true;
try{await player?.dispose();by("status").textContent="Disposed";}
catch(e){by("status").textContent=String(e.message??e);}
finally{by("load").disabled=false;}
};
by("load").disabled=false;by("status").textContent="Not loaded";
window.addEventListener("pagehide",()=>{void player?.dispose();},{once:true});
</script>
</html>

ページ内の Our Notes: Treasure プリセットは、より長い millsage_004_1_02 ストーリーを使います。完全な公開 JSON には 728 個のコマンドがあります。どちらのプリセットも同じ短い API を使います。id はストーリーカタログが返す ID に置き換えてください。

カタログ JSON には参照が含まれます。Haneoka アダプターがランタイム、モデルメタデータ、メディアレコードを準備します。自分の完全な Vega ストーリー JSON を再生するには、手順 3~6 の fetch → response.json() → mountStory({ document }) の経路を保ちます。Haneoka ヘルパーがリソースオリジンを提供するので、リソースベースを設定するのは、高度なプレーヤーオプションで意図的にアセットを差し替えるときだけです。

Cubism プロバイダーは標準の *.model3.json と旧式の *.model.json を受け付けます。標準モデルでは、キャラクター記述子がマニフェストを指せます。

{"runtime":{"format":"cubism3","model":"models/Mira/Mira.model3.json"}}

書き出したディレクトリ構成を保ちます。

models/Mira/
Mira.model3.json
Mira.moc3
Mira.2048/texture_00.png
motions/...
expressions/...
コンポーネント必要なとき
model3.json、参照される .moc3、参照されるすべてのテクスチャ基本のモデル描画。名前とパスはマニフェストと一致させる
Cubism プラグイン、Web ランタイムモジュール、互換の Cubism Coreモデル描画の実行。メインのストーリー埋め込みはモデルプロバイダーを自動でインストールしない
物理、ポーズ、ユーザーデータの JSONFileReferences で宣言されているとき。未使用の宣言を削除するか、参照先のファイルを用意する
モーションと表情のファイルストーリーが使うとき。マニフェストのカタログとファイルパスを保つ
追加のシェーダー JSON やシェーダーディレクトリこのランタイムに外部シェーダー設定は不要。描画シェーダーは同梱されている

Live2D の書き出しモデルファイルのガイドと model3.json 仕様を参照してください。プロバイダーはマニフェストから付属ファイルを探します。

完全なモデルストーリーのテンプレートを story.json として保存します。command: 0 は入口の動作、targets はミラの識別、positionType: 5 は中央への配置、characterModel はモデルの指定です。runtime.model を実際のマニフェストパスにし、モデルの付属ファイル名とディレクトリはそのままにします。手順 6 の完全な HTML を使い、Advanced: standard Live2D in my own story を開いて Cubism プロバイダーを有効にし、オリジナルの JSON を選んで Load、Play を押します。このテンプレートには自分で書き出したモデルファイルが必要で、ダウンロードには図のモデルディレクトリは含まれません。

ZIP 入力は現在実装されていません。 モデルのアーカイブを展開し、パスを保ったまま展開済みファイルを配信して、記述子をそのマニフェストに向けてください。既存のモデル書き出しは ZIP のダウンロードを作るもので、ZIP を読み込むものではありません。公開オプションには shaders や shadersDir もありません。独自のシェーダーパックローダーには別のランタイム拡張が必要で、これらのキーをストーリーに加えても効果はありません。

表示されるもの対処
story.json: HTTP 404http://localhost:8000/story.json を開き、ファイル名、ディレクトリ、大文字小文字を確認する
JSON パースエラー提供された完全な JSON を使い、テキストの値を 1 つだけ編集する。JSON は二重引用符を使い、末尾カンマは使えない
Failed to fetchJSON のアドレスを直接開く。HTTP のページを使い、ブラウザーで失敗した要求のアドレスを確認する
別ドメインの CORS エラーファイルをページと同じオリジンで配信するか、リソースサーバーの応答でページのオリジンを許可する。クライアントの要求ヘッダーでは CORS の許可は得られない
会話は動くが画像や音声ファイルが失敗するリテラルのパスと assetsBase を確認する。JSON を 1 つ選んでも付属ファイルは選ばれない
モデルランタイムが失敗するランタイムモジュール、Core、マニフェスト、バイナリ、テクスチャの要求を別々に確認する。JSON の取得成功はストーリーファイルが読めることしか示さない
読み込みがキャンセルされた、またはプレーヤーが閉じられたawait player.dispose() の後に新しいハンドルで再試行する。完全な例はこれを自動で行う

play() は開始または再開、pause() は一時停止、next() は台詞を進めます。await player.seek(0.5) は、選択肢を含む現在到達可能なストーリー経路の中間にあるコマンド境界へ移動し、復元したコマンド番号を返します。player.snapshot は状態と進行を提供します。状態イベントは約 100 ms ごとに更新され、操作時はすぐに更新されます。

ready はレンダラー、必要なリソース、フォント、シーク索引を待ちます。cancel() は読み込みを止めます。準備完了後に渡したシグナルを中止するとプレーヤーを破棄します。dispose() は冪等です。同じ要素に別のプレーヤーをマウントする前に待ってください。リソースの失敗は ready の拒否とエラーイベントで現れ、オブザーバーのエラーは onListenerError で隔離されます。

ランタイムは宣言された URL を解決し、台詞、ID、ソースのラベルはそのままにします。ローカルのリソースマップは Unicode を含むリテラルのパスを使います。コアローダーは所有する Blob URL を解放し、モデルプロバイダーとレンダラーはデコード済みリソースと GPU リソースを解放します。転送の既定値は CORS、資格情報なし、応答ごと 64 MiB、リソース期限 30 秒です。動的なランタイムモジュールと Core スクリプトには独自のブラウザー読み込み経路があります。

すべてのプレーヤーには、メインサイトへリンクする半透明の Haneoka アイコンが、最初は左上に表示されます。brandingCorner: "top-right" で移動できます。オーバーレイはシーン全体の領域を保ちます。ストーリーとアセットの著作権とライセンスはそれぞれのものです。

バンドルするアプリケーションでは、パッケージ名は @haneoka/embed-vega で、テーマと Haneoka ヘルパーにもパッケージのサブエントリがあります。npm での公開は未完了です。このチュートリアルではホストされたブラウザーモジュールを使ってください。ソースリポジトリからビルドするときは、ワークスペースの依存関係をインストールしてビルドしてからパックします。テーマのフォントとスプライトのアセットはビルド済みモジュールと一緒に置きます。連携できる全体は下のパラメーター一覧にあります。

独自のファイル・ネイティブリソースポートを提供する Engine 連携については、ホストリソースで完全な型、キャンセル、リースの所有を参照してください。

ブラウザーの入口は https://haneoka.org/embed/vega-haneoka.js、バンドル用のパッケージサブエントリは @haneoka/embed-vega/hosted です。ホストされた入口は既定で自身のリリースマニフェストを選びます。パッケージを直接インポートすると既定は https://haneoka.org/embed/manifest.json です。コンテナーはレイアウトサイズが 0 でないブラウザーの HTMLElement です。ブラウザーのコンテキストがないか、ID が空または文字列でなければ TypeError、破棄前に同じコンテナーへ 2 度目のホストマウントをすると Error です。ハンドルはすぐに返るので、ready を待つ間保持してください。

オプション型;必須/既定値効果と制約
id必須の文字列現在のカタログが返すストーリー ID そのまま。空・空白・非文字列は同期的に拒否し、利用できない ID はソースのエラーで ready を拒否する。
server任意の文字列;intlアセットサーバー。テキスト言語とは独立。既存のカタログスラッグの検証と提供状況を使う。
locale任意の文字列;ja起動時のコンテンツと UI の言語。公開ストーリーの配列は ja/en/zh-TW/zh-CN/ko を使う。ストーリーの作成済み言語から選ぶ。
signal任意の AbortSignal起動をキャンセルする。ready 後の中止はプレーヤーを破棄する。すでに中止済みのシグナルは ready を拒否する。
manifestUrl任意の絶対 HTTP(S) URL;ホスト ESM は入口のリリースマニフェスト、パッケージインポートは安定ルート互換の SDK マニフェストを選ぶ。不正な URL・プロトコルは例外、不正・バージョン混在のメタデータは ready を拒否する。1 つの完全なリリースを使う。
apiBase任意の絶対 HTTP(S) ディレクトリ;https://haneoka.org/api/v1/互換のストーリーカタログの代替。コアのディレクトリ検証を使う。モデルのパスはそのカタログから得る。
runtime任意の CubismEmbedOptions;リリースのランタイム記述子ランタイムの高度な上書き。必須のネストフィールドは moduleUrl と runtime.cubismCoreUrl、runtime.cubism2CoreUrl と runtime.motionSyncCoreUrl は任意。ライセンスを受けた互換のモジュール・Core URL を使う。ファクトリーの契約は下記。
playerOptions任意のオブジェクト;組み立てたプレーヤーに渡す mountStory オプションの一部。全フィールドと除外は下記。
fetcher任意の (Request)=>Promise<Response>;ネイティブ fetchマニフェスト、ランタイム記述子、ストーリー、リソースの転送。既定のファーストパーティ要求は no-referrer を使う。動的モジュール・Core スクリプトはネイティブのブラウザー読み込みのまま。
headers任意の HeadersInit;なしコア転送ヘッダー。スクリプト・モジュールの要求はネイティブの経路を使う。
credentials任意の omit/same-origin/include;omitリソース転送の資格情報。公開の読み取りは omit。資格情報付きのクロスオリジン応答にはサーバーの許可が必要。
maxBytes任意の正の安全な整数;67108864起動と子転送の応答ごとのエンコード済みバイト上限。不正な値は RangeError、超過すると該当の読み取りを拒否する。GPU・デコード済みメモリの上限とは別。
onEvent任意の (HaneokaStoryEvent)=>void生成時から stage、load、state、seek、error イベントを受け取る。
onListenerError任意の (unknown)=>voidオブザーバーの例外は隔離されてここに転送される。リスナーの失敗は再生の失敗にならない。

playerOptions は theme、brandingCorner、shell、plugins、renderer、assetsBase、resolveResource、resourceTimeoutMs、resolveLocalizedText、onListenerError を受け付けます。ネストしたフィールド、既定値、範囲、エラーは下の mountStory の表と同じです。このヘルパーは theme の既定を haneoka にし、テーマと Cubism プラグインをインストールして、渡されたプラグイン配列を追加します。子は shell が明示されない限り Haneoka のゲームシェルを使います。通常はカタログのリソースベースを保ち、assetsBase/resolveResource はリソースを意図的に置き換えるときだけ上書きします。子のオプション検証は起動中に行われ、ready を拒否します。最上位の onListenerError はホストのオブザーバーを扱い、playerOptions.onListenerError は子の契約に従います。

playerOptions は source、document、server、locale、signal、onEvent、fetcher、headers、credentials、maxBytes を除外します。共有コンテキストのフィールドは最上位で渡します。このファーストパーティヘルパーはカタログ ID を受け取ります。作成済みドキュメント、HTTP の JSON URL、選んだ File には手順 3~6 の mountStory とソースファクトリーを使います。

ハンドルのメンバー型・シグネチャ動作
readyPromise<void>選んだモジュール、ランタイム記述子、子のデータ・モデル・フォント・シーク準備を待つ。元の起動・読み込みエラーで拒否する。
snapshotStoryEmbedSnapshotphase は loading/ready/cancelled/error/disposed、playing/paused/finished/seeking、commandIndex/commandCount/progress。ラッパーの phase はこのハンドルを表す。
playerVegaPlayerHandle または undefined子への高度なアクセス。寿命はホストのハンドルが所有する。
stagemanifest/modules/runtime/data/resources/ready現在の準備段階。自分の読み込みラベルに使える。
release文字列または undefinedマニフェスト読み込み後に選ばれた不変の SDK リリース。
play/pause/next()=>voidready が必要で、なければ Error。委譲した再生のエラーは子の error イベントを使う。
seek(ratio:number)=>Promise<number>有限の 0~1 のコマンド経路の進行。復元した境界を返す。子の範囲・ライフサイクルエラーは意味を保つ。
subscribe(listener)=>unsubscribe以後の HaneokaStoryEvent を受け取る。破棄後の購読は Error。
cancel()=>void読み込み中の起動を終了する。ready 後は dispose を使う。
dispose()=>Promise<void>冪等。キャンセルし、起動の後始末を待ち、子とローダーを破棄し、コンテナーの登録を解除する。後始末の失敗は AggregateError で拒否しうる。再利用前に待つ。

stage イベントは {type:"stage",stage,release?} です。load/state/seek/error の形は下の mountStory と同じです。公開モジュールマニフェストとランタイム記述子は同じリリースに属します。不正なモジュールパス、ランタイム記述子のハッシュ・長さの不一致、配布オリジン外のランタイムパス、インポートの失敗、リソースエラーは ready を拒否します。起動に失敗すると子と登録が片付けられるので、ホストは新しいハンドルを作れます。自分のライフサイクルを閉じるため、失敗したハンドルも破棄してください。

必須の位置引数 container は ownerDocument/defaultView を持ち、レイアウト寸法が 0 でないブラウザーの HTMLElement です。ブラウザー以外のコンテナーは TypeError、破棄前に同じコンテナーへ 2 つ目のストーリーをマウントすると Error です。ハンドルは同期的に返ります。TypeScript では document か source のどちらか一方だけを選びます。実装は渡された source を優先し、コマンドの欠落や不正は ready を拒否します。

オプション型;必須/既定値効果、制約、失敗
documentAdvStory;source の代わりに必須メモリ上のエンジンドキュメント。commands は配列が必要で、不正なドキュメントは TypeError で ready を拒否する。作成された入力であり、5 言語カタログである必要はない。
sourceDataSource<AdvStory>;document の代わりに必須load(context) がエンジンドキュメントを返す。memory/http/file/カスタムのファクトリーに対応。ソースの失敗は ready を拒否する。
server任意の文字列;undefined、公開アダプターは intlsource に渡すアセットレジストリ。locale とは独立。
locale任意の文字列;en作成・再生の言語。起動時にプレーヤーのロケールとシェル UI の言語も設定する。
assetsBase任意の絶対 HTTP(S) ディレクトリ;ソースのベースクエリ・フラグメントなし。ソースのリソースオリジンを上書きする。不正なベースは TypeError。
resolveResource任意の (key,DataContext)→string/URL/Blob または Promise;キーそのまま宣言されたメディア・モデル・フォントのパスを対応付ける。解決済み URL は 1 度だけ通過する。リゾルバーのエラーは ready・リソース使用を拒否する。
fetcher任意の (Request)→Promise<Response>;fetchデータとリソースのバイトはコア転送を使う。モジュール・Core スクリプトのローダーは別の読み込み経路を持つ。
credentialsomit/same-origin/include;omitローダーの要求。必要に応じてサービスが資格情報・オリジンを許可する。
headers任意の HeadersInit;なしコア転送ヘッダー。スクリプト・モジュール要求のヘッダーではない。
maxBytes正の安全な整数;67108864ドキュメント・リソースごとのデコード済みバイト数。不正な値は RangeError。GPU・キャッシュ合算の予算ではない。
onListenerError任意の (unknown)→voidオブザーバーエラーの受け口。再生結果から隔離される。
themeportable/haneoka;portableHaneoka にはテーマ・シェルプラグインが必要。寄与がなければ ready を拒否する。
brandingCornertop-left/top-right;top-leftフルサイズのシーン上に常時表示される半透明の角のクレジット。非表示にするオプションはない。
shellfalse または VegaPlayerShellOptionsportable の既定は false、Haneoka の既定は {initialScreen:"game"}。全フィールドは下記。
pluginsreadonly VegaPlugin[];[]richtext、portable UI、Three、リソースアダプターの後に追加されるプロバイダー・コマンド。実際のプラグインの setup/manifest/contributions が必要。setup・依存の失敗は ready を拒否する。
rendererThreeRendererPluginOptions;{}レンダラーとポストテクスチャの全設定は下記。
resourceTimeoutMs正の安全な整数;30000アダプターのリソース読み取りごとの合計期限。不正な値は RangeError、超過すると TimeoutError で拒否する。ストーリー全体の期限ではない。
signal任意の AbortSignal起動をキャンセルする。ready 後の中止はこのプレーヤーを破棄する。リソースごとのタイムアウトは別。
onEvent任意の (StoryEmbedEvent)→void起動前に登録される。subscribe と同じイベント形式。
resolveLocalizedText任意の (value:unknown)→{text:string,lang?:string}カスタムのテキスト解決。既定は Vega のローカライズ。リゾルバーの形を返す必要があり、独自の例外は実行中の操作を失敗させうる。

共通の 9 つのローダーオプションは詳しいコアの転送・リソース契約に従います。マウントの自動再生や全体の loadTimeoutMs オプションはありません。読み込み全体には外部のシグナル・期限を使い、再生はユーザー操作から開始します。

シェルのネストオプションと設定

Section titled “シェルのネストオプションと設定”
shell フィールド型/既定値意味
storage任意の VegaSaveStorage;メモリread(key)→Promise<string|null>、write(key,value)→Promise<void>、remove(key)→Promise<void>、list(prefix)→Promise<readonly string[]>。永続化は呼び出し側のバックエンドが担う。不正なセーブは該当のセーブ・ロード操作を失敗させる。
projectId任意の文字列トリムした明示 ID、次に story.vegaProject.id/story.id、次に vega-story。セーブの名前空間の識別。
settingsId任意の文字列;projectId別の設定用名前空間。永続化された設定は initialSettings より優先する。
initialSettings任意の Partial<VegaNarrativeSettings>;表の既定値シェル生成時に適用する。その後マウントが外側の locale をプレーヤー・UI に適用するので、起動言語には外側の locale を使う。
title任意の文字列トリムした明示タイトル、次にローカライズしたプロジェクト・ストーリーのタイトル・名前、次に projectId。
initialScreentitle/game汎用シェルの既定は title、Haneoka 埋め込みの暗黙シェルは game で開始する。
onExitRequest任意の ()=>void/Promise<void>シェルが一時停止・タイトルへ戻った後に Exit を受け取る。遷移・閉じる処理はホストが選ぶ。コールバックの失敗はそのシェル操作を拒否する。
initialSettings キー型/既定値範囲と動作
textSpeed数値/1.1~5 に制限。
autoDelay数値/1.5秒。0~30 に制限。
masterVolume数値/10~1 に制限。
bgmVolume数値/.80~1 に制限。
voiceVolume数値/10~1 に制限。
seVolume数値/.90~1 に制限。
language文字列/autoストーリーの言語または auto。保存する文字列は最大 64 文字。
uiLanguage文字列/autoUI 言語。サーバーとは別。
reducedMotion真偽値/false対応する UI の動きを減らす。
highContrast真偽値/false対応する UI のコントラスト設定。
instantText真偽値/false読むテキストをすぐ完全に表示する。
webText真偽値/falseHaneoka 読書 UI のブラウザーテキストモード。バージョンの注記は下記。
textSize数値/1.5~2 に制限。
subtitlesEnabled真偽値/true利用できる字幕を表示する。
bgmEnabled真偽値/trueBGM の有効設定。

有限でない数値設定は代替値を使い、真偽値設定は正規化されます。不明なシェル設定キーは設定時に RangeError です。webText は公開ソースリビジョン engine1916c57/theme1d5b20e/shell436ade8 にあります。対応するビルドか、それらのリビジョンを含む後の CDN 記録を使ってください。旧 r22 プレーヤーを明示的に固定するなら、そのリビジョンが対応する設定を使います。対応するシェルでは、生成時に shell.initialSettings.webText を設定するか、ready 後に story.player.shell.setSetting("webText", true) を呼びます。

レンダラーのネストオプション

Section titled “レンダラーのネストオプション”
フィールド型/既定値意味と組み合わせ
rendererIdvega-three-webgl2/haneoka-three-webgl2;vega-three-webgl2シーンプロバイダーの経路。通常は埋め込みのバックエンドをそのままにする。
profilevega/unity-adv;vegaプロバイダーのシーンプロファイル。
staticPortraitFallback真偽値/trueモデルプロバイダーがないときに静止立ち絵への代替を許す。
colorGradingModeldr/hdr;ldrポストのカラーパイプライン。
contributionId文字列/haneoka.renderer-threeインストールするレンダラー寄与の識別。
contributionName文字列/Vega Three WebGL2人が読む寄与名。
backendvega-three-webgl2/haneoka-three-webgl2;vega-three-webgl2寄与の経路。埋め込みは vega-three-webgl2 を選ぶ。非互換の登録はプレーヤー生成を拒否する。
postTextureResolver任意の ThreePostTextureResolverpostTextureAssets とは併用できない(TypeError)。リゾルバーが渡したテクスチャを所有し、任意の dispose はプラグインの寿命に結び付く。
postTextureAssets任意の Readonly<Record<string,string>> または ReadonlyMap<string,string>安定したレンダラーテクスチャキーをキーとする認可済み URL。単独で渡すとリゾルバーを作る。

カスタムの postTextureResolver には必須の resolve(reference:unknown,usage)→Three.Texture|null、任意の preload(requests,signal?)→Promise<void>、任意の dispose() があります。各要求は {reference,usage} で、usage には film-grain、color-lookup、lens-dirt が含まれます。null を返すと既存のパイプラインの代替を許します。選んだ Three インスタンスと互換のテクスチャを返し、後始末はプロバイダーが担います。パラメーターの競合はプラグイン生成時に同期的に例外となり、読み込み・描画・プロバイダーのエラーは ready やイベントで届きます。

ソース、Cubism、テーマのファクトリー

Section titled “ソース、Cubism、テーマのファクトリー”

haneokaStorySource({id,apiBase?}) には公開ストーリー ID が必要です。apiBase の既定値は https://haneoka.org/api/v1/ で、コアのディレクトリ検証に従います。選んだストーリー、story-runtime、宣言された Live2D メタデータだけを、マウントの server/locale で読み込みます。ストーリー・モデルの欠落や不正なカタログレコードは読み込みを拒否します。リリースやアカウントの引数は不要です。

cubismStoryPlugin({moduleUrl,runtime}) には別途ライセンスを受けた絶対モジュール URL と runtime.cubismCoreUrl が必要です。runtime.cubism2CoreUrl? と runtime.motionSyncCoreUrl? は任意のバージョン別スクリプト URL です。モジュールは createCubismWebRuntimeAdapter をエクスポートする必要があり、エクスポートやプロバイダー機能がなければモデル準備を拒否します。このプラグインはモデルが必要なときにアダプターを遅延読み込みし、プロバイダーのライフサイクルを保ち、Core は同梱しません。互換のアダプターと Core のバージョンを揃えてください。

haneokaStoryPlugins にオプションはなく、テーマと既定シェルのプラグイン一覧です。createHaneokaThemeAssetsPlugin(provider) は任意の assets()、resolveChatImage(value,kind:icon|stamp)、resolveSourceAsset(path) コールバックを受け取ります。assets は任意の URL フィールド arrow,auto,chatWindow,chatBackground,chatBackgroundFallback,chatLock,chatTextBox,chatComposerPlus,chatComposerPhoto,chatComposerPicture,chatComposerSmile,chatComposerMicrophone,chatIcons,fast,frame,fullscreen,subtitles,skip を返します。上書きがなければ既存のテーマアセットを使います。HANEOKA_POST_TEXTURE_ASSETS は固定の認可済みレンダラーアセットマップで、必要なら renderer.postTextureAssets に渡します。

ドキュメントの形、ハンドルとイベントの全容

Section titled “ドキュメントの形、ハンドルとイベントの全容”

AdvStory には commands(このマウントで必須)、任意の runtime と localization、ソース定義のフィールドがあります。localization には任意の locales:string、defaultLocale:string、arrayOrder:string が含まれ、作成者が定義するもので 5 言語固定の要件ではありません。runtime は Vega の Partial<AdvRuntimeConfig> で、エンジンを通して保持されます。個々のコマンドのオペコードとリソース構造は選んだコマンド・プロバイダーのスキーマに従います。例では公開プロトコルの Stage25 と Talk2 を使います。

ハンドルのメンバーシグネチャ動作・エラー
readyPromise<void>レンダラー・リソース・フォント・シーク索引の起動。onEvent が error を観測しても拒否は残る。
snapshot読み取り専用オブジェクトphase,playing,paused,finished,seeking,commandIndex,commandCount,progress。phase は loading/ready/cancelled/error/disposed。
playerVegaPlayerHandle または undefinedready 後の高度なエンジンハンドル:id,player,lifetime,root,slots,shell?,dispose()。破棄は埋め込みに任せる。
play / pause / next()=>voidready が必要(なければ Error)。Play は再開する。非同期のインタープリター失敗は error イベントになる。Next は読み進めを要求する。
seek(ratio:number)=>Promise<number>有限の 0~1、それ以外は RangeError。実際のコマンド番号を返す。再生・一時停止の意図を保つ。
subscribe(listener)=>unsubscribe以後のイベント。破棄後の購読は Error。
cancel()=>void読み込み中の起動のみ。ready 後は何もしない。再試行の前に破棄する。
dispose()=>Promise<void>共有・冪等の Promise。ローダー・URL の前にエンジンを解放し、ホストの DOM・リスナーを除去し、起動の後始末を待つ。後始末の失敗は AggregateError。

イベントは {type:"load",event:EmbedEvent}、{type:"state",snapshot}、{type:"seek",commandIndex}、{type:"error",error} です。load イベントのフィールドはコアに従います。state は変更されたスナップショットを 100ms ごとと操作時に公開します。リスナーの例外は隔離されます。破棄は phase disposed で完了し、その state/load イベントは生成時のリスナーが観測できます。リソース・ソース・プラグインの失敗は実際のエラーを保ち、キャンセルは signal.reason で拒否します。この埋め込みは第二のエラーコード体系を加えません。

このページは安定エイリアスを使います。古い不変の r22 プレーヤーは専用行のクレジットレイアウトを保ちます。リビジョンを明示的に固定するときは、エンジン、テーマ、シェル、ランタイムのバージョンの互換を保ってください。