跳转到内容

Vega 剧情嵌入

先写两句对白,保存成文件,在自己的网页上播放。跑通以后,再依次添加背景、声音和模型。本教程使用已托管的 Vega 浏览器模块,第一步无需安装 npm 包。

保持选中「完整原创示例」,点击「加载所选内容」,等待就绪,再点「播放」。你会看见庭园背景和第一句对白。「下一句」继续对白;「暂停」暂停播放;进度条可以跳转。「换内容前退出」关闭当前播放器。

1. 选择剧情

从完整原创文件开始。加载后,你会看见示例画面,点击播放读第一句。

打开完整 JSON · 下载 JSON

正在准备示例…

高级参数与完整输入(修改后重新加载)
内容语言
加载记录与错误详情
查看页面示例代码

原创示例使用这份完整中文 story.json 和 garden.svg。「Our Notes 宝物」读取本站已发布的游戏剧情及它引用的模型、声音素材。「高级参数」里的设置在下次加载时生效。切换语言会选择作者已有的对应文本;单语剧情不会自动翻译。

新建一个叫 my-story 的文件夹。在纯文本编辑器里粘贴下面的完整 JSON,以 UTF-8 保存为 story.json,放进这个文件夹。确认实际文件名不是 story.json.txt。JSON 存的是数据:保留引号和逗号,不要写入 const、import 等 JavaScript 代码。

{"localization":{"locales":["zh-CN"],"defaultLocale":"zh-CN"},"commands":[
{"command":2,"targetName":"小遥","text":"这是我的第一段剧情。"},
{"command":2,"targetName":"小遥","text":"点击下一句继续。"}
]}

也可以打开完整对白 JSON,另存为 story.json。commands 是按顺序执行的剧情动作。这里的 2 表示对白,targetName 是说话人的名字,text 是屏幕上的句子。先改这两句文字,其余字段照示例保留。

打开完整入门 HTML,保存为 index.html,与 story.json 放在一起。也可以下载完整入门文件夹 ZIP,解压即可得到这个目录。下面给出了可以整体复制的源码。

my-story/
index.html
story.json

用 HTTP 打开这个文件夹。没有 Python 时先从 Python 官网下载安装。已安装 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 关闭服务。

点击「加载 / 重试」,等页面显示「已加载」,再点「播放」。第一句会出现,「下一句」会显示完整文字或继续下一句。这份初始剧情没有图片,所以背景是纯色。点「退出」释放播放器后,可以重新加载。

完整 index.html:全部复制到一个文件中
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>我的第一段剧情</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>我的第一段剧情</h1>
<p id="status" role="status" aria-live="polite">点击加载,读取 story.json。</p>
<div id="stage"></div>
<button id="load">加载 / 重试</button>
<button id="play" disabled>播放</button>
<button id="pause" disabled>暂停</button>
<button id="next" disabled>下一句</button>
<button id="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 = "正在加载…";
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 = "已加载。点击播放,再点下一句。";
} catch (error) {
if (current === generation) {
const failed = handle; handle = undefined; await failed?.dispose().catch(() => {});
if (current === generation) status.textContent = `加载失败:${error.message}。检查文件后重试。`;
}
} 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 = "已退出。可再次点击加载。"; };
window.addEventListener("pagehide", () => { void close(); }, { once: true });
</script>
</html>

代码明确做了三步:

const response = await fetch("./story.json"); // 请求刚保存的文件
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const storyData = await response.json(); // 把 JSON 文本读成 JavaScript 对象
const player = mountStory(document.getElementById("stage"), {
document: storyData, // 将这个对象交给播放器
locale: "zh-CN",
assetsBase: new URL("./", response.url).href,
});
await player.ready; // 完成加载后,页面才启用播放按钮

这段解释的是完整 HTML 中的加载部分。文件名、网址和读出来的对象是不同的输入:

你手上有什么怎么交给播放器
JavaScript 中已经创建好的剧情对象mountStory(container, { document: storyData })
指向完整剧情 JSON 的 HTTP 网址如上先 fetch 再读取 JSON;也可用第 6 步的 httpDataSource
通过文件选择框选中的 JSON 文件使用 fileDataSource,同时指定图片、声音从哪里读取
本站游戏剧情 ID使用第 7 步的 haneokaStorySource

把 garden.svg 保存到同一个文件夹。用完整中文背景剧情 替换 story.json,或者在 commands 数组最前面、第一句对白之前插入这条动作:

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

