跳转到内容

Home Spot 场景嵌入

@haneoka/embed-home-spot 在网页容器中加载本站或作者自带的 Three/Spine 场景,提供角色选择、重播与 PNG 导出。先用下面的实际示例,再接入自己的网页。

第一次加载:直接操作本页示例

Section titled “第一次加载:直接操作本页示例”
  1. 滚动到下面的实际加载并修改完整场景。
  2. 保持原创场景,点击加载。画面中会出现旋转的小蓝色图形。
  3. 点击画面下方的角色按钮,再点击取消选择恢复完整场景。
  4. 点击重播,再点击导出 PNG,最后点击下载 PNG保存 512×288 图片。
  5. 点击退出并销毁,关闭当前场景后再尝试其他输入方式。

运行模块在点击后加载,这条路径使用公开浏览器 ESM,无需安装包。

  1. 在同一示例中点击本站 ID。参数会变为 spotId: "40002"、server: "intl"。
  2. 点击加载,等待状态显示已就绪。此时角色按钮来自实际场景图层。
  3. 用这些按钮选择角色,再试重播、导出 PNG和退出并销毁。

接入自己的网页时,先提供容器,再在 module 脚本或应用中导入托管 ESM:

<div id="spot"></div>
import { mountHaneokaHomeSpot } from "https://haneoka.org/embed/home-spot.js";
const spot = mountHaneokaHomeSpot(document.querySelector("#spot"), {
spotId: "40002",
server: "intl",
locale: "zh-CN",
ariaLabel: "车库场景",
onEvent(event) { if (event.type === "error") console.error(event.error); },
});
await spot.ready;
const character = spot.document.layers.find(layer => layer.characterId > 0);
if (character) spot.update({ selectedCharacterId: character.characterId });
spot.replay();
const png = await spot.exportPng({ width: 512, height: 288 });
// 场景显示期间保留 spot;在页面或组件关闭时调用 await spot.dispose()。

这是接入代码片段;本页示例已经提供可直接操作的控件。适配器读取 current stories 文档并选择 homeSpots[spotId].spine,无需账号或 release。公开素材请求使用 no-referrer,保留 credentials、signal 与自定义 fetcher;手动组合低级 source 时也使用此公开资源策略。语言与 server 独立。

  1. 下载完整原创场景包,解压 ZIP,保留 home-spot 文件夹及子目录名称。
  2. 在下面的示例中点击本地文件。
  3. 在本地 scene JSON中选择 home-spot/scene.json。
  4. 在本地素材目录中选择整个解压后的 home-spot 文件夹,并确认浏览器的目录选择提示。
  5. 点击加载,画面应与第一次加载的小蓝色图形相同。
  6. 尝试选择、重播和 PNG 导出,再点击退出并销毁。
home-spot/
scene.json
pièce.glb
人物/
acteur.atlas
acteur.json
acteur.png

ZIP 打包了这五个示例文件,先解压,再把 JSON 和单独素材交给播放器。scene.json 引用 pièce.glb 与 人物/acteur.atlas,atlas 在自身目录中引用 acteur.png。保留重音符号和中文目录,才能匹配素材键。上面的两个文件输入分别选择 JSON 与完整素材目录。浏览器没有目录选择功能时,可以先用 HTTP JSON体验同一场景;接入自己的应用时,通过资源 Map 提供本地文件。

后文的包导入和 File 变量供已有打包应用接入时参考。浏览器可使用托管 Home Spot ESM和core ESM。npm 包仍待发布。源码构建应使用匹配的 embed/provider 版本、Three 0.184.x 和 Spine 4.2.119;Spine 使用其自身 runtime license,场景和美术保留作者许可。

自带 scene 包含 GLB 背景、背景矩阵、atlas、Spine layer 与相机。自包含 GLB 内嵌 image/buffer,atlas 页面和 skeleton 经 resolver 提供。

