我用魔珐星云 SDK 做了个虚拟汽车展厅:数字人能走、能说、能被打断
一个 SDK,让 AI 从"只会打字"进化到"会走路、会说话、有表情"的 3D 数字人。这篇文章带你从零看懂它的能力边界,并用一个虚拟展厅案例把它榨干。
为什么写这篇
你有没有这种感觉——现在大部分 AI 应用,交互还停留在"文字进、文字出"的阶段。哪怕加了语音,也只是多了一个 TTS 通道,AI 还是"坐在那儿"不会动。
但如果 AI 能站起来走两步、说到产品时手一挥弹出一张 3D 模型图、你随时能打断它提问——这个体验完全是另一个量级的。
魔珐星云(xingyun3d.com)的具身驱动 SDK 干的就是这件事:基于文本输入,实时生成语音+表情+动作,驱动 3D 数字人。我花了半天通读了它的全部开发文档,然后用一个虚拟汽车展厅导购案例把它的 10 项核心能力全跑了一遍。
这篇文章先讲清楚 SDK 能干什么,再带你看案例里最有技术含量的几个点。
一、这个 SDK 到底能干什么
先说结论,星云 SDK(Web 版,基于 JS + WebGL)提供的能力可以用一句话概括:
给它一段 SSML 文本,它就能让一个 3D 数字人把这段话说出来——口型、表情、肢体动作全部实时同步,还能边走边说、边说边弹多媒体卡片。
拆开来看,核心能力有这些:
1. 实时 3D 渲染 + 口型同步
底层是 WebGL 2.0 做 GPU 加速渲染,端上完成计算。TTS 生成的语音和数字人口型是帧级同步的,不是那种"嘴皮子乱动"的假同步。支持流式分段发送,适配 LLM 的流式输出和长文本场景。
2. 状态行为控制
数字人有 5 种行为状态:
| 状态 | 含义 |
|---|---|
| Idle | 空闲,静默待机动画 |
| Interactive Idle | 交互空闲,表示"我可以被打扰" |
| Listen | 聆听中 |
| Think | 思考中(适合接 LLM 的"正在生成"间隙) |
| Speak | 说话中 |
状态之间可以自由切换,这就够你搭一个完整的对话状态机了。
3. 行走动画
这个很有意思——数字人可以在场景里水平移动。你提前配置好"停靠点"(walk_points),然后在 SSML 里嵌一个 <ue4event type="walk"> 标签,数字人说到那个位置就会走向目标点。边走边说是支持的。
注意:行走属于定制化角色功能,需要角色制作时预置行走动画数据。
4. Widget 组件 + 自定义渲染
数字人播报时,可以在画面上叠加图片、字幕、视频等 UI 组件。关键是——这些组件的触发时机由 SSML 文本中的位置决定,说到哪儿弹哪儿,和语音天然同步。
而且 SDK 提供了 proxyWidget 机制,你可以完全接管渲染,不用它内置的丑字幕,自己画卡片。
5. 客户端打断
用户不想听了?传统方案要通知服务端停止,一来一回有延迟。星云支持端上打断——enableClientInterrupt: true + avatar.interrupt('user'),零延迟停嘴。
6. 网络韧性和 WebGL 自愈
弱网下自动重连(最多 27 次),断线期间播放缓存的 Idle 动画保证画面不中断。GPU 资源被系统回收导致 WebGL 上下文丢失?SDK 会自动重建。这两点对移动端和长时间运行场景很重要。
二、案例:虚拟汽车展厅 AI 导购
看完文档我就在想,什么场景能把上面这些能力全部串起来而且不违和?
答案是:虚拟展厅导购。
场景设计:一个数字人销售员站在展厅入口,用户点击"开始导览"后,数字人走向第一台车,到了之后开始流式讲解,讲解过程中自动弹出 3D 模型旋转、参数卡片、赛道视频等 Widget。用户随时可以打断提问"这台车多少钱"“续航多少”,数字人回答后继续讲解。走完 4 个站点结束。
这个场景用到的能力映射:
行走动画 → 站点间移动
流式语音 → 长讲解分段发送(is_start/is_end)
SSML uievent → 说到对应位置弹图片/3D模型/视频
proxyWidget → 完全自定义渲染所有多媒体卡片
客户端打断 → 用户随时插话
状态机 → IDLE→WALK→SPEAK→LISTEN→THINK→SPEAK 闭环
动态布局 → 行走时全身、讲解时中景
网络韧性 → 断线自动重连 + 离线 Idle 动画
WebGL恢复 → GPU 回收自动重建
错误码分级 → 6大类40+错误码处理
10 项能力,一个案例全覆盖。
下图是整个案例的分层架构,从上到下分三层:应用层(你写的业务逻辑)、SDK 能力层(星云提供的 10 项核心能力)、SDK 底层(WebGL 渲染 + TTS + 行走引擎 + 通信)。
界面长这样(三栏布局):
- 左栏:4 个展厅站点列表,当前站点高亮,行走时有动画指示
- 中间:数字人 3D 渲染区 + 右上角 Widget 卡片流 + 底部行走轨道指示 + 字幕条
- 右栏:对话区(可向导购员提问)+ 实时日志面板
三、技术含量最高的几个点
3.1 SSML 里的"事件触发"——语音和多媒体怎么同步的
这是我觉得最优雅的设计。看这段 SSML:
<speak>
现在我们在
<uievent>
<type>show_image</type>
<data>
<image>https://example.com/car.jpg</image>
<title>雷霆 EV · 全景</title>
</data>
</uievent>
雷霆EV的展位前。这台车搭载双电机……
</speak>
数字人说"现在我们在"的时候,图片卡片就弹出来了,然后继续说"雷霆EV的展位前"。
同步的精度不是靠定时器,是靠 SSML 文本中的物理位置。<uievent> 标签放在哪个词前面,事件就在哪个词被说出时触发。一条 SSML 可以嵌多个 <uievent>,依次触发。
下图直观展示了这个同步机制:左侧是 SSML 文本流(时间从上到下推移),
<uievent>标记夹在文本段落之间;右侧是 Widget 渲染区,说到<uievent>位置时对应卡片弹出。
支持的内置事件类型:
| type | 说明 |
|---|---|
show_image |
图片卡片 |
show_video |
视频播放 |
show_model3d |
3D 模型(.glb) |
show_text |
文本/参数卡片 |
show_link |
链接卡片 |
bgm_start |
背景音乐 |
你还可以通过 proxyWidget 完全接管渲染逻辑。我在案例里注册了全部 6 种 renderer:
const avatar = new XmovAvatar({
// ...
proxyWidget: {
show_image(data) { /* 自己画图片卡片 */ },
show_model3d(data) { /* 自己画3D模型占位 */ },
show_video(data) { /* 自己接 <video> */ },
show_text(data) { /* 自己画参数表格 */ },
show_link(data) { /* 自己画链接卡 */ },
bgm_start(data) { /* 自己放 BGM */ },
subtitle_on(data) { /* 自己显示字幕 */ },
subtitle_off() { /* 自己隐藏字幕 */ },
},
});
注册之后,SDK 内置渲染就不再生效了,所有 UI 都归你管。这意味着你可以保持自己产品的设计语言,不被 SDK 的默认样式绑架。
优先级规则:
onWidgetEvent>proxyWidget> SDK 内置渲染。如果你同时定义了onWidgetEvent,它会拦截所有事件,proxyWidget就不触发了。
3.2 行走动画——SSML 里塞一条"走过去"指令
行走功能的配置和触发分两步:
第一步:构造时配置停靠点(X 坐标):
const avatar = new XmovAvatar({
config: {
walk_config: {
walk_points: { A: 0, B: 213, C: 426, D: 640 },
init_point: 0,
},
},
});
第二步:SSML 里嵌入行走指令:
<speak>
请跟我来到下一个展位。
<ue4event>
<type>walk</type>
<data><target>B</target></data>
</ue4event>
这里是幻影GT运动轿跑。
</speak>
数字人说完"请跟我来到下一个展位",就会走向 B 点,边走边说"这里是幻影GT"。onWalkStateChange 回调会告诉你 'walk_start' 和 'walk_end'。
踩了两个坑记录一下:
changeWalkConfig()只能在非行走状态调用,行走中调不生效,得等走完再重试enableClientInterrupt = false时,播报过程中无法执行行走指令——这两个功能有依赖关系
3.3 流式语音——对接 LLM 的关键
speak() 的完整签名是:
avatar.speak(ssml, is_start?, is_end?, extra?)
is_start = true:这是本次说话的第一段is_end = true:这是本次说话的最后一段
为什么要分段?因为 TTS 生成有延迟。如果等整段 100 字的回答生成完再发给数字人,用户要干等好几秒。但如果你把它拆成 5 段,每段 20 字,第一段 TTS 完成数字人就能开口,后面的段在说话的同时继续生成——首字延迟从"等全文"降到"等第一段"。
这正好匹配 LLM 的流式输出:LLM 一边吐 token,你一边 speak(chunk, is_start, is_end) 往 SDK 里灌。
const chunks = chunkText(answer, 40); // 每段40字
chunks.forEach((chunk, i) => {
const isStart = i === 0;
const isEnd = i === chunks.length - 1;
avatar.speak(`<speak>${chunk}</speak>`, isStart, isEnd);
});
3.4 客户端打断——零延迟停嘴
传统打断要走一次网络往返:通知服务端"停" → 服务端停止推送音频流 → 客户端停止播放。这个过程有延迟,用户点完打断按钮数字人还会嘟囔半秒。
星云的端上打断直接在客户端切断:
const avatar = new XmovAvatar({
enableClientInterrupt: true, // 构造时开启
});
// 用户点打断
const latency = avatar.interrupt('user');
console.log('打断耗时:', latency, 'ms'); // 通常个位数ms
interrupt() 返回执行耗时(毫秒),type 参数用于日志区分来源('user' / 'user_speaking' / 'speak')。
3.5 错误码体系——6 大类 40+ 错误码
SDK 的错误码是分段设计的,通过 onMessage 回调统一上报:
| 范围 | 分类 | 典型码 |
|---|---|---|
| 10001-10004 | 初始化 | 容器找不到、WebSocket 连不上、会话创建失败 |
| 20001-20010 | 处理 | 视频帧提取失败、Worker 初始化失败(SDK 自动容错) |
| 30001-30007 | 资源 | 背景图加载失败、身体数据过期 |
| 40001-40007 | 解码 | 音频/视频/面数据解码失败 |
| 50001-50004 | 网络 | 断线→重连→恢复→重连耗尽 |
| 60001-60007 | WebGL | 上下文丢失→恢复超时→恢复成功→恢复失败 |
我在案例里按"致命/警告/通知"三级处理:
function handleSDKError(error) {
const { code, message } = error;
if (code >= 10000 && code < 20000) {
// 初始化错误 — 致命,提示用户
} else if (code >= 50000 && code < 60000) {
if (code === 50001) showOfflineTip(); // 断线
if (code === 50002) hideOfflineTip(); // 恢复
if (code === 50004) showRefreshDialog(); // 重连耗尽
} else if (code >= 60000 && code < 70000) {
if (code === 60001) showGPULoading(); // 上下文丢失
if (code === 60004) hideGPULoading(); // 恢复成功
if (code === 60006) showRefreshDialog(); // 恢复失败
}
}
值得说的是,处理类错误(20000s)和解码类错误(40000s)大部分 SDK 内部会自动容错——跳过坏帧、降级渲染——不需要你中断业务。但建议记日志用于排查。
四、状态机设计
案例里我自己搭了一个 6 态有限状态机来管理数字人行为:
IDLE ──► WALK ──► SPEAK ──► INTERACTIVE_IDLE ──► LISTEN ──► THINK ──► SPEAK
▲
│
(用户提问)
上行是导览循环(IDLE→WALK→SPEAK→INT_IDLE,绿色虚线回流到 WALK 表示"走向下一站"),下行是问答循环(INT_IDLE→LISTEN→THINK→SPEAK’→INT_IDLE,红色虚线)。两条循环共享
INTERACTIVE_IDLE枢纽节点。interrupt()可以在 SPEAK 状态直接打断回到 LISTEN。
转换前校验合法性,非法转换直接拒绝。为什么不用 SDK 内置的状态?因为 SDK 的 AvatarStatus(online/offline/visible/invisible/close)管的是连接和可见性,而行为状态(idle/listen/think/speak/walk)是应用层语义,需要自己管。
两者的关系是:SDK 状态是底层"在线/离线"层面的,应用状态机是业务层面的,二者正交。
五、上手只需要 5 分钟
文档的快速开始写得挺清楚,核心就 4 步:
Step 1:去 xingyun3d.com 应用中心创建驱动应用,选角色/音色/表演风格,拿到 App ID 和 App Secret。
Step 2:引入 SDK(一个 script 标签,无需构建工具):
<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>
Step 3:准备容器 + 初始化:
<div id="avatar-container" style="width: 360px; height: 640px;"></div>
<script>
const avatar = new XmovAvatar({
containerId: '#avatar-container',
appId: 'your-app-id',
appSecret: 'your-app-secret',
gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',
onMessage(error) { console.error(error.code, error.message); },
onStatusChange(status) { console.log('状态:', status); },
});
avatar.init({
onDownloadProgress(p) { console.log('加载:', p + '%'); },
}).then(() => {
setTimeout(() => avatar.speak('<speak>你好!</speak>'), 3000);
});
</script>
Step 4:页面卸载时销毁(很重要,否则会话泄漏):
window.addEventListener('beforeunload', () => {
avatar.destroy('page_unload');
});
环境要求:Chrome 94+ / Edge 94+ / Safari 15+ / Firefox 100+,需要 WebGL 2.0 支持。
六、环境支持
- 浏览器:Chrome 94+ / Edge 94+ / Safari 15+ / Firefox 100+(推荐 Chrome)
- WebGL:需要 2.0 支持
- 网络:WebSocket 长连接
- 芯片:x86 没问题;ARM 架构 RK3588 建议 1080P,RK3566 建议 720P
- 移动端:除了 Web SDK,还有 Android 原生 SDK
七、哪些场景适合用
聊几个我觉得有价值的落地方向:
1. 虚拟展厅/博物馆导览
数字人边走边讲,到展品前弹出 3D 模型和历史图片。行走 + Widget 同步是这个场景的刚需,目前市面上能做到"边走边说边弹卡片"的方案不多。
2. 电商直播/产品发布会
数字人主播介绍产品时弹出产品图、规格表、购买链接。show_link 事件可以直接引导下单。比真人主播成本低,24 小时不断播。
3. 智能客服/政务大厅
数字人站在虚拟大厅里,用户走近触发交互,提问后流式回答。打断功能在客服场景是刚需——用户不想听完一长段废话。
4. 教育培训
数字人教师边走边讲,配合 3D 模型展示零件结构、实验器材。show_model3d 支持 .glb 格式,可以接 Three.js 生态。
5. 对接 LLM 做对话数字人
这是最有想象力的方向。LLM 流式输出 → speak(chunk, is_start, is_end) 灌进去 → 数字人实时说话 + 口型同步 + 表情动作。把"只会打字的 ChatGPT"变成"会说话会动的人"。
八、我的体感
通读完文档 + 写完案例,说几点真实感受:
做得到位的:
- SSML 的事件触发机制设计得很优雅,语音和多媒体的同步精度是文本级而非定时器级,可靠性高
- 双模式(proxyWidget 完全接管 / SDK 内置渲染)给了足够的灵活性
- 错误码体系完整,6 大类覆盖了初始化到 WebGL 的全链路,对线上排查友好
- 自动重连 27 次 + WebGL 上下文自愈,移动端和长时间运行场景的韧性到位了
- 端上打断是真零延迟,体验明显好于服务端打断
需要注意的:
- 行走是定制化功能,不是所有角色都能走,得角色制作时预置动画数据
enableClientInterrupt和行走指令有依赖关系,关闭打断时播报中无法行走- ARM 芯片性能有上限,RK3566 建议 720P,别硬上 1080P
- SDK 通过
<script>引入,挂载到window.XmovAvatar,Vue/React 项目里记得在mounted/useEffect里初始化、在beforeDestroy/cleanup里销毁
整体评价: 如果你的项目需要"AI 有身体"——不只是文字交互,而是 3D 可视化、有动作、有表情、能在场景里移动——星云 SDK 目前是 Web 端最完整的方案之一。5 分钟跑通基础渲染,半天能做出一个像样的交互场景。
送书福利
所有本地跑通Demo的用户都可以参与送书活动,每人随机赠送一本AI相关技术书籍,全新包邮。
demo接入教程
魔珐星云数字人SDK接入教程
详细规则如下:
每天体验5分钟以上,连续两天调用SDK。
将注册手机号和收货地址填入下方问卷即可。
https://docs.qq.com/form/page/DRUZOaGx6Q2pHYXJV
更多推荐



所有评论(0)