25 表示切换场景,background.url 是图片文件名。这条动作与后面的对白之间需要一个逗号。现在文件夹里有 index.html、story.json、garden.svg 三个文件。重新加载后,庭园会出现在对白后面。入门 HTML 的 assetsBase 指向 JSON 所在文件夹,因此能找到旁边的 garden.svg。

5. 添加一个声音,再集中填写 cue 地址

Section titled “5. 添加一个声音,再集中填写 cue 地址”

把 cue-tone.wav 保存到同一文件夹,用完整中文声音剧情 替换 story.json。播放第一句时,会响起一小段音量较低的原创测试音。这是在第一条对白中新增的字段:

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

要播放台词录音,就换成自己的音频文件,并把 playableUrl 改为准确文件名。soundId 标识这个 cue,playableUrl 指向能播放的声音。通过点击或触摸「播放」开始,让浏览器允许声音播放。SDK 会通过共用资源传输读取声明的音频,自动准备播放网址。资源请求遵守所设置的字节预算与加载期限;退出时释放播放器拥有的播放网址。

已有剧情里已经写了 cue ID 时,可以在 JavaScript 中集中维护地址,再于挂载前补入声音记录。在入门 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.playableUrl、se.playableUrl。本站适配器已经能根据声音资源表,把 voiceRefs、bgmRef、seRef 补成完整记录。其他剧情格式需要适配器转换它们的 cue 字段;时间轴、并行动作里的 cue 也要处理。

完整来源选择 HTML 提供四种读取方式,并包含可见错误、取消、播放和进度条。把它保存为 index.html,与上述 JSON 和素材放在一起。选择「Another JSON address」,填入完整网址再加载;选择「A JSON file on my computer」则可以选择保存到电脑的 JSON。

HTTP 也有更短的 SDK 写法,替换入门 HTML 中 fetch、读取 JSON 和挂载的部分:

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: "zh-CN",
signal,
});
await handle.ready;

httpDataSource 会在内部请求、读取 JSON。相对素材路径以 JSON 所在目录为基准,绝对网址保留自己的来源。只有素材放在其他目录时才需要填写 assetsBase。

选择一个 JSON,只授予网页读取这个文件的权限,不会同时选中旁边的图片、模型和音频。完整 HTML 使用 fileDataSource(file) 读所选 JSON,素材仍从 HTML 文件夹的 HTTP 地址读取。要完全使用自己选择的本地素材,见 本地文件与 fileResourceResolver 说明。模型清单引用的其他文件也必须能通过资源映射找到。

7. 用一个入口载入 Our Notes 已发布剧情

Section titled “7. 用一个入口载入 Our Notes 已发布剧情”

手上有本站剧情 ID 时,使用 mountHaneokaStory。它从同一版本清单选择播放器模块、主题和 Cubism 运行时;你填写剧情 ID、服务器与语言即可。

打开完整短剧情 HTML,保存为 index.html,按第 3 步用 HTTP 打开。点「加载」,等「已就绪」,再点「播放」。示例使用短剧情 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: "zh-CN",
});
await player.ready;
// 在按钮点击时调用:
player.play();

完整 HTML 提供元素、按钮、加载阶段、取消、错误提示与销毁。播放器画面也带有自身控制;外部按钮用于演示 handle 方法,自己接入网页时可选用适合布局的一套控制。

已发布短剧情的完整 index.html
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Haneoka 剧情</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 剧情</h1>
<button id="load" disabled>加载</button>
<button id="cancel" disabled>取消加载</button>
<button id="play" disabled>播放</button>
<button id="next" disabled>下一句</button>
<button id="exit" disabled>销毁</button>
<output id="status" role="status">正在准备播放器</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:"正在准备播放器",modules:"正在准备播放器",runtime:"正在准备播放器",data:"正在加载剧情",resources:"正在加载素材",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:"zh-CN",
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="已就绪";}
}catch(e){if(current===generation)by("status").textContent=player?.snapshot.phase==="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="播放中";};
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="已销毁";}
catch(e){by("status").textContent=String(e.message??e);}
finally{by("load").disabled=false;}
};
by("load").disabled=false;by("status").textContent="尚未加载";
window.addEventListener("pagehide",()=>{void player?.dispose();},{once:true});
</script>
</html>