import { mountHomeSpot } from "@haneoka/embed-home-spot";
import { fileResourceResolver } from "@haneoka/embed-core";
const identity = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
const scene = {
supported: true,
backgroundScene: "room.glb",
backgroundTransform: identity,
atlas: "actors/actor.atlas",
scale: 0.01,
camera: {
position: [0,0,-5], startPosition: [0,0,-5], target: [0,0,0],
fieldOfView: 20, introDuration: 0,
},
layers: [{
key: "actor", runtime: { json: "actors/actor.json" },
animation: "idle", sortingOrder: 0, transform: identity,
}],
};
const resources = new Map([
["room.glb", roomGlbFile],
["actors/actor.atlas", atlasFile],
["actors/actor.json", skeletonFile],
["actors/actor.png", textureFile],
]);
const spot = mountHomeSpot(element, {
document: scene,
resolveResource: fileResourceResolver(resources),
locale: "fr",
ariaLabel: "Une scène originale",
});
await spot.ready;

File 变量由文件选择器或桌面宿主取得,key 支持 Unicode。JSON skeleton 使用 runtime.json,二进制使用 runtime.skel,与安装的 Spine 4.2 匹配;animation 名称必须存在于 skeleton。相机向量和 row-major layer transform 使用 provider 的 Unity 坐标,GLB backgroundTransform 为 column-major。场景围绕原点制作时可用 identity。layer 可加 characterId 和本地 hitPolygon,保留独立 sortingOrder。

import { httpDataSource } from "@haneoka/embed-core";
const spot = mountHomeSpot(element, {
source: httpDataSource({ url: "https://media.example/projects/cafe/scene.json" }),
ariaLabel: "Cafe",
});
await spot.ready;

room.glb 基于 scene.json 目录解析,actors/actor.atlas 的 actor.png 基于 atlas 目录解析。根路径使用资源 origin,绝对地址保留自身 origin。assetsBase 可以覆盖为另一个绝对目录,resolveResource 可返回 URL/string/Blob。HTTP/file/内存数据使用相同资源端口。

默认 cors/omit,每项 64 MiB,可设置 maxBytes、fetcher 与 credentials。跨域公开资源允许宿主 origin;cookie 资源需显式允许 origin 和凭据。HTTP 失败、scene 不可用、skeleton 不兼容与 CORS 拒绝会拒绝 ready 并产生 error。

mount 立即返回 handle,ready 指向最新 load:

const spot = mountHomeSpot(host, { source, signal: componentSignal });
spot.ready.catch(showLoadError);
// 加载途中也可以:await spot.dispose()。
await spot.ready;
const character = spot.document.layers.find(layer => layer.characterId > 0);
if (character) spot.update({ selectedCharacterId: character.characterId });
spot.replay();
await spot.load({ document: anotherScene });
await spot.dispose();

load 取消并清理上一代,返回本代 Promise。cancel 只取消当前加载代次;已完成场景保留,之后可用新 source/document 再 load。带 dispose 的 source 按单 load 所有权使用,每次创建新的 source。dispose 幂等、拆除 DOM/监听/场景/source,已销毁 handle 不能复用;清理失败以 aggregate error 拒绝。

update 选择或重播,不重建资源;resize 请求更新视口,provider 也观察容器。修改几何、相机或资源使用 load。

事件包括 loading、ready、cancelled、error、selection、contextlost、contextrestored、disposed;resource 事件包裹 core operation/key/loaded/total。subscribe 返回取消监听函数,onListenerError 隔离观察者异常。

exportPng 返回当前帧 PNG Blob,按 GPU 限制渲染指定尺寸后恢复互动视口。保持比例可保留构图;它不推进动画,调用方创建的预览 URL 自行 revoke。

组件从挂载开始预留 16:9 空间,可用 aspectRatio 指定其他比例。canvas 填满区域,clearColor 默认读取宿主 MD3 surface token。组件只拥有自己的子元素,页面的其他子节点由宿主保留。

Haneoka 原图标链接位于宿主边角,在游戏 canvas 和 PNG 之外。用已有 Material 按钮提供 replay、selection、export 与加载状态,本地化 ariaLabel 和按钮文字;键盘选择可通过宿主按钮调用 update。场景使用原 PNG,压缩纹理与 PMA 支持由 scene provider 和实际 descriptor 决定。

下面先加载微小原创场景:一个旋转Spine attachment、自包含GLB和小atlas。HTTP读取实际scene JSON;file读取所选JSON/素材目录,或把原创样本当真实Blob。本站ID使用实际公开便捷mount。选择角色、replay、替换load代次、resize、导出512×288 PNG均调用真实controller。

