从单向展示到实时交互:我用「魔珐星云」做了一个 OC 桌宠 Demo

从单向展示到实时交互:我用「魔珐星云」做了一个 OC 桌宠 Demo

  • 写在最前面
    • 什么是魔珐星云「具身驱动」?
    • [1. 在这个项目中你最终会得到什么?](#1. 在这个项目中你最终会得到什么?)
    • [2. 它不是播放器,而是 AI 屏幕的实时表达层](#2. 它不是播放器,而是 AI 屏幕的实时表达层)
    • [3. 环境准备](#3. 环境准备)
      • [3.1 安装工具](#3.1 安装工具)
      • [3.2 创建项目目录结构](#3.2 创建项目目录结构)
    • [4. 第一步:拿到 App ID / App Secret(魔珐星云后台)](#4. 第一步:拿到 App ID / App Secret(魔珐星云后台))
    • [5. 第二步 装依赖 & 启动脚本(package.json)](#5. 第二步 装依赖 & 启动脚本(package.json))
    • [6. 第三步:主进程 main.js(给 AI 表达层一个可驻留的终端容器)](#6. 第三步:主进程 main.js(给 AI 表达层一个可驻留的终端容器))
    • [7. 第四步:界面 index.html(构建 AI 屏幕的交互载体)](#7. 第四步:界面 index.html(构建 AI 屏幕的交互载体))
    • [8. 第五步:配置 config.js(角色设定 + 动作映射 + LLM )](#8. 第五步:配置 config.js(角色设定 + 动作映射 + LLM ))
    • [9. 第六步:染进程 renderer.js(把文本输出变成"可见的表达")](#9. 第六步:染进程 renderer.js(把文本输出变成“可见的表达”))
      • [9.1 初始化 SDK:XmovAvatar 怎么连上去?](#9.1 初始化 SDK:XmovAvatar 怎么连上去?)
      • [9.2 让数字人做动作:SSML 的正确姿势](#9.2 让数字人做动作:SSML 的正确姿势)
      • [9.3 为什么"聊天不会弹黑色窗"?](#9.3 为什么“聊天不会弹黑色窗”?)
      • [9.4 窗口拖拽移动怎么做的?](#9.4 窗口拖拽移动怎么做的?)
      • [9.5 面板不重叠的秘密:互斥 + 主进程改宽度](#9.5 面板不重叠的秘密:互斥 + 主进程改宽度)
    • [10. 第七步:查询 KA 动作池(把动作名填进 config.js)](#10. 第七步:查询 KA 动作池(把动作名填进 config.js))
      • [10.1 鉴权怎么计算?(X-TOKEN)](#10.1 鉴权怎么计算?(X-TOKEN))
      • [10.2 写 tools/query_ka.js](#10.2 写 tools/query_ka.js)
    • [11. 常见坑位排雷](#11. 常见坑位排雷)
      • [11.1 SDK 错误码 10003 是什么?](#11.1 SDK 错误码 10003 是什么?)
      • [11.2 为什么我用 IP 地址打开会报错?](#11.2 为什么我用 IP 地址打开会报错?)
      • [11.3 如何避免积分消耗过快?](#11.3 如何避免积分消耗过快?)
      • [11.4 我接大模型对话需要自己提供 Key 吗?](#11.4 我接大模型对话需要自己提供 Key 吗?)
    • [一键复盘:从 0 到 1 的正确开发顺序](#一键复盘:从 0 到 1 的正确开发顺序)
    • 结尾体验


🌌你好!这里是 晓雨的笔记本 在所有感兴趣的领域扩展知识,感谢你的陪伴与支持~ 👋 欢迎添加文末好友,不定期掉落福利资讯


写在最前面

版权声明:本文为青山雨原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。

数字人这件事,其实已经不再是"高配电脑才能玩"的重资产项目了。现在只要一台普通笔记本,8GB 内存即可流畅运行,无需高端显卡,也能把数字人稳定跑起来,做成一个常驻桌面的 OC 桌宠。

但真正让我觉得这件事有意思的,不只是"跑起来了",而是很多人以为自己做的是数字人,最后做出来的其实只是单向展示、无法实时互动的传统方案:形象能出现,内容能播,但很难真正承接 AI 的实时表达。真正难的,从来不是把角色摆到屏幕上,而是把 AI 输出继续往下走,变成语音、口型、表情、动作,以及可持续的互动反馈。

魔珐星云「具身驱动」 的价值,就在于它把这条原本很重、很碎的链路压缩得足够轻量:准备好容器、填好应用信息、完成初始化,然后就可以用相对简单的调用,让角色开口说话、带动作表现、实时响应互动。对开发者来说,这不仅意味着"低配电脑也能跑",也意味着数字人的开发门槛确实被进一步拉低了。传统数字人采用云端集中渲染方案,核心流程是由云端 GPU 完成画面生成,再将结果下发至终端呈现。这种方式强依赖网络、延迟高、无法实时打断,仅适用于固定讲解、宣传片等低交互场景,本质仍是单向展示工具,无法支撑连续自然的实时交互。

对于想快速做原型、验证玩法,或者把角色能力接进自己产品里的人来说,这种降低门槛不只是省设备、省成本,更重要的是省掉了大量底层链路的折腾时间。你可以先把它当成一个 OC 桌宠来做,但往大看,它已经是成熟的轻量化 AI 屏幕终端:让 AI 不只会回答,还能被真正地"看见"和"感知到"


什么是魔珐星云「具身驱动」?

它是一套让 "AI 有形象、会说话、能做动作" 的数字人驱动能力,核心作用是把文本输出实时转化为数字人的语音、口型、表情和动作,并在页面里实时呈现。

六大核心能力
  1. 高质量:生成逼真 3D 形象,实时输出自然生动的声音、表情与动作,赋予人物真实可信的表达力。
  2. 低延时:端到端响应约 500ms,交互实时流畅;支持随时打断,贴近真人对话体验。
  3. 低成本:仅需百元级芯片即可运行,大幅降低部署门槛,支持大规模普及。
  4. 高并发:支持千万级设备同时驱动,轻松应对批量化接入,保障体验稳定可靠。
  5. 多风格:覆盖超写实、二次元、卡通、美型等多样角色风格和人设,场景与角色可灵活选择。
  6. 多终端:全面适配手机、车机、Pad、PC、电视与大屏,兼容 Android、iOS、鸿蒙等主流系统。
维度 传统数字人 魔珐星云具身驱动
表达方式 预制内容播放、单向展示 文本实时驱动语音、表情、动作
工程位置 播放器 / 展示层 AI 屏幕表达层 / 具身底座
交互能力 弱交互、难接实时链路 可接 LLM、可接事件、可扩展工具
业务价值 看起来像"人" 真正进入服务终端场景
核心技术优势

依托文生 3D 多模态动作大模型AI 端渲和解算技术,魔珐星云打破了 3D 数字人生成领域的 "高质量、低成本、低延迟" 不可能三角,让高水准的数字人体验不再是昂贵且延迟的奢侈品。这一突破真正支持 AI 具身智能在各行业的大规模落地应用。

开发与场景适配

在开发侧,它更像 "拿来即用" 的角色表现层:只需准备容器、初始化 SDK、建立会话,就能让角色开口或做动作。还支持用 KA(技能动作)的语义指令触发预设动作表演,快速实现更丰富的互动。

适合打造桌宠、虚拟助手、导览员、客服形象、互动 NPC 等需要 "角色表现" 的场景。

官方链接: https://xingyun3d.com?utm_campaign=daren&utm_source=xiaoyu

  • **邀请码:**JH2A7JY55F
  • 通过邀请码注册的用户可以获得1000积分进行体验测试
AI辅助开发(使用Kimi Code)
  1. 直接投喂 :将 Xmov_Skill.md 文件直接拖入 AI 对话框。
  2. 指令跟随:拖入文件后,直接发送后续的构建指令(如"根据Xmov_Skill.md,完成一个段子制造机数字人。所有内容都在一个页面内,不要水平滚动。页面风格为极简风")。

Kimi辅助Code


下面让我们开始开发一个属于自己的OC桌宠吧~


1. 在这个项目中你最终会得到什么?

运行后,你会看到一个:

  • 透明背景、永远置顶、无边框的桌宠窗口
  • 左侧可以打开 聊天面板 / 待办工具面板
  • 点击人物会触发:语音 + KA 动作
  • 支持 "长时间不理自动说话" + "投喂/清洁/好感数值" + "待办列表"

桌宠展示


2. 它不是播放器,而是 AI 屏幕的实时表达层

魔珐星云具身驱动(JS SDK)核心就是把 AI 输出从"文字"升级为"3D 多模态 ":你给它文本,它会实时生成 语音、表情、动作,并驱动 3D 数字人渲染出来。

最常用的就两件事:

  1. 初始化 XmovAvatar(绑定容器、appId/appSecret、gatewayServer)
  2. 调用 speak(ssml, is_start, is_end) 让数字人说话,或用 SSML 触发 KA 动作

3. 环境准备

3.1 安装工具

  • Node.js:建议 18+
  • npm
  • Windows
  • CPU

3.2 创建项目目录结构

plain 复制代码
xingyun-oc-pet/
├─ package.json
├─ main.js          # Electron 主进程:透明置顶窗口、IPC
├─ index.html       # 页面:数字人容器 + UI
├─ renderer.js      # 渲染进程:SDK 初始化、交互、状态
├─ config.js        # OC 设定、动作映射、配置
└─ tools/query_ka.js# 查询 KA 动作池(可选)

4. 第一步:拿到 App ID / App Secret(魔珐星云后台)

需要去魔珐星云平台创建"驱动应用",选择角色、音色、表演风格,然后拿到:

  • App ID
  • App Secret

官方文档明确写了创建入口与初始化参数位置。

⚠️ 建议:不要把密钥硬编码进仓库(尤其公开仓库),下面教程将使用占位符。


5. 第二步 装依赖 & 启动脚本(package.json)

package.json 里保留两条脚本:一个启动 Electron,一个查询 KA。

plain 复制代码
{
  "scripts": {
    "start": "electron .",
    "query-ka": "node tools/query_ka.js"
  },
  "dependencies": {
    "axios": "^1.6.0",
    "crypto-js": "^4.2.0",
    "electron": "^28.0.0"
  }
}

安装依赖:

plain 复制代码
npm i

启动桌宠:

plain 复制代码
npm start

6. 第三步:主进程 main.js(给 AI 表达层一个可驻留的终端容器)

main.js 的职责只有一个:创建一个透明置顶窗口,并提供 IPC:

  • renderer 要拖拽移动窗口:pet:getBounds / pet:setPos
  • renderer 打开面板时,主进程让窗口向左变宽:pet:setLayout
  • 退出:pet:quit

下面是 main.js代码:

plain 复制代码
const { app, BrowserWindow, screen, ipcMain, globalShortcut } = require("electron");
const http = require("http");
const fs = require("fs");
const path = require("path");

app.commandLine.appendSwitch("ignore-gpu-blocklist");
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
app.commandLine.appendSwitch("disable-features", "OutOfBlinkCors");

let server = null;
let win = null;

// ---- 窗口布局参数(可按需微调)----
const BASE_W = 320;     // 桌宠基础宽度(人物+底栏)
const BASE_H = 480;
const PANEL_W = 300;    // 侧栏宽度(聊天/工具)
const MARGIN = 20;

let clickThrough = false; // 默认不穿透(因为要操作输入框/菜单)

function startStaticServer(rootDir, port = 0) {
  return new Promise((resolve) => {
    server = http.createServer((req, res) => {
      try {
        let urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
        if (urlPath === "/") urlPath = "/index.html";
        const filePath = path.join(rootDir, urlPath);

        if (!filePath.startsWith(rootDir)) {
          res.writeHead(403);
          return res.end("Forbidden");
        }

        fs.readFile(filePath, (err, data) => {
          if (err) {
            res.writeHead(404);
            return res.end("Not found");
          }
          const ext = path.extname(filePath).toLowerCase();
          const mime = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css" }[ext] || "application/octet-stream";
          res.writeHead(200, { "Content-Type": mime });
          res.end(data);
        });
      } catch (e) {
        res.writeHead(500);
        res.end("Error");
      }
    });

    server.listen(port, "127.0.0.1", () => resolve(server.address().port));
  });
}

function applyClickThrough(enabled) {
  clickThrough = !!enabled;
  if (!win) return;
  win.setIgnoreMouseEvents(clickThrough);
  win.webContents.send("pet:clickThroughChanged", clickThrough);
}

// 保持右下角锚点不变:窗口变宽时"向左长"
function setWindowLayout({ toolsOpen = false, chatOpen = false }) {
  if (!win) return;
  const b = win.getBounds();
  const right = b.x + b.width;
  const bottom = b.y + b.height;

  const newW = BASE_W + (toolsOpen ? PANEL_W : 0) + (chatOpen ? PANEL_W : 0);
  const newH = BASE_H;

  win.setBounds(
    { x: Math.round(right - newW), y: Math.round(bottom - newH), width: newW, height: newH },
    false
  );
}

async function createWindow() {
  const primaryDisplay = screen.getPrimaryDisplay();
  const { width, height } = primaryDisplay.workAreaSize;

  const rootDir = __dirname;
  const port = await startStaticServer(rootDir);

  win = new BrowserWindow({
    width: BASE_W,
    height: BASE_H,
    x: width - BASE_W - MARGIN,
    y: height - BASE_H - MARGIN,
    transparent: true,
    frame: false,
    resizable: false,
    alwaysOnTop: true,
    skipTaskbar: true,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      webSecurity: false,
      backgroundThrottling: false,
    },
  });

  await win.loadURL(`http://127.0.0.1:${port}/index.html`);

  applyClickThrough(false);

  // 可选:隐藏热键切穿透
  globalShortcut.register("Control+Alt+P", () => {
    applyClickThrough(!clickThrough);
  });
}

// -------- IPC --------
ipcMain.handle("pet:getBounds", () => {
  if (!win) return { x: 0, y: 0, width: 0, height: 0 };
  return win.getBounds();
});

ipcMain.on("pet:setPos", (_e, pos) => {
  if (!win || !pos) return;
  win.setPosition(Math.round(pos.x), Math.round(pos.y), false);
});

ipcMain.on("pet:quit", () => app.quit());

ipcMain.on("pet:setClickThrough", (_e, enabled) => applyClickThrough(!!enabled));

ipcMain.on("pet:setLayout", (_e, layout) => setWindowLayout(layout || {}));

// ------------------------------------------------------------
app.whenReady().then(createWindow);

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") app.quit();
});

app.on("will-quit", () => {
  try { globalShortcut.unregisterAll(); } catch {}
  try { server?.close?.(); } catch {}
});

7. 第四步:界面 index.html(构建 AI 屏幕的交互载体)

index.html 负责将页面布局分为两个部分,分别是"右侧桌宠区固定 + 左侧面板区扩展",具体如下:

  • #base 固定在右侧,宽度 --base-w
  • #panels 在左侧,打开聊天/工具时显示 .side
  • 数字人的容器是 #sdk
  • SDK 用官方脚本引入:xmovAvatar@latest.js
plain 复制代码
<div id="sdk"></div>
<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>
<script src="./renderer.js"></script>

✅ 小提醒:官方 SDK 文档提过,某些能力只支持 localhosthttps,直接用 IP/其他域名可能报错。 在 main.js 里可以启动本地静态服务 127.0.0.1,为了更加稳定。


8. 第五步:配置 config.js(角色设定 + 动作映射 + LLM )

config.js 里分为 4 个模块:

  1. 具身驱动密钥appId/appSecret(从平台获取)
  2. 人设:名字、性格、口头禅、背景、NG 话题
  3. 动作映射 actions :填写对应的 action_semantic
  4. LLM(可选) :如果你要接 DeepSeek / OpenAI 兼容接口,就填 endpoint/apiKey/model

⚠️ 重要:星云 SDK 不会自动给你"大模型 key"。官方 demo 也是让你自己填"大模型 key"。 所以:LLM 这块完全是"你自己选供应商 + 你自己出 key"。

下面是我这边设计的一套提示词,大家可以根据自己的OC设定,设计提示词:

plain 复制代码
const OC_CONFIG = {
  // 具身驱动密钥(从星云后台拿)
  appId: "YOUR_APP_ID",
  appSecret: "YOUR_APP_SECRET",

  // 角色设定
  name: "莱缇娅",
  personality: "内心敏感细腻、轻微社牛",
  catchphrases: [
    "数据不会说谎,但我会哦~",
    "哎哎哎等一下!这个 bug 我好像见过!",
    "别担心,有本小姐在,再烂的代码也能给你救回来!"
  ],
  backstory: "(略)",
  ngTopics: [
    "禁止触碰她的兔子挂饰",
    "禁止在她面前提"数据清零"",
    "禁止未经允许进入她工作室的"黑箱区"",
    "禁止嘲笑她的双丸子头"
  ],

  ui: {
    avatarScale: 0.50,
    randomIntervalSec: 14,
    idleSpeakAfterSec: 60
  },

  // 动作映射(用 query_ka.js 查到真实 action_semantic 再填)
  actions: {
    idle: ["Standing_Idle_01", "Look_Around"],
    feed: "Happy_Jump",
    clean: "Shy_Hide",
    greet: "Wave_Hello",
    click: ["Wave_Hello", "Happy_Jump", "Look_Around"]
  },

  randomLines: [
    "数据流有点乱...让我捋一捋...",
    "秩序区的全息投影又抽风了?别慌,我来!"
  ],

  tools: {
    weather: { location: "Los Angeles" }
  },

  // 接 deepseek 兼容接口
  llm: {
    enabled: false,
    endpoint: "https://api.deepseek.com/chat/completions",
    apiKey: "YOUR_LLM_KEY",
    model: "deepseek-chat"
  }
};

9. 第六步:染进程 renderer.js(把文本输出变成"可见的表达")

9.1 初始化 SDK:XmovAvatar 怎么连上去?

SDK 文档的标准初始化长这样:传 containerId/appId/appSecret/gatewayServer,并且可以接各种回调(widget、message、stateChange)。

initAvatar() 里需要做 3 个关键优化:

  1. 先检查 WebGL:没有 WebGL 就不渲染(否则会黑屏/报错)
  2. proxyWidget 吞掉 subtitle_on/off:让 SDK 不要弹默认字幕窗,只保留语音+动作
  3. onStateChange("idle") 作为"登场时机":一进入 idle 就播一句语音,然后启动"长时间不理自动说话"

官方文档也说明了:speak(ssml, is_start, is_end) 支持直接文本或 SSML。

9.2 让数字人做动作:SSML 的正确姿势

官方文档给了 "技能 KA 指令" 的 SSML 结构:在 <speak> 里塞 <ue4event><type>ka</type>...<action_semantic>...</action_semantic>

buildSSML() 里就是按这个结构拼的:

  • 纯动作:buildSSML("", "Happy_Jump")
  • 动作 + 说话:buildSSML("谢谢投喂~", "Happy_Jump")

9.3 为什么"聊天不会弹黑色窗"?

因为把 SDK 的默认字幕事件代理掉了:

  • proxyWidget.subtitle_on/off = () => {}
  • onWidgetEvent 再兜底,把包含 subtitle 的事件直接 return

这跟官方文档里 "proxyWidget 可以重写默认行为" 的说法一致。

9.4 窗口拖拽移动怎么做的?

用的是一个很实用的思路:

  • renderer 监听 mousedown/mousemove/mouseup
  • mousedown 时用 ipcRenderer.invoke("pet:getBounds") 拿到窗口当前坐标
  • mousemove 时计算新坐标,用 ipcRenderer.send("pet:setPos") 移动窗口

并且加了"排除区域":

  • 点到底部栏、工具面板、聊天面板、信息面板 → 不触发拖拽(否则输入框根本点不了)

9.5 面板不重叠的秘密:互斥 + 主进程改宽度

在"待办事件"和"聊天界面"中, toggleTools() / toggleChat() 做了互斥:开一个就关另一个。

然后 applyBodyFlags() 会通知主进程:

plain 复制代码
ipcRenderer.send("pet:setLayout", { toolsOpen, chatOpen });

主进程根据开关计算新宽度,并保持右下角锚点不动。


10. 第七步:查询 KA 动作池(把动作名填进 config.js)

现在的 actions 里像 Wave_Hello / Happy_Jump 这些,需要以 真实的 action_semantic 为准。

官方给了 KA 查询接口:

  • host:https://nebula-agent.xingyun3d.com
  • path:GET /user/v1/external/lite_ka_summary
  • 需要三段鉴权头:X-APP-ID / X-TIMESTAMP / X-TOKEN

10.1 鉴权怎么计算?(X-TOKEN)

文档说 X-TOKEN 的算法是:

plain 复制代码
md5(lower_api_path + lower_method + sort_json_str + secret + timestamp)

其中:

  • api_path 不包含 host,要小写
  • method 小写
  • data 用"key 排序后"的 JSON 字符串(GET 查询时一般是 {}
  • timestamp 是秒级时间戳(60 秒有效)

10.2 写 tools/query_ka.js

创建 tools/query_ka.js

plain 复制代码
const axios = require("axios");
const CryptoJS = require("crypto-js");

const HOST = "https://nebula-agent.xingyun3d.com";
const API_PATH = "/user/v1/external/lite_ka_summary"; // 不带 host

const { appId, appSecret } = require("../config.js"); // 如果你 config.js 没有 module.exports,就改成手填

function stableJsonStringify(obj) {
  if (!obj || typeof obj !== "object") return "{}";
  const keys = Object.keys(obj).sort();
  const sorted = {};
  for (const k of keys) sorted[k] = obj[k];
  return JSON.stringify(sorted).replace(/\s+/g, "");
}

function md5Hex(str) {
  return CryptoJS.MD5(str).toString();
}

function buildHeaders({ ak, secret, method, apiPath, data }) {
  const t = Math.floor(Date.now() / 1000);
  const lowerPath = apiPath.toLowerCase();
  const lowerMethod = method.toLowerCase();
  const sortJsonStr = stableJsonStringify(data || {});

  const signSrc = `${lowerPath}${lowerMethod}${sortJsonStr}${secret}${t}`;
  const token = md5Hex(signSrc);

  return {
    "X-APP-ID": ak,
    "X-TIMESTAMP": String(t),
    "X-TOKEN": token
  };
}

async function main() {
  if (!appId || !appSecret || appId.includes("YOUR_")) {
    console.error("请先在 config.js 里填 appId/appSecret(或改成环境变量读取)");
    process.exit(1);
  }

  const method = "GET";
  const data = {}; 
  const headers = buildHeaders({
    ak: appId,
    secret: appSecret,
    method,
    apiPath: API_PATH,
    data
  });

  const url = `${HOST}${API_PATH}`;
  const resp = await axios.request({
    url,
    method,
    headers,
    data
  });

  console.log("KA summary:");
  console.log(JSON.stringify(resp.data, null, 2));
}

main().catch((e) => {
  console.error("query-ka failed:", e?.response?.data || e.message);
  process.exit(1);
});

上面逻辑就是把文档里的鉴权步骤用 Node 写了一遍。

运行:

plain 复制代码
npm run query-ka

拿到结果后,去 config.js -> actions 把需要的动作语义填进去即可。


11. 常见坑位排雷

11.1 SDK 错误码 10003 是什么?

文档错误码里写了 START_SESSION_ERROR = 10003,含义是会话启动(start_session)异常。

通常排查顺序:

  1. appId/appSecret 是否填对(最常见)
  2. 网络能否访问 nebula-agent.xingyun3d.com
  3. 是否用 localhost/127.0.0.1(文档说某些能力不支持 IP/非本机域名)
  4. 容器是否存在(10001 容器不存在)

11.2 为什么我用 IP 地址打开会报错?

文档直接写了:SDK 中某些方法仅支持 localhosthttps。 用 Electron 内置本地服务器跑 127.0.0.1,这个问题基本就解决了。

11.3 如何避免积分消耗过快?

文档 FAQ 提到:调试时用基础音色、长时间无互动可切离线模式等。

当前的桌宠做法也比较"省"------没有持续字幕渲染、人物缩放也比较小。

11.4 我接大模型对话需要自己提供 Key 吗?

需要。官方 Demo 的接入说明里也明确了:大模型与 ASR 的连接参数需要自行配置。


一键复盘:从 0 到 1 的正确开发顺序

GitHub传送门:https://github.com/qingshanyu9/XingyunOCPet

  1. 创建项目目录 + package.json(先能 npm start
  2. main.js:先让透明置顶窗口跑起来
  3. index.html:先把 #sdk 容器和 UI 布局摆好
  4. 引入 SDK 脚本 + 写 renderer.jsinitAvatar()(先让人出现)
  5. speak() 跑通语音(再加 SSML 动作)
  6. 再做:拖拽、菜单、面板互斥、聊天、待办
  7. 最后补:query_ka.js 查动作池,完善 actions 映射

结尾体验

实际做下来,最直观的体验就是两点:硬件要求低开发节奏快 。 角色的接入非常简单:页面放一个容器、填好 App ID / App Secret,初始化实例后直接调用 <font style="background-color:rgb(255,245,235);">speak(...)</font> 就能驱动数字人开口说话;想让角色配合动作,用 SSML 加上 KA 指令即可。

在这种低配(无独显、纯 CPU) 的笔记本上也能跑起来,桌宠这种形态从"窗口悬浮、拖拽移动"到"能说会动",核心代码量不大,就几百行,调通链路也不绕。再加上 OC 的设定也很好做:把名字、性格、口头禅、随机台词,以及不同交互对应的动作语义集中写在一个配置文件里,基本不用改主逻辑,就能快速换一个完全不同的角色气质,甚至可以做多套设定一键切换。

这件事最让我有感触的一点是:数字人门槛确实在降低。以前大家一提到数字人,第一反应往往是"贵""重""要高配设备""要很复杂的渲染链路",但真正上手之后会发现,至少在这种桌面原型和轻量交互场景里,它已经没有想象中那么遥远了。

更进一步说,这次做出来的虽然是一个 OC 桌宠,但它本质上不只是"桌面上多了一个会说话的角色"。同样的初始化方式、语音驱动、动作触发和交互逻辑,稍微改一下形态,就可以继续往虚拟助手、导览员、客服形象、互动 NPC 甚至各类 AI 服务终端上延展。也就是说,它表面上是一个小 Demo,底层验证的其实是另一件事:很多原本只能展示内容的屏幕,正在被升级成一个可交互、可表达、可持续在线的 AI 入口

所以最终成品看起来像是"把一个能互动的数字人,塞进桌面角落里随时待命",但如果把视角再放大一点,它验证的并不只是"桌宠能不能做",而是今天的数字人是不是正在从一个高门槛的展示物,逐渐变成一个更轻、更快、更容易落地的交互组件。


hello,这里是 晓雨的笔记本。如果你喜欢我的文章,欢迎三连给我鼓励和支持:👍点赞 📁 关注 💬评论,我会给大家带来更多有用有趣的文章。

原文链接 👉 ,⚡️更新更及时。

欢迎大家点开下面名片,添加好友交流。

相关推荐
前端若水2 小时前
动画与交互 —— 过渡、关键帧、滚动驱动、视图过渡
交互
UXbot3 小时前
AI应用原型平台核心能力:界面自动生成、交互流程编辑、多格式代码导出详解
前端·低代码·交互·软件构建·原型模式·web app
AIminminHu20 小时前
(让 C++ 程序长出大脑:从“语音遥控器”到具身智能 Agent 的进化之路)------OpenGL渲染与几何内核那点事------(二-1-(15))
开发语言·c++·agent·具身智能
CyanMind20 小时前
GMR 工程实践笔记:把自己的机器人接入动作重定向流程
机器人·具身智能·模仿学习·动作重定向
cy_cy00221 小时前
解析活跃氛围的互动屏幕应用
大数据·科技·人机交互·交互·软件构建
byte轻骑兵21 小时前
【HID】规范精讲[11]: 蓝牙HID设备信号交互流程深度拆解
人工智能·交互·hid·蓝牙键盘·蓝牙鼠标
深蓝学院1 天前
Science Robotics重磅 | 如何用运动学智能,实现跨机器人技能复用?
机器人·具身智能
AGV算法笔记1 天前
【具身智能研究进展】RoboBrain 2.5:让机器人真正理解“空间”和“时间”的大脑模型
算法·3d·机器人·具身智能·感知算法