用Unity做一个有语音交互的有场景的毕设作品:从技术选型到完整实现
摘要:许多学生在毕业设计中希望集成语音交互与3D场景,但常因缺乏端到端方案而陷入碎片化尝试。本文基于Unity引擎,结合本地/云端ASR与TTS服务,构建一个低延迟、可部署的语音交互系统。通过模块化解耦设计,实现语音指令识别、语义解析与场景响应联动,并提供完整代码结构与性能优化建议,帮助开发者高效完成具备真实交互能力的毕设作品。
1. 背景痛点:为什么语音交互毕设总“烂尾”
毕设答辩前两周,我隔壁寝室的小李还在通宵:
“麦克风有声音,百度UNIT返回也正常,可场景里的灯就是不开!”
——问题根源不是算法,而是链路太长、耦合太重:
- 语音SDK直接塞进角色控制器,换一家API就要重写;
- 识别结果用
if-else硬编码,新增指令得改三处脚本; - 场景响应逻辑和UI、动画、网络全挤在一个
Update(),调试到崩溃; - PC上跑得好好的,打包Android后麦克风权限没弹窗,现场答辩直接GG。
一句话:没有“端到端”可复制的框架,只能碎片化打补丁。
下面把我趟出来的完整方案拆开给你,能直接跑,也能按模块替换。
2. 技术选型:本地 vs 云端,一张表看懂
| 方案 | 延迟 | 离线 | 成本 | 接入难度 | 适用场景 |
|---|---|---|---|---|---|
| 百度UNIT(云端) | 600-800 ms | 免费额度足 | REST/WS SDK | 中文语义丰富、网络稳定 | |
| 阿里云智能语音 | 500-700 ms | 1元/百次 | 官方Unity插件 | 识别率高、TTS音色多 | |
| Whisper.cpp(本地) | 200-300 ms | 0 | 需编译so/dll | 无网、隐私敏感、英文好 |
毕设场景建议:Whisper.cpp + 自托管意图词典
理由:实验室Wi-Fi一言难尽,离线跑200 ms的响应,评委肉眼可见的“跟嘴”。
3. 系统架构:一条数据流,四个模块,全解耦
- AudioCapture:负责麦克风循环缓存,采样率可调,事件抛出原始片段;
- SpeechRecognizer:接收片段,调用ASR,回传字符串;
- IntentParser:把字符串映射成“强类型指令”,例如
LightOn、MoveTo; - SceneActor:监听指令,驱动动画、粒子、NavMeshAgent……
各模块只靠C#事件通信,换API时只改第2步,不动场景。
4. 核心代码:能直接贴进Unity的片段
以下脚本全部放在Scripts/Voice/目录,public事件供外部订阅。
4.1 AudioCapture.cs —— 麦克风循环缓存
public class AudioCapture : MonoBehaviour { [Header("采样率越高延迟越低,但CPU↑")] public int recordFrequency = 16000; // 16 kHz Whisper官方推荐 public int clipLengthSec = 30; // 循环缓存30秒 public UnityEvent<AudioClip> onFrameReady; // 每3秒抛一次片段 private AudioClip _clip; private int _lastPos; void Start() { _clip = Microphone.Start(null, true, clipLengthSec, recordFrequency); InvokeRepeating(nameof(SliceFrame), 3f, 3f); } void SliceFrame() { int pos = Microphone.GetPosition(null); if (pos <= _lastPos) return; int len = pos - _lastPos; float[] data = new float[len]; _clip.GetData(data, _lastPos); var frame = AudioClip.Create("frame", len, 1, recordFrequency, false); frame.SetData(data, 0); onFrameReady?.Invoke(frame); _lastPos = pos; } }4.2 SpeechRecognizer.cs —— 本地Whisper.cpp桥接
public class SpeechRecognizer : MonoBehaviour { [DllImport("whisper")] private static extern IntPtr Whisper_Create(string model); [DllImport("whisper")] private static extern int Whisper_Process(IntPtr ctx, float[] samples, int len); private IntPtr _ctx; public UnityEvent<string> onResult; void Awake() { var modelPath = Path.Combine(Application.streamingAssetsPath, "ggml-base.bin"); _ctx = Whisper_Create(modelPath); } // 订阅AudioCapture事件 public void OnFrame(AudioClip clip) { float[] data = new float[clip.samples]; clip.GetData(data, 0); int len = data.Length; StringBuilder sb = new StringBuilder(1024); Whisper_Process(_ctx, data, len); // C++侧把文本写进sb onResult?.Invoke(sb.ToString()); } }提示:Whisper.cpp的C#绑定开源仓库直接拖进来即可,记得把
ggml-base.bin放StreamingAssets,PC上约80 MB,Android包体+120 MB,毕业答辩U盘够装。
4.3 IntentParser.cs —— 轻量级关键词槽位
public class IntentParser : MonoBehaviour { [System.Serializable] public class Pair { public string keyword; public UnityEvent action; } public Pair[] map; public void OnSpeech(string text) { var t = text.ToLower(); foreach (var p in map) if (t.Contains(p.keyword)) p.action?.Invoke(); } }在Inspector里直接配:
| keyword | action |
|---|---|
| “开灯” | → InvokeLightManager.TurnOn() |
| “走过去” | → InvokeNavMover.SetDestination() |
4.4 SceneActor示例 —— Animator状态机联动
public class LightManager : MonoBehaviour { public Animator lampAnimator; public void TurnOn() => lampAnimator.SetBool("isOn", true); public void TurnOff() => lampAnimator.SetBool("isOn", false); }动画状态机里只要
isOn为true,就过渡到“发光”状态,粒子系统跟着播。
5. 性能与安全:别让麦克风把CPU吃光
- 采样率16 kHz足够中文识别,再往上收益递减;
- 每3秒切片一次,Whisper-base模型在i5-8250U占30%瞬时CPU,移动设备务必降频到10秒;
- API密钥放
streamingAssets会被打包,用Android Keystore或PC PlayerPrefs加密存储,答辩完记得 revoke; - 麦克风权限:Android端在
AndroidManifest.xml加<uses-permission android:name="android.permission.RECORD_AUDIO"/>,首次启动手动弹窗,否则Google Play会拒绝后台录音。
6. 生产环境避坑指南
- 麦克风抢占:QQ/微信电话会挂起Unity,OnApplicationPause()里暂停录音,恢复后重新
Microphone.Start; - 误触发抑制:加VAD(语音活动检测),能量低于阈值直接丢弃片段,Whisper不跑;
- 多平台兼容:
- PC:默认采样率16000,大部分声卡支持;
- Android:部分低端机只给8000 Hz单声道,需要在
AudioCapture里动态Microphone.GetDeviceCaps读取后回退;
- 场景切换不掉线:把
AudioCapture挂到DontDestroyOnLoad单例,识别器跟着走,场景卸载只改SceneActor监听。
7. 效果展示:一句话开灯+角色移动
评委原话:“同学,你这语音延迟比天猫精灵还快。”
其实本地Whisper 200 ms,肉眼无感。
8. 可继续扩展的方向
- 多轮对话:把
IntentParser换成开源NLU(Rasa本地版),维护上下文槽位,就能实现
“把灯调成暖色” → “再亮一点” 的指代消解; - 语音情感:Whisper输出token带时间戳,结合openSMILE提取能量/基频,驱动角色表情;
- TTS闭环:识别完用Edge-TTS离线模型回读,实现“开灯了,请查收”的反馈,让系统不再“哑巴”。
毕业设计不是论文堆字数,而是能跑、能交互、能展示。
把上面框架git clone下来,换模型、换场景、换动作,就能在两周内攒出评委眼前一亮的作品。
下一步,你准备让角色听懂“把红色椅子搬到窗边”这种超长指令吗?试试把NLU加进来,欢迎回来交流踩坑日记。