修改参数并点击加载后创建场景。原创数据只有一个简小场景;选择、重播与 PNG 导出使用实际场景控制器。

语言

正在准备示例…

查看实际示例代码

点击才import/create,同一应用重建先dispose。input/url为应用输入字段,options为SDK参数;exportURL由应用创建并在替换/退出revoke。callback/modules是实际函数/对象,见下表。五语顺序ja/en/繁/简/ko,作者locale可自由输入。

import { mountHaneokaHomeSpot } from "https://haneoka.org/embed/home-spot.js";
const scene = mountHaneokaHomeSpot(element, {spotId:"40002",server:"intl",locale:"en"});
await scene.ready;
const character = scene.document.layers.find(layer => layer.characterId > 0);
if (character) scene.update({selectedCharacterId:character.characterId});
scene.replay();
const png = await scene.exportPng({width:512,height:288});
await scene.dispose();

稳定alias随实际SDK部署更新,固定版本用manifest.modules;r22保留该版源码/布局。当前共用署名透明底、55%图标透明度角落悬浮,完整画面无额外row;PNG只含canvas。SDK产物部署从源码生成,npm独立发布。

host必填HTMLElement且有ownerDocument,否则TypeError。手动mount/load严格document或source二选一,否则TypeError。创建owned child与稳定比例,保留宿主其它子节点。

字段类型/默认作用/组合/错误
documentHomeSpotDocument,替代source必填下述完整scene,unsupported/不完整拒绝ready。
sourceDataSource<HomeSpotDocument>,替代document必填memory/http/file/custom,source失败传递。
serverstring/undefined,便捷intlregistry,与locale独立。
localestring/en作者/请求语言,不限五语建议值。
assetsBase可选绝对HTTP(S)目录/sourcebase不含query/fragment,非法TypeError。
resolveResource(key,DataContext)=>URL/string/Blob/Promise,原keyUnicode字面key,atlas图基于atlas目录;Map缺key Error。
fetcher(Request)=>Promise<Response>,fetchbytes传输,便捷公共mount加no-referrer且保留signal/headers/credentials。
credentialsomit/same-origin/include,omitcore请求,跨域服务允许origin/credentials。
headersHeadersInit/无共用头,source逐请求同名覆盖。
maxBytes正safe integer/67108864每JSON/bytes预算,非decoded/GPU总量;非法RangeError。
onListenerError(unknown)=>void,可选observer/ detachedcleanup错误。
signalAbortSignal,可选abort销毁,包括ready后;无内置整scene timeout。
ariaLabelstring/Home Spot作者语言canvas可访问名。
clearColor可选CSScolor宿主MD3surface→Canvas系统色;解析错误可拒绝ready,无可计算色Error。
aspectRatiofinite正数/16÷9加载及ready比例,非法RangeError;resize以宿主比率为准。
selectedCharacterId可选number,provider00清除,未匹配角色alpha.3,匹配/未选1。
modules可选runtimeModules默认lazy Three/GLTFLoader/Spine,构造能力缺失TypeError。
onEvent(HomeSpotEvent)=>void首load前登记。
spotId便捷mount必填string/numberString非空,空TypeError,不存在Error;非作者mount字段。
apiBase便捷mount/source可选string默认 https://haneoka.org/api/v1/,core目录校验。

便捷mount自动给source,不同时填document/source。读取current stories→homeSpots[id].spine,无账号/release。haneokaHomeSpotSource({spotId,apiBase?})同数据源但不创建renderer,手动组合时公开素材传输可用no-referrer。

字段类型/默认runtime要求/作用
supportedboolean,必须truefalse/省略拒绝provider。
atlasstring,runtime必填默认atlas,空/缺拒绝。
backgroundScenestring,runtime必填GLB,selfcontained内嵌buffer/images,缺/坏拒绝。
backgroundTransform16项number[]必填GLB column-major,错长度拒绝,使用finite。
animationstring/空layer fallback animation。
scalenumber/.01 fallbackSpine单位,runtime.scale优先,0用fallback。
fadeInDurationnumber/0秒,非负。
camera可选object下表完整。
layersreadonly layer[],非空skeleton/atlas图层,空拒绝。

source扩展字段保持,但不把任意字段当mount新开关。