本页「Our Notes 宝物」预设保留较长的 millsage_004_1_02。它的完整公开 JSON包含 728 条动作。两个预设使用相同的短 API;换剧情时,将 id 改为资料目录实际返回的编号。

游戏目录中的 JSON 带有素材引用,本站适配器会准备运行资料、模型信息与声音记录。自己创作的完整 Vega JSON 则继续使用第 3–6 步的 fetch → response.json() → mountStory({ document })。本站助手已提供素材来源,只有主动覆盖素材时才在高级 playerOptions 中设置资源目录。

Cubism provider 已能读取标准 *.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 runtime 模块及兼容的 Cubism Core执行模型渲染;基础剧情嵌入不会自动安装模型 provider
Physics、Pose、UserData JSON清单 FileReferences 声明了它们时需要;提供文件或删除不用的声明
动作与表情文件剧情使用时需要;保留模型清单中的名称与文件路径
额外 shader JSON 或 shader 目录当前运行时内置渲染着色器,基础模型无需外部 shader 配置

文件说明见 Live2D 的导出数据指南与 model3.json 规范。Provider 按模型清单寻找其他文件。

接着把完整模型剧情模板保存为 story.json。模板里的 command: 0 是入场,targets 指定小遥,positionType: 5 把角色放在中间,characterModel 指向模型。把自己的实际清单路径填进 runtime.model,其余模型文件保持原名与目录。使用第 6 步的完整 HTML,展开「Advanced: standard Live2D in my own story」,选中启用 Cubism,再选原创 JSON、加载、播放。这个模板需要你自己的模型导出文件;下载模板并不包含示意目录里的模型。

目前还没有直接载入 ZIP 的入口。 先解压、保留相对目录、通过 HTTP 提供这些文件,再把角色描述指向模型清单。已有的模型 ZIP 导出用于下载,不能作为 ZIP 载入功能。公开参数也没有 shaders、shadersDir;若要载入自定义 shader 包,需要增加运行时扩展,给剧情添加这两个字段不会产生该效果。

页面显示什么下一步做什么
story.json: HTTP 404打开 story.json,检查目录、文件名及大小写
JSON 解析失败先换回完整示例,再一次只改一个文字值;JSON 使用双引号、没有末尾多余逗号
Failed to fetch直接打开 JSON 网址,确认页面使用 HTTP,再查看浏览器实际失败的请求地址
其他域名提示 CORS把素材与网页放在同一来源,或由素材服务器在响应中允许你的网页来源;客户端请求头不能授予跨域权限
对白能读,图片或声音失败检查素材路径与 assetsBase;选择一个 JSON 不会连带选择它的素材
模型运行时失败分别检查 runtime 模块、Core、模型清单、二进制和贴图请求;JSON 请求成功只说明剧情文件可读取
已取消或已退出await player.dispose() 完成后创建新实例重试;完整示例已处理此步骤

play() 开始或恢复,pause() 暂停,next() 继续对白。await player.seek(0.5) 跳到当前可达剧情路径一半的动作边界,包含选项路径,返回实际恢复的动作序号。比例表示剧情进度。player.snapshot 提供状态与进度;状态事件约每 100 ms 更新,控制操作立即更新。

ready 等待渲染器、所需资源、字体与跳转索引。cancel() 停止加载;就绪后取消外部 signal 会销毁播放器。dispose() 可重复调用。在同一元素中创建另一个播放器前,等待它完成。加载失败通过 ready 拒绝与 error 事件报告;监听器错误由 onListenerError 隔离处理。

运行时解析已声明的资源网址,保留对白、ID 与来源标签。文件映射按字面路径匹配,也支持 Unicode。Core loader 释放自己创建的 Blob URL,模型 provider 和渲染器释放解码及 GPU 资源。默认传输为 CORS、omit credentials、每项响应 64 MiB、资源请求 30 秒期限。动态 runtime 模块与 Core 脚本使用浏览器自己的加载路径。

播放器默认在左上角悬浮半透明 Haneoka 原图标,链接主站。brandingCorner: "top-right" 可放到右上角,浮层保留完整画面面积。你的剧情和素材保留各自作者与许可。

构建工程使用的包名是 @haneoka/embed-vega,主题与本站助手另有 package 子入口。公众 npm 安装入口尚未发布,本教程直接使用托管浏览器模块。从源码仓库构建时,先安装 workspace 依赖并构建,再打包。主题模块需保留它的字体和图片素材。下面的参考表继续逐项说明全部参数。

