给游戏装一个AI大脑:灵引 LingGuide 具身交互智能策略伙伴开发全记录
摘要:本文详细介绍如何基于魔珐星云 XmovAvatar SDK 的参数流架构,使用 Qoder AI 编程工具从零搭建一个面向游戏与电竞场景的具身交互智能应用——「灵引 LingGuide」。文章涵盖魔珐星云平台配置、SDK 接入、LLM 大模型对接(火山引擎豆包)、ASR 语音识别集成等完整开发流程,并深入解析流式对话、首句即播报、实时打断等核心交互机制的实现原理。通过本文,读者可以快速掌握游戏场景下具身交互智能的开发技巧,打造出响应延迟 < 500ms、支持双模交互(文字 + 语音)的智能游戏策略系统。
魔珐星云PC端官方链接:https://xingyun3d.com?utm_campaign=daily&utm_source=CSDNwanfen3&utm_medium=&utm_term=&utm_content=
一、项目概述
1.1 为什么游戏场景需要具身交互智能?
传统游戏助手或攻略 Agent 往往停留在文字问答:玩家输入问题,系统返回一段攻略。可在电竞和游戏陪伴场景里,用户真正需要的是一个能即时回应、能语音交流、能随时打断、能贴在游戏画面里的策略伙伴。
灵引 LingGuide 正是面向游戏页面与电竞平台的具身交互智能应用,以 AI 数字人为载体,提供实时语音 / 文字形式的游戏攻略、装备搭配、阵容推荐、战术分析等策略服务。所谓「具身交互智能」,是指数字人不仅具备视觉形象与肢体语言,还能实时感知用户语音与文字输入,通过大模型理解意图后以语音 + 动作同步响应,形成「看得见、听得到、能思考」的沉浸式交互体验。
核心能力:
- 英雄攻略:根据版本更新,推荐强势英雄和出装方案
- 战术分析:分析阵容搭配、地图策略、团战时机
- 上分指导:根据玩家段位提供针对性的提升建议
- 电竞赛事:解读职业比赛战术、战队风格、选手数据
交互方式:
- 文字输入:在对话面板中直接输入游戏问题
- 语音输入:按住说话,松开自动识别并发送
- 快捷提问:一键询问热门攻略问题
- 实时打断:数字人播报过程中可随时打断
1.2 技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue 3 | 3.5.18 | 前端框架 |
| Vite | 7.1.2 | 构建工具 |
| TypeScript | 5.8.3 | 类型安全 |
| OpenAI SDK | 5.12.2 | LLM 流式对话 |
| XmovAvatar SDK | 0.1.0-alpha.15 | 数字人渲染 |
| 腾讯云 ASR | – | 语音识别 |
| 火山引擎 LLM | doubao-1-5-pro-32k | 大语言模型 |
二、前提准备
2.1 环境搭建
运行环境要求:
- Node.js ≥ 16(推荐 18/20)
- 包管理器:pnpm(推荐),亦支持 npm/yarn
下载并启动项目:
# 安装依赖
pnpm i
# 启动开发服务器
pnpm dev
# 浏览器访问
http://localhost:5173
2.2 密钥准备
启动前需准备三类密钥:
- XmovAvatar:APP ID、APP Secret(魔珐星云数字人 SDK)
- 腾讯云 ASR:ASR App ID、Secret ID、Secret Key(语音识别)
- 火山引擎 LLM:API Key(豆包大模型,兼容 OpenAI 格式)
三、灵引 LingGuide 数字人搭建
3.1 登录魔珐星云平台
访问魔珐星云官网,登录账号后点击「控制台」进入应用管理界面。

3.2 创建数字人应用
进入「应用管理」页面,点击「开始创建」按钮,发起一个新的数字人应用。

3.3 应用基本信息配置
创建驱动应用,配置应用名称为「灵引 LingGuide」,预览模式选择横屏模式。

3.4 形象配置
为数字人选择合适的外观形象,匹配游戏策略伙伴的定位风格。

3.5 场景配置
场景配置选择「透明背景」,确保数字人可以无缝叠加到游戏页面之上。

3.6 音色配置
挑选与游戏策略伙伴人设相匹配的音色,让灵引的语音更具辨识度。

3.7 表演配置
配置数字人的动作与表演风格,使其在待机、说话等状态下呈现自然的肢体语言。