字段默认含义
position[0,0,-1]Unity basecamera。
startPosition[0,0,-2]入口起点。
target[0,0,0]目标。
fieldOfView20度,建议finite正且<180;0fallback。
aspect16/9初始,resize按实际viewport替换。
orbitRatio0forward轴位移。
introDuration0秒,负clamp0。
introEase09 cubic-out,其它linear。
mouseFollowundefined下述pointer参数。
sensitivityundefinedsource metadata保留,当前math用max/threshold,不用此项。
threshold0角度deadzone,非负。
maxLeft0左yaw角。
maxRight0右yaw角。
maxUp0上pitch角。
maxDown0下pitch角。
smoothTime.1秒,最低.001。

vectors为3数,layer matrix为Unity row-major,backgroundTransform为GLB column-major。缺/坏vector项用provider数字fallback;原点作者场景可identity,游戏数据沿真实source矩阵。

字段类型/默认选择与作用
key必填string字面identity,Unicode可用。
characterIdnumber/0选择ID。
sortingOrdernumber/0递增painterorder,同值输入稳定顺序。
animationstring/descriptor.animationruntime.animation优先。
skeleton可选stringnormalization输入,新scene显式runtime.json/skel。
transform可选number[]Unity row-major,通常16finite数。
hitPolygonreadonly[x,y,z][]本地选择polygon。
formatunknown可选Spine adapter normalize。
runtimereadonly record可选使用下述资源keys。
runtime.jsonstring可选首 skeleton候选。
runtime.skelstring可选binary source/解析。
runtime.modelstring可选fallback source。
runtime.atlasstring/descriptor.atlaslayeroverride。
runtime.formatstring可选spine-binary,.skel后缀也选binary。
runtime.scalenumber/descriptor.scale→.01parser scale。
runtime.animationstring/layer→descriptoranimation名。

缺normalized skeleton TypeError。Spine4.2版本与atlasregion/page必须匹配,动画名应存在;HTTP/parse/texture/module拒绝ready。压缩/PMA沿provider数据,不引入新渲染规则。

three:object、GLTFLoader:constructor、spine:object。spine需TextureAtlas/ThreeJsTexture/AtlasAttachmentLoader/SkeletonBinary/SkeletonJson/SkeletonMesh;Three namespace提供WebGLRenderer/Color/Scene/PerspectiveCamera/Vector2/Vector3/Euler/Matrix4/MeshBasicMaterial等实际构造器。类型有意opaque,runtime校验,使用匹配Three/Spine4.2.119;import本身不创建WebGL,许可runtime由应用供应。

成员参数/结果行为/错误
ready最新Promise<void>随load改变,各load Promise只属本代。
stateloading/ready/cancelled/error/disposed实际phase。
elementHTMLElementownedwrapper。
canvasHTMLElement/undefinedready后。
documentdescriptor/undefined本代成功scene。
update{selectedCharacterId?,replay?}选择/replay不重建,显式undefined清除选择;disposed Error。
load{document}或{source}→Promise严格一个,错误TypeError,dispose前代scene/source并取消;source单代所有权。
replay()=>void需ready,否则Error。
resize()=>void需ready,另有ResizeObserver。
exportPng{width?,height?}→Promise<Blob>ready/context,默认drawingbuffer,缺一维按比率补。
subscribelistener→unsubscribe未来事件,disposed Error。
cancel()=>void仅loading,ready保留;新source重load。
dispose共用Promise<void>幂等,立即detach/abort,等待source,失败AggregateError。

PNG尺寸正safe integer,≤min(maxTextureSize,MAX_RENDERBUFFER_SIZE),总像素≤16777216,否则RangeError。捕获当前帧不推进时间,暂改target后恢复viewport/pixelratio/aspect;contextlost、2D/编码不可用Error。Blob不自动生成URL,preview/download URL由作者revoke,PNG不含宿主署名。

events完整为resource{event:EmbedEvent}、loading/ready/cancelled{generation}、error{generation,error}、selection{characterId}、contextlost/contextrestored/disposed。resource属loading generation;observer隔离。cancel为AbortError、external signal销毁、HTTP/decoder/provider错误保留原因,不复用已释放source。

应用字段sdkBase可指定兼容自托管core.js/home-spot.js的绝对HTTP(S)目录,默认官方/embed/,不是mount参数。构造/资源映射变更用加载按钮;替换仅把document/source传入已有handle。