Engine 应用自带文件或原生资源端口时,见宿主资源教程,包含完整类型、取消和租约所有权。

浏览器入口为 https://haneoka.org/embed/vega-haneoka.js,构建工程中的包子入口为 @haneoka/embed-vega/hosted。托管模块默认选择自身版本的 manifest;直接导入包时,默认读取 https://haneoka.org/embed/manifest.json。容器是具有有效宽高的浏览器 HTMLElement。缺少浏览器上下文、id 为空或非 string 时同步抛 TypeError;同一容器尚未销毁就再次挂载时抛 Error。函数立即返回 handle,等待 ready 期间也保留它以便清理。

参数类型/必填/默认作用与限制
id必填 string当前目录实际返回的剧情 ID。空白或非 string 同步拒绝;不存在的 ID 通过来源错误拒绝 ready。
server可选 string,intl素材服务器,与文本语言独立。使用既有目录的 slug 校验和可用性规则。
locale可选 string,ja启动时的内容与 UI 语言。公开剧情数组顺序为 ja/en/zh-TW/zh-CN/ko,选择剧情实际提供的语言。
signal可选 AbortSignal取消准备;就绪后 abort 会销毁播放器。已取消的 signal 会拒绝 ready。
manifestUrl可选绝对 HTTP(S) URL;托管 ESM 默认本版本 manifest,包导入默认稳定根 manifest选择兼容的 SDK 清单。非法 URL 或协议同步拒绝;格式错误或混合版本 metadata 拒绝 ready。使用一份完整版本清单。
apiBase可选绝对 HTTP(S) 目录,https://haneoka.org/api/v1/其他兼容的剧情目录。遵守 core 目录校验,模型路径继续来自这个目录的响应。
runtime可选 CubismEmbedOptions,默认版本内的 runtime descriptor高级运行时覆盖。嵌套必填 moduleUrl 与 runtime.cubismCoreUrl;runtime.cubism2CoreUrl、runtime.motionSyncCoreUrl 可选。提供相互兼容且有许可的 module/Core 地址;下方 factory 表保留完整契约。
playerOptions可选 object,传给已组装播放器的 mountStory 参数;完整允许字段与排除项见下文。
fetcher可选 (Request)=>Promise<Response>,原生 fetch清单、运行时描述、剧情及资源传输。默认本站请求使用 no-referrer;动态模块/Core 脚本仍按浏览器原生路径加载。
headers可选 HeadersInit,无Core 传输请求头,不配置浏览器 script/module 请求。
credentials可选 omit/same-origin/include,omit资源传输凭据。公开读取使用 omit;带凭据跨域需要服务器提供对应许可。
maxBytes可选正 safe integer,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 插件并追加传入的 plugins;子播放器默认使用游戏 shell,也可显式设置 shell。常规使用保留目录提供的素材根,只有主动替换素材时才覆盖 assetsBase/resolveResource。子参数在准备过程中校验,因此它们的错误会拒绝 ready;顶层 onListenerError 处理本站助手的观察者,playerOptions.onListenerError 沿子播放器契约。

playerOptions 排除 source、document、server、locale、signal、onEvent、fetcher、headers、credentials、maxBytes,共用上下文放在顶层。这个本站助手接收目录 ID;作者对象、HTTP JSON 或所选 File 继续按第 3–6 步使用 mountStory 与数据源 factory。

handle 成员类型/签名行为
readyPromise<void>等待所选模块、运行时描述及子播放器的数据、模型、字体和跳转准备;失败保留原始准备/加载错误。
snapshotStoryEmbedSnapshotphase 为 loading/ready/cancelled/error/disposed,另含 playing/paused/finished/seeking、commandIndex/commandCount/progress。phase 描述本 handle。
playerVegaPlayerHandle 或 undefined子播放器高级访问,生命周期由本站 handle 管理。
stagemanifest/modules/runtime/data/resources/ready当前准备阶段,可映射为面向用户的加载提示。
releasestring 或 undefined清单读取后确定的不可变 SDK 版本。
play/pause/next()=>void需先 ready,否则 Error;播放异步失败沿子播放器 error 事件。
seek(ratio:number)=>Promise<number>finite 0–1 当前动作路径比例,返回实际边界;子播放器范围和生命周期错误保持原义。
subscribelistener=>unsubscribe监听后续 HaneokaStoryEvent;disposed 后调用会抛 Error。
cancel()=>void终止加载中的准备;就绪后使用 dispose。
dispose()=>Promise<void>幂等,取消、等待准备清理、销毁子播放器与 loader 并释放容器登记。清理失败可抛 AggregateError;复用容器前等待它完成。