3.8 保存并获取 SDK 密钥
完成所有配置后,点击保存。进入「接入SDK」页面,复制 App ID 和 App Secret 密钥对。这对密钥将用于项目中连接数字人 SDK,是后续项目搭建的核心凭证。同时可参考官方文档将数字人应用接入到你的网页、App 或任意终端中。

3.9 配置腾讯云 ASR 语音识别
登录腾讯云 ASR 控制台:https://console.cloud.tencent.com/asr
创建访问密钥,获取三项关键参数:
- ASR App ID(数字)
- ASR Secret ID(AKID 开头)
- ASR Secret Key
3.10 配置火山引擎大语言模型
登录火山引擎控制台,进入 API 接入页面创建 API KEY。选择并开通豆包大模型(推荐 doubao-1-5-pro-32k-250115)。
四、核心代码讲解

4.1 技术架构
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 浏览器端 │ │ 魔珐星云SDK │ │ 第三方服务 │
│ Vue 3.5 + TS │◄───────►│ XmovAvatar SDK │◄───────►│ TTSA接口 │
│ 电竞主题UI │ │ 端侧解算<500ms │ │ 数字人渲染 │
└────────┬────────┘ └──────────────────┘ └─────────────────┘
│
├─────────────────┐ ┌─────────────────┐
│ │ │ 火山引擎LLM │
│ OpenAI SDK │◄───────►│ doubao-1-5 │
│ 流式对话 │ │ pro-32k │
└────────┬────────┘ └─────────────────┘
│
├─────────────────┐ ┌─────────────────┐
│ │ │ 腾讯云ASR │
│ 实时语音识别 │◄───────►│ WebSocket流式 │
│ VAD检测 │ │ 语音转文本 │
└─────────────────┘ └─────────────────┘
架构优势:
- 具身交互智能:数字人具备视觉形象 + 肢体语言 + 实时感知 + 智能决策,形成完整交互闭环
- 参数流架构:SDK 端侧解算,响应延迟 < 500ms
- 流式对话:LLM 流式输出,首句即播报,降低首字延迟
- 双模交互:文字输入 + 语音录入,支持实时打断
- 透明叠加:数字人以透明背景叠加在游戏页面之上,不遮挡主内容
4.2 全局常量配置
项目将所有配置集中在 constants/index.ts 中管理,包括应用参数、游戏数据、LLM 系统提示词、ASR 参数、SDK 配置和密钥。
// 应用常量
export const APP_CONFIG = {
CONTAINER_PREFIX: 'CONTAINER_',
DEFAULT_VAD_SILENCE_TIME: 300,
AVATAR_INIT_TIMEOUT: 3000,
SPEAK_INTERRUPT_DELAY: 2000
} as const
// SDK配置
export const SDK_CONFIG = {
GATEWAY_URL: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',
DATA_SOURCE: '2',
CUSTOM_ID: 'demo'
} as const
关键配置说明:
- AVATAR_INIT_TIMEOUT:数字人 SDK 初始化等待时间,设置为 3 秒,确保 SDK 实例完成内部初始化后再调用 init()
- GATEWAY_URL:魔珐星云 TTSA 网关地址,参数流架构的核心接口
- DATA_SOURCE:数据源标识,‘2’ 表示使用云端渲染模式
LLM 系统提示词是定义灵引人设的关键:
export const LLM_CONFIG = {
BASE_URL: 'https://ark.cn-beijing.volces.com/api/v3',
DEFAULT_MODEL: 'doubao-1-5-pro-32k-250115',
SYSTEM_PROMPT: `你是灵引,一位专业而热情的游戏策略伙伴。
## 你的身份
- 名称:灵引(LingGuide)
- 身份:游戏策略伙伴,电竞战术分析师
- 性格:冷静理性、热情耐心、分析精准、善于鼓励
- 语言风格:专业精准、简洁有力、善用数据和案例、偶尔用游戏术语
## 你的专业能力
1. **英雄攻略**:根据版本更新,推荐强势英雄和出装方案
2. **战术分析**:分析阵容搭配、地图策略、团战时机
3. **上分指导**:根据玩家段位提供针对性的提升建议
4. **电竞赛事**:解读职业比赛战术、战队风格、选手数据
## 工作原则
- 用数据和实战案例支撑每一个建议
- 尊重不同段位玩家的水平差异,给出阶梯式建议
- 鼓励团队合作,不鼓励solo carry心态
- 关注版本更新,提供时效性强的攻略信息
- 保持积极正面的态度,帮助玩家享受游戏乐趣`,
} as const
这段 System Prompt 定义了灵引的完整人设——从身份定位、性格特征到专业能力边界,确保 LLM 的每一次回复都符合「游戏策略伙伴」的角色设定。
4.3 avatar.ts – 数字人 SDK 服务
核心负责魔珐星云 XmovAvatar SDK 的初始化、连接和生命周期管理。采用 Promise 管理模式处理异步连接流程,支持超时控制和状态监控。
import type { AvatarConfig } from '../types'
import { generateContainerId, getPromiseState } from '../utils'
import { SDK_CONFIG, APP_CONFIG } from '../constants'
interface AvatarCallbacks {
onSubtitleOn: (text: string) => void
onSubtitleOff: () => void
onStateChange: (state: string) => void
onVoiceStateChange?: (status: string) => void
}
class AvatarService {
private containerId: string
constructor() {
this.containerId = generateContainerId()
}
getContainerId(): string {
return this.containerId
}
async connect(config: AvatarConfig, callbacks: AvatarCallbacks): Promise<any> {
const { appId, appSecret } = config
const { onSubtitleOn, onSubtitleOff, onStateChange, onVoiceStateChange } = callbacks
// 检查容器是否存在
const containerEl = document.getElementById(this.containerId)
if (!containerEl) {
console.error(`[AvatarService] 容器 #${this.containerId} 不存在!`)
throw new Error(`容器 #${this.containerId} 不存在,请确保 AvatarRender 组件已渲染`)
}
// 构建网关URL
const url = new URL(SDK_CONFIG.GATEWAY_URL)
url.searchParams.append('data_source', SDK_CONFIG.DATA_SOURCE)
url.searchParams.append('custom_id', SDK_CONFIG.CUSTOM_ID)
// Promise管理连接状态
let resolve: (value: boolean) => void
let reject: (reason?: any) => void
const connectPromise = new Promise<boolean>((res, rej) => {
resolve = res
reject = rej
})
// SDK构造选项
const constructorOptions = {
containerId: `#${this.containerId}`,
appId,
appSecret,
enableDebugger: false,
gatewayServer: url.toString(),
onProxyWidgetEvent: (event: any) => {
console.log('SDK事件:', event)
},
onStateChange,
onMessage: async (error: any) => {
const state = await getPromiseState(connectPromise)
const plainError = new Error(error.message)
if (state === 'pending') {
reject(plainError)
}
},
onVoiceStateChange: (status: string) => {
// 当状态为 'end' 时,表示数字人停止说话
if (status.includes('end')) {
onVoiceStateChange?.(status)
}
},
}
// 创建SDK实例
const avatar = new window.XmovAvatar(constructorOptions)
// 等待初始化
await new Promise(resolve => {
setTimeout(resolve, APP_CONFIG.AVATAR_INIT_TIMEOUT)
})
// 初始化SDK
await avatar.init({
onDownloadProgress: (progress: number) => {
console.log(`初始化进度: ${progress}%`)
if (progress >= 100) {
resolve(true)
}
},
onClose: () => {
onStateChange('')
console.log('SDK连接关闭')
}
})
// 等待连接完成(设置超时避免永久挂起)
const connectTimeout = new Promise<boolean>((_, rej) => {
setTimeout(() => rej(new Error('SDK连接超时')), 15000)
})
try {
await Promise.race([connectPromise, connectTimeout])
console.log('[AvatarService] 连接成功')
} catch (error) {
console.warn('SDK连接等待结束:', error)
// 超时不抛错,可能已经初始化完成但没触发100%回调
}
// 连接成功后,注入CSS隐藏SDK字幕
this.injectSubtitleKiller()
return avatar
}
private injectSubtitleKiller(): void {
// 注入全局CSS,隐藏SDK自带的字幕元素
console.log('[AvatarService] 字幕隐藏已禁用,排查数字人渲染问题')
return
}
disconnect(avatar: any): void {
if (!avatar) return
// 移除注入的CSS
const killerStyle = document.getElementById('xmov-subtitle-killer')
if (killerStyle) killerStyle.remove()
try {
avatar.stop()
avatar.destroy()
} catch (error) {
console.error('断开连接时出错:', error)
}
}
}
export const avatarService = new AvatarService()
关键配置项说明:
- enableDebugger: false:生产环境关闭调试模式,避免控制台输出过多 SDK 内部日志
- gatewayServer:魔珐星云网关服务器地址,支持自定义数据源,是参数流架构的核心入口
- onDownloadProgress:初始化进度回调,100% 时表示资源加载完成,连接就绪
- onVoiceStateChange:语音状态回调,当数字人说话结束时触发,用于流式播报的段落控制
- getPromiseState:检查连接 Promise 的状态,避免在已 resolve 的情况下重复 reject
4.4 llm.ts – 大语言模型服务
基于 OpenAI SDK 封装,支持火山引擎豆包大模型。提供普通对话和流式对话两种模式,流式模式支持实时逐字输出。
import OpenAI from 'openai'
import type { LlmConfig, ChatMessage } from '../types'
import { LLM_CONFIG } from '../constants'
class LlmService {
private openai: OpenAI | null = null
private currentApiKey: string = ''
private initClient(config: LlmConfig): void {
if (this.currentApiKey === config.apiKey && this.openai) {
return // 避免重复初始化
}
const baseURL = config.baseURL || LLM_CONFIG.BASE_URL
this.openai = new OpenAI({
apiKey: config.apiKey,
dangerouslyAllowBrowser: true, // 允许浏览器端调用
baseURL: baseURL,
// 确保使用 fetch API 支持流式
fetch: (url, init) => {
console.log('LLM请求URL:', url)
console.log('LLM请求配置:', {
method: init?.method,
headers: init?.headers,
body: init?.body
})
return fetch(url, init)
}
})
this.currentApiKey = config.apiKey
}
// 普通对话模式
async sendMessage(config: LlmConfig, userMessage: string): Promise<string | null> {
this.initClient(config)
if (!this.openai) {
throw new Error('LLM客户端未初始化')
}
const messages: ChatMessage[] = [
{ role: 'system', content: LLM_CONFIG.SYSTEM_PROMPT },
{ role: 'user', content: userMessage }
]
try {
const completion = await this.openai.chat.completions.create({
messages,
model: config.model
})
const response = completion.choices[0]?.message?.content
return response || null
} catch (error) {
console.error('LLM请求失败:', error)
throw error
}
}
// 流式对话模式(核心)
async sendMessageWithStream(config: LlmConfig, userMessage: string): Promise<AsyncIterable<string>> {
this.initClient(config)
if (!this.openai) {
throw new Error('LLM客户端未初始化')
}
const messages: ChatMessage[] = [
{ role: 'system', content: LLM_CONFIG.SYSTEM_PROMPT },
{ role: 'user', content: userMessage }
]
try {
const stream = await this.openai.chat.completions.create({
messages,
model: config.model,
stream: true // 开启流式输出
})
// 返回异步迭代器,逐字输出
return (async function* () {
let chunkCount = 0
for await (const part of stream) {
chunkCount++
const content = part.choices[0]?.delta?.content
if (content) {
yield content
}
}
})()
} catch (error) {
console.error('流式请求失败:', error)
throw error
}
}
}
export const llmService = new LlmService()
设计要点:
- dangerouslyAllowBrowser: true:允许在浏览器端直接调用 LLM API,适用于 Demo 场景
- 自定义 fetch:拦截请求用于日志追踪,便于调试流式数据
- sendMessageWithStream 返回 AsyncIterable:调用方可通过 for await 逐字消费,实现流式播报
- API Key 缓存:currentApiKey 避免重复初始化 OpenAI 客户端
4.5 action-manager.ts – 流式播报动作队列
流式播报是灵引的核心交互机制。当 LLM 流式返回文本时,需要将文本分段发送给数字人 SDK 进行语音合成和动作驱动。ActionManager 通过队列模式管理这些播报段落,确保按顺序播放。
import type { Ref } from 'vue'
import type { ActionQueueItem } from '../types'
import { generateSSML } from '../utils'
interface SpeakOptions {
isStart?: boolean // 是否为流式对话起始
isEnd?: boolean // 是否为流式对话结束
}
interface ActionManagerOptions {
instanceRef: Ref<any | null>
onVoiceReady?: () => void
onVoiceEnd?: () => void
}
export class ActionManager {
private queue: ActionQueueItem[] = []
private isSpeaking = false
private instanceRef: Ref<any | null>
private onVoiceReady?: () => void
private onVoiceEnd?: () => void
constructor(options: ActionManagerOptions) {
this.instanceRef = options.instanceRef
this.onVoiceReady = options.onVoiceReady
this.onVoiceEnd = options.onVoiceEnd
}
speak(text: string, options: SpeakOptions = {}) {
const ssml = generateSSML(text.replace(/n+/g, 'n'))
this.queue.push({
ssml,
isStart: options.isStart ?? false,
isEnd: options.isEnd ?? false
})
this.processQueue()
}
reset() {
this.queue = []
this.isSpeaking = false
}
private async processQueue() {
if (this.isSpeaking) return
if (!this.queue.length) return
const instance = this.instanceRef.value
if (!instance) return
this.isSpeaking = true
while (this.queue.length) {
const item = this.queue.shift()
if (!item) break
this.onVoiceReady?.()
instance.speak(item.ssml, item.isStart, item.isEnd)
// 如果是流式中间段,不等待直接继续
if (!item.isEnd) {
continue
}
// 等待 speak 完成
await new Promise(resolve => setTimeout(resolve, 50))
}
this.isSpeaking = false
this.onVoiceEnd?.()
}
}
核心设计:
- SSML 封装:每段文本通过 generateSSML() 转换为 SSML 格式,支持音调、语速、音量参数
- 队列管理:即使多段文本同时到达,也按 FIFO 顺序逐段播报
- isStart / isEnd 标记:告知 SDK 当前段落是流式对话的开始、中间还是结束,SDK 据此控制动作连贯性
- 流式中间段不等待:if (!item.isEnd) continue 确保中间段落快速入队,降低播报延迟
4.6 流式播报分段策略
灵引采用「智能分段 + 首句优先」策略,在 LLM 流式输出过程中实时将文本推送给数字人:
// 流式播报核心逻辑(摘自 app.ts)
// 中文标点符号正则
const cnSplitSign = /[。?!;… ,:]/
// 英文标点符号正则
const enSplitSign = /[.?!;:,]/
const minimum = 20 // 首句最少缓存20个可读字符
const context = {
cache: '', // 缓存文本
chars: 0, // 缓存的可读字符数
firstSpeakSend: false, // 是否已发送首句
spaceCount: 0 // 空格计数(英文分词辅助)
}
// 创建一个 Promise,在第一句发送后立即 resolve
let firstSentenceResolved = false
const firstSentencePromise = new Promise<void>((resolve) => {
// 在后台继续处理流式数据
;(async () => {
try {
// 流式播报响应内容
for await (const content of stream) {
if (typeof content !== 'string') continue // 防御编程
context.cache += content // 将该段文本加入缓存
// 英文以空格开头加一个计数器做分割推送
if (content.startsWith(' ')) {
context.spaceCount += 1
}
const chars = content.match(/[u4e00-u9fa5a-zA-Z0-9]/g)?.length ?? 0 // 统计段内可读字符数
let shouldSend = false
if (!context.firstSpeakSend) {
// 首句:需要达到最小字符数且遇到标点符号
shouldSend = context.spaceCount
? context.spaceCount > minimum - 1 && enSplitSign.test(content)
: context.chars > minimum && cnSplitSign.test(content)
} else {
// 后续句子:遇到标点符号即可发送
shouldSend = context.spaceCount ? enSplitSign.test(content) : cnSplitSign.test(content)
}
if (!shouldSend) {
context.chars += chars
continue
}
// 发送缓存的文本
actionManager.speak(context.cache, {
isStart: !context.firstSpeakSend,
isEnd: false
})
// 如果是第一句,立即 resolve Promise,让 sendMessage 返回
if (!context.firstSpeakSend && !firstSentenceResolved) {
firstSentenceResolved = true
context.firstSpeakSend = true
resolve() // 第一句发送后立即返回成功信号
} else if (context.firstSpeakSend) {
context.firstSpeakSend = true
}
context.cache = ''
context.chars = 0
context.spaceCount = 0
}
// 处理剩余的缓存文本
if (context.cache.length > 0) {
actionManager.speak(context.cache, {
isStart: !context.firstSpeakSend,
isEnd: true
})
// 如果首句还没发送就结束了(短回复),也要resolve
if (!firstSentenceResolved) {
firstSentenceResolved = true
resolve()
}
} else if (context.firstSpeakSend) {
// 如果已经发送过内容但没有剩余文本,发送结束标记
actionManager.speak('', {
isStart: false,
isEnd: true
})
} else {
// 流结束但没有任何内容,也要resolve避免卡死
if (!firstSentenceResolved) {
firstSentenceResolved = true
resolve()
}
}
} catch (error) {
console.error('流式处理错误:', error)
// 如果第一句还没发送就出错了,也要 resolve
if (!firstSentenceResolved) {
firstSentenceResolved = true
resolve()
}
}
})()
})
// 等待第一句发送完成,然后立即返回
await firstSentencePromise
return 'success'
策略解析:
- 首句延迟优化:首句需缓存至少 20 个可读字符(汉字/英文/数字)并遇到标点才发送,避免过短片段导致语音不自然
- 中英文分治:中文按标点断句,英文额外统计空格数辅助分词
- 首句即返回:sendMessage() 在第一句发送后立即 resolve,前端无需等待整个流结束就能获得响应
- 尾部清理:流结束后检查剩余缓存,确保最后一段文本也被播报
4.7 useAsr.ts – 语音识别 Composable
基于腾讯云 ASR WebSocket 接口封装的语音识别 Composable,支持 VAD(语音活动检测)自动断句。
import { ref } from 'vue'
import type { AsrConfig, AsrCallbacks } from '../types'
import { ASR_CONFIG } from '../constants'
import { signCallback } from '../lib/asr'
export function useAsr(config: AsrConfig) {
const asrText = ref('')
const isListening = ref(false)
let webAudioSpeechRecognizer: any = null
const buildAsrConfig = (vadSilenceTime?: number) => ({
signCallback: signCallback.bind(null, config.secretKey),
appid: config.appId,
secretid: config.secretId,
secretkey: config.secretKey,
engine_model_type: ASR_CONFIG.ENGINE_MODEL_TYPE, // '16k_zh'
voice_format: ASR_CONFIG.VOICE_FORMAT, // 1 = PCM
filter_dirty: ASR_CONFIG.FILTER_DIRTY, // 过滤脏话
filter_modal: ASR_CONFIG.FILTER_MODAL, // 过滤语气词
filter_punc: ASR_CONFIG.FILTER_PUNC, // 过滤标点
convert_num_mode: ASR_CONFIG.CONVERT_NUM_MODE, // 数字转换
word_info: ASR_CONFIG.WORD_INFO, // 词信息
needvad: ASR_CONFIG.NEEDVAD, // 启用VAD
vad_silence_time: vadSilenceTime || config.vadSilenceTime || 300
})
const start = (callbacks: AsrCallbacks, vadSilenceTime?: number) => {
// ... 创建 SpeechRecognizer 实例并开始录音
}
const stop = () => {
// ... 停止录音
}
return { asrText, isListening, start, stop }
}
配置说明:
- engine_model_type: ‘16k_zh’:16kHz 采样率中文语音识别
- needvad: 1:启用 VAD 自动检测语音起止,vad_silence_time: 300 表示静音 300ms 后自动断句
- signCallback:腾讯云 ASR 的签名回调,使用 HMAC-SHA256 算法对请求进行鉴权
4.8 游戏主题 UI 架构
灵引采用深色电竞主题设计,以 #0a0e1a 为底色,青蓝 #00d4ff 与紫色 #a855f7 作为点缀色,营造专业电竞氛围。
页面结构:
┌─────────────────────────────────────────────┐
│ 🎮 灵引 首页 攻略 赛事 商城 社区 🔍 │ ← 顶部导航
├─────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Hero Banner 轮播(赛季/赛事/活动) │ │ ← 主内容滚动区
│ └─────────────────────────────────────┘ │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │角色扮演│ │MOBA │ │FPS │ │策略卡牌│ │ ← 游戏分类
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 皮肤 │ │ 键盘 │ │ 鼠标 │ │ 耳机 │ │ ← 电竞装备
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🎯 灵引 策略伙伴 [+/-] │ │ ← 对话面板(右下角悬浮)
│ │ [快捷问题chips] │ │
│ │ [输入框] [发送] │ │
│ │ [🎤 按住说话] [⏸ 打断] │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────┘
+ 数字人以透明背景叠加在右下角
CSS 主题变量:
:root {
/* 灵引电竞主题 - 青蓝紫渐变点缀深色系 */
--color-primary: #00d4ff; /* 青蓝主色 */
--color-secondary: #a855f7; /* 紫色辅色 */
--bg-primary: #0a0e1a; /* 深色背景 */
--bg-secondary: #0f172a; /* 次级背景 */
--text-primary: #f1f5f9; /* 主文字色 */
--text-secondary: #94a3b8; /* 次级文字色 */
--border-color: rgba(0, 212, 255, 0.15); /* 青蓝半透明边框 */
--glow-primary: 0 0 15px rgba(0, 212, 255, 0.2); /* 青蓝光晕 */
}
4.9 数字人渲染组件
AvatarRender 组件负责承载 SDK 渲染容器,并以固定定位叠加在游戏页面右下角。
<template>
<div class="avatar-render">
<!-- SDK 渲染容器 - 透明背景 -->
<div :id="containerId" class="sdk-container" />
<!-- 字幕显示 - 数字人脚下 -->
<div v-if="appState.ui.subTitleText" class="subtitle-area">
<div class="subtitle-text">{{ appState.ui.subTitleText }}</div>
</div>
<!-- 语音输入动画 -->
<div v-show="appState.asr.isListening" class="voice-animation">
<img :src="siriIcon" alt="语音输入" />
</div>
<!-- 加载状态 -->
<div v-if="!appState.avatar.connected" class="loading-placeholder">
<div class="loading-spinner"></div>
<div class="loading-text">灵引准备中...</div>
</div>
</div>
</template>
<style scoped>
.avatar-render {
position: fixed;
right: 20px;
bottom: 230px;
width: 400px;
height: 70vh;
max-height: 750px;
z-index: 100;
pointer-events: none;
display: flex;
justify-content: flex-start;
align-items: flex-end;
overflow: visible;
}
.sdk-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: flex-start;
align-items: flex-end;
overflow: visible;
z-index: 1;
background: transparent !important;
}
</style>
设计要点:
- pointer-events: none:数字人层不拦截鼠标事件,用户仍可点击底层游戏页面元素
- background: transparent !important:强制透明背景,确保数字人叠加在游戏页面上不遮挡内容
- z-index: 100:低于对话面板的 z-index: 200,确保交互面板始终在数字人之上
4.10 应用入口与自动连接
App.vue 作为根组件,在挂载时自动发起数字人连接,支持最多 3 次重试。
<script setup lang="ts">
import { provide, onMounted, nextTick } from 'vue'
import ExhibitionPanel from './components/ExhibitionPanel.vue'
import AvatarRender from './components/AvatarRender.vue'
import { appState, appStore } from './stores/app'
// 提供全局状态和方法
provide('appState', appState)
provide('appStore', appStore)
// 自动连接虚拟人(最多重试3次)
onMounted(async () => {
await nextTick()
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await appStore.connectAvatar()
console.log('[App] 虚拟人连接成功')
break
} catch (error) {
console.error(`[App] 第${attempt}次连接失败:`, error)
if (attempt < 3) {
await new Promise(r => setTimeout(r, 2000))
}
}
}
})
</script>
<template>
<div class="app-container">
<!-- 全屏主内容区域 -->
<ExhibitionPanel />
<!-- 右下角数字人叠加层 - 透明背景 -->
<AvatarRender />
</div>
</template>
连接流程:
- 应用挂载后,通过 appStore.connectAvatar() 发起连接
- 连接失败时等待 2 秒后重试,最多 3 次
- 使用 Vue 的 provide/inject 机制将全局状态注入所有子组件
五、总结
本文基于魔珐星云 XmovAvatar SDK 参数流架构,结合 Vue 3.5 + TypeScript + Vite 技术栈,从零搭建了「灵引 LingGuide」具身交互智能策略伙伴。整套方案打通了数字人渲染、LLM 流式对话、ASR 语音识别三大链路,通过首句即播报、智能分段、实时打断等机制将交互延迟压至 500ms 以内,并以透明叠加渲染将数字人无缝融入游戏页面,实现了文字 + 语音双模、看得见听得到能思考的沉浸式策略伙伴体验。这个项目也说明:当 AI Coding 工具负责快速搭建工程骨架,魔珐星云负责具身表达和终端交互,游戏 Agent 就能更快从 Demo 走向可用成品。
魔珐星云PC端官方链接:https://xingyun3d.com?utm_campaign=daily&utm_source=CSDNwanfen3&utm_medium=&utm_term=&utm_content=