资源推荐
tang-changan
tang-changan

两周,一座可以语音和李白对诗的大唐长安城

这是一个可以在浏览器里直接打开的大唐长安城,不需要安装任何客户端。

进入页面,默认是俯瞰视角的沙盘,朱雀大街、大明宫三大殿纵轴、七层大雁塔、曲江池画舫、东西二市。时间在走,昼夜在循环,宵禁时段行人消失、坊灯亮起。切换季节会有桃花或雪花飘落,切换天气会有真实风向的斜雨或横扫的沙尘。点击任意一个 NPC,弹出身份和台词。

按「走进长安」进入 RPG 模式,选一个职业,WASD 控制角色在城里行走,靠近 NPC 按 E 对话,可以猜拳、猜谜、飞花令、对对联。走到宫殿门口按 F,进入 FPS 第一人称展厅,看程序化生成的壁画,看向哪幅壁画底部就弹出标题注释。进入 AI 展馆「智机府」,可以按住麦克风,用语音和李白、杜甫、王维实时对话,他们用各自的说话风格回应。

项目在两周内完成,完全开源,代码在 GitHub,前端部署在 GitHub Pages。

下面拆解它用到的技术,重点放在每个方向上真正难的地方。

Three.js:三套相机状态的切换

用 Three.js 做这类项目,渲染本身不是瓶颈,难的是管理多套相机状态之间的过渡。

项目有三个相机模式:沙盘俯瞰用 OrthographicCamera,进入 RPG 模式切换为第三人称透视跟随,FPS 模式再换成 PointerLockControls 锁鼠标。难点在于每次切换都不能跳切,从俯瞰沙盘突然跳到角色背后会让人找不到自己在哪。解决方式是每帧用 lerp 插值相机位置和目标点,把两种视角之间的过渡变成连续的滑动。

性能上,1200+ 网格 + 217 个 NPC 在移动端跑到 30+ FPS,靠的是全程序化几何体、不引入外部模型文件,以及 60Hz 帧循环里只做屏幕空间最近 NPC 查询,避免每帧做全量 3D 距离计算。

后期处理接了 OutlinePass 做悬停高亮、UnrealBloom 给灯笼和金顶做辉光,这两个 Pass 基本决定了场景的视觉质感,但也是移动端最需要注意的性能点。

Web Audio API:不引入音频文件的音效合成

把项目部署在 GitHub Pages 上,不适合打包音频资源。项目选择用 Web Audio API 直接合成所有音效,寺钟是三个泛音叠加模拟金属共鸣衰减,宵禁鼓是低频正弦加白噪声,鸡鸣用锯齿波频率扫描,雷鸣是雨天随机触发的低频噪声。

这个方向的卡点不是 API 用法,而是参数调校:要让合成出的寺钟听起来像寺钟而不是电子音,需要在泛音比例、包络曲线和衰减时间上反复调。

Agora ConvoAI + iframe:让 3D 游戏和语音 Agent 共享状态

语音部分没有集成进主游戏,而是拆成独立子项目:Next.js 前端 + FastAPI 后端,通过 iframe 嵌进游戏页面,用 postMessage 传递状态。这个架构决定背后的原因是:语音 Agent 需要独立部署和维护,和 Three.js 主场景耦合在一起反而会让两边都难调试。

卡点在状态同步:玩家走到李白旁边按 E 打开对话,游戏需要告诉语音面板「当前角色是李白」,语音面板说话结束后要把字幕、头像动画同步回游戏。这条 postMessage 通道看起来简单,但要保证时序,角色身份要在语音会话建立之前传过去,不然 ConvoAI 用的还是上一个角色的 Persona。

Persona 本身是另一个设计点:每个角色配独立的系统提示和 TTS 音色,李白和杜甫用同一套语音基础设施,但说话风格和声音完全不同。场景上下文也会注入进去,让角色“知道”玩家当前在宫殿里还是在市集里。

如何本地运行

cd tang-changan
python3 -m http.server 8088
# 访问 http://localhost:8088/

语音功能需要额外启动语音子项目,详见仓库 docs/05_从设计到完成_开发过程科普教程.md,里面有完整的 Agora CLI 安装、登录、ConvoAI 检查和后端启动流程。

GitHub 仓库 (opens in a new tab)

来源