stage 事件为 {type:"stage",stage,release?};load/state/seek/error 结构与下方 mountStory 一致。模块清单与 runtime descriptor 来自同一版本;错误模块路径、运行时描述的 hash/长度不匹配、越出分发来源的 runtime 路径、导入失败和资源失败都会拒绝 ready。准备失败会清理子实例和登记,宿主可建立新 handle;仍应 dispose 旧 handle 完成自己的生命周期。

container必填浏览器HTMLElement,有ownerDocument/defaultView和有效尺寸;非法TypeError,同容器未销毁再次挂载Error。立即返回handle。TypeScript中document/source二选一;实际实现优先source,缺/坏commands会拒绝ready。

字段类型/默认作用与错误
documentAdvStory,source替代必填内存剧情,commands需array,非法ready TypeError;作者数据可单语。
sourceDataSource<AdvStory>,document替代必填memory/http/file/custom源返回engine文档,source失败传递ready。
serverstring,undefined;本站intl来源registry,与语言独立。
localestring,en作者语言,启动后同时设置player语言与shell UI语言。
assetsBase可选绝对HTTP(S)目录,sourcebase不含query/fragment,非法TypeError。
resolveResource(key,DataContext)=>string/URL/Blob或Promise完整路径映射,已解析URL只过一次;错误拒绝资源/ready。
fetcher(Request)=>Promise<Response>,fetch文档/资源bytes;script/module有独立加载路径。
credentialsomit/same-origin/include,omitcore传输,凭据跨域需允许origin/credentials。
headersHeadersInit,无core请求头,不配置Core脚本/module。
maxBytes正safe integer,67108864每文档/bytes预算,非法RangeError,不是GPU总预算。
onListenerError(unknown)=>void,可选observer异常隔离。
themeportable/haneoka,portableHaneoka需对应plugins,缺contribution拒绝ready。
brandingCornertop-left/top-right,top-left半透明角落悬浮,画面全尺寸,无hide。
shellfalse或VegaPlayerShellOptionsportable默认false,Haneoka默认initialScreen:game。
pluginsreadonly VegaPlugin[],[]加到已有richtext/portable/Three/resources后;setup/dependency失败拒绝ready。
rendererThreeRendererPluginOptions,{}下表完整字段。
resourceTimeoutMs正safe integer,30000每资源总期限,非法RangeError,超时TimeoutError;不是整剧情期限。
signalAbortSignal,可选boot取消,ready后abort销毁。
onEvent(StoryEmbedEvent)=>void,可选boot前登记,事件同subscribe。
resolveLocalizedText(unknown)=>{text:string,lang?:string},Vega默认自定义文本resolver,保留返回shape;作者错误传递相应操作。

core继承参数的路径、预算、transport规则见core;mount没有autoplay/general loadTimeoutMs。整次期限用外部signal,音频从用户点击播放。

字段默认/类型行为
storage可选VegaSaveStorage,memoryread(key)=>Promise<string/null>、write(key,value)=>Promise<void>、remove(key)=>Promise<void>、list(prefix)=>Promise<readonly string[]>;宿主保存,坏save在相应load拒绝。
projectId可选stringexplicit trim→vegaProject.id→story.id→vega-story,save namespace。
settingsIdstring,projectIdpreferences独立key,已保存settings优先initialSettings。
initialSettingsPartial<VegaNarrativeSettings>下表默认;mount随后用外locale设置player/UI,初始语言用外层locale。
title可选stringexplicit→本地化project/story title/name→projectId。
initialScreentitle/gamegeneric默认title,Haneoka隐式shell为game。
onExitRequest()=>void/Promise<void>暂停回title后通知宿主退出,callback拒绝对应shell操作。
initialSettings类型/默认范围/作用
textSpeednumber/1.1–5 clamp。
autoDelaynumber/1.5秒,0–30。
masterVolumenumber/10–1。
bgmVolumenumber/.80–1。
voiceVolumenumber/10–1。
seVolumenumber/.90–1。
languagestring/auto内容语言,保存最多64字符。
uiLanguagestring/autoUI语言,与server独立。
reducedMotionboolean/false支持的UI减少动画。
highContrastboolean/falseUI对比偏好。
instantTextboolean/false文本即时完成。
webTextboolean/falseHaneoka Reading UI浏览器文字模式,见版本条件。
textSizenumber/1.5–2。
subtitlesEnabledboolean/true可用字幕。
bgmEnabledboolean/trueBGM开关。

