领取优惠


用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. 系统架构:一条数据流,四个模块,全解耦

  1. AudioCapture:负责麦克风循环缓存,采样率可调,事件抛出原始片段;
  2. SpeechRecognizer:接收片段,调用ASR,回传字符串;
  3. IntentParser:把字符串映射成“强类型指令”,例如LightOnMoveTo
  4. 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
“开灯” → Invoke LightManager.TurnOn()
“走过去” → Invoke NavMover.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吃光

  1. 采样率16 kHz足够中文识别,再往上收益递减;
  2. 每3秒切片一次,Whisper-base模型在i5-8250U占30%瞬时CPU,移动设备务必降频到10秒
  3. API密钥放streamingAssets会被打包,用Android Keystore或PC PlayerPrefs加密存储,答辩完记得 revoke;
  4. 麦克风权限: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. 可继续扩展的方向

  1. 多轮对话:把IntentParser换成开源NLU(Rasa本地版),维护上下文槽位,就能实现
    “把灯调成暖色” → “再亮一点” 的指代消解;
  2. 语音情感:Whisper输出token带时间戳,结合openSMILE提取能量/基频,驱动角色表情;
  3. TTS闭环:识别完用Edge-TTS离线模型回读,实现“开灯了,请查收”的反馈,让系统不再“哑巴”。

毕业设计不是论文堆字数,而是能跑、能交互、能展示
把上面框架git clone下来,换模型、换场景、换动作,就能在两周内攒出评委眼前一亮的作品。
下一步,你准备让角色听懂“把红色椅子搬到窗边”这种超长指令吗?试试把NLU加进来,欢迎回来交流踩坑日记。

领取优惠


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