数字非finite用fallback,boolean规范化;set未知key RangeError。webText已进入engine1916c57/theme1d5b20e/shell436ade8源码,需匹配构建或含这些版本的后续CDN receipt;若主动固定旧r22,请使用该固定版已有参数。匹配版本可设置shell.initialSettings.webText,或ready后story.player.shell.setSetting(“webText”,true)。

字段类型/默认作用/组合
rendererIdvega-three-webgl2/haneoka-three-webgl2,前者provider route。
profilevega/unity-adv,vegascene profile。
staticPortraitFallbackboolean/true模型provider缺失时static portrait。
colorGradingModeldr/hdr,ldrcolor pipeline。
contributionIdstring/haneoka.renderer-three注册identity。
contributionNamestring/Vega Three WebGL2显示名。
backend两个rendererID,vega-three-webgl2embed选择vega backend,不匹配拒绝ready。
postTextureResolver可选ThreePostTextureResolver不与postTextureAssets同时提供,否则TypeError;plugin持有可选dispose。
postTextureAssets可选Readonly<Record<string,string>>或ReadonlyMap<string,string>授权素材stablekey到URL,单独提供时建resolver。

自定义resolver完整字段:resolve(reference:unknown,usage)=>Three.Texture/null必填;preload([{reference,usage}],signal?)=>Promise<void>、dispose()可选。usage包括film-grain/color-lookup/lens-dirt,null沿pipelinefallback;Three实例应匹配,textures由provider回收。参数冲突同步TypeError,加载/provider/render失败经ready/events。

haneokaStorySource({id,apiBase?})的id必填;apiBase 默认 https://haneoka.org/api/v1/,并遵core目录校验。读取story/story-runtime/声明的Live2D metadata,server/locale来自mount;缺数据/坏record拒绝load,无账号/release字段。

cubismStoryPlugin({moduleUrl,runtime})需合法供应的module绝对URL与runtime.cubismCoreUrl;runtime.cubism2CoreUrl/motionSyncCoreUrl可选。module需export createCubismWebRuntimeAdapter,缺export或renderer能力拒绝prepare。按模型需要lazy加载,Core不打入SDK;adapter/Core应版本匹配。

haneokaStoryPlugins无参数,是theme/default-shell数组。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;缺值沿theme默认。HANEOKA_POST_TEXTURE_ASSETS为固定授权renderer映射,可放renderer.postTextureAssets。

AdvStory包含commands(mount要求)、可选runtime/localization及source扩展字段。localization有locales:string、defaultLocale:string、arrayOrder:string,作者定义语言;runtime为engine Partial<AdvRuntimeConfig>。command/resource字段由opcode/provider模型定义,示例使用公共Stage25/Talk2。

成员签名行为/错误
readyPromise<void>renderer/resources/fonts/seekindex。
snapshotreadonly objectphase/playing/paused/finished/seeking/commandIndex/commandCount/progress;phase为loading/ready/cancelled/error/disposed。
playerVegaPlayerHandle/undefinedid/player/lifetime/root/slots/shell?/dispose,高级访问;由embed统一销毁。
play/pause/next()=>void需ready否则Error;play异步错误经error事件,next请求下一句。
seek(ratio:number)=>Promise<number>finite0–1否则RangeError,实际commandIndex,保持play/pause。
subscribelistener=>unsubscribe未来事件,disposed后Error。
cancel()=>void仅boot,ready后无动作;retry先dispose。
dispose共用Promise<void>幂等,先engine后loader/URLs,移DOM/监听并等待boot;清理失败AggregateError。

事件完整为load{event:EmbedEvent}、state{snapshot}、seek{commandIndex}、error{error}。core字段按core表;state100ms+操作发布变更,observer异常隔离。取消保留signal.reason,source/resource/plugin保留其错误,没有额外第二套error code。

本页示例使用稳定 alias。显式固定旧的 r22 时,该版仍采用预留署名行的布局;固定版本应保持 engine、theme、shell 与 runtime 相互匹配。