Unity × Agent:让 NPC 从“会聊天”到“像人一样陪玩家游玩”
让 Unity 接入 AI 的相关能力,实现一个基于 Unity 的 Agent 实现方式。 它需要具备理解、记忆、执行和回应能力——就像真正的人类在与我一同游玩。
初始抉择:Unity vs Python、本地模型 vs API调用之间的权衡与抉择
项目开始于一个家游戏公司,他们一直想把ChatGPT的能力应用于游戏当中。可是受限于团队规模、AI工程经历以及开发周期的限制,不得不把这件事情搁置。
而我在这家公司的任务就是负责其中的AI部分的开发。
随着我的深入,我发现这整个Agent的难点并不在于将 API 接入 Unity ,而是如何在合适的时候填充对应的需要的内容。
经过几天的对于团队以及 Unity 生态的了解,我发现 Unity 的 LLM 生态远不成熟,支持十分有限。 Unity当中很多的AI功能只是能跑和有的状态(比如 LLM Unity,光是找它的 API 文档就用了两三天)。而且这些插件的实现也是python几行的事情。而Python中对于Agent的框架不胜枚举、各具特色。有用于RAG的Chroma、LlamaIndex,有用于Agent编排的LangChain、PhiData、Langgraph等等。
有趣的是当时发现一个AI狼人杀的故事,具体是狼人杀的外壳介入了ChatGPT。即使这样简单的狼人杀搭配LLM的适配也用了20+人搭配9个月的开发时间。而且这个游戏还要付API费用,游戏开发者特地在说明中写游戏购买的40元实际上是API费用,之后得靠赞助以及充值来支持后续游玩。 所以在当时我们还考虑了本地模型方面,25年初的主流选择是 LLaMA2,但实际性能严重不足,并且这种模型输出中英文混杂,体验很差。真正做到质量、速度、成本平衡的开源模型只有 Qwen 2.5 3B,但其许可证(Qwen Research)不允许商用。所以我们只能先用着这个模型,把希望寄托给时间,寄托给Scaling Law。
考虑到实际的团队规模以及时间,选择Python才是最优解。 于是我们选择了Python 为主, Unity负责前端展示。模型调用主要的开发方向是API调用。归根结底,外部的时间和工具还没到位,我们只能选择这一条道路。
当时我最大的感受是在各种技术发展、协议限制、团队成本的权衡下,通常最契合目标的路径只有一条。
逐项击破:从”能用”到”像人”
这个系统的目标,在设计之初就要从游玩者的角度出发 一方面是团队中没有LLM Training的经历致使我们没有技术惯性,另一方面在游戏行业以及游戏开发者本身,就是用户、开发一体的。 所以这里的感性指标是”能记住事”、“有个人情绪”、“像个我的朋友”、“不只说,还做事”。
整个迭代过程,是一场持续削减延迟、压缩上下文、模拟心理模型、从真实用户体验出发反向找到技术限制的可行实现方式的过程。
问题一:RAG 准确率过低
传统的各种文本分段 + 向量化检索(即 RAG)在游戏这个场景下效果极差。 游戏本身的设定、故事决定了游戏当中大多数的文本资料都是经历描述。重点不在经历,在于多个人在多个时间点的多个经历的结合。与其凭借当时非常不智能的RAG模型以及切片算法,不如手动抽取游戏当中的重点并将其手动持久化供RAG随游戏剧情进度检索开放。这样做到了检索质量、速度、成本之间的平衡。
最后我们的技术方案时放弃 RAG,转向结构化数据:直接使用 CSV 文件作为信息载体。Unity 端随进度解锁 CSV,Python 后端通过 AI 自主的工具调用进行搜索,检索结果直接拼接进 prompt。 这样,Agent 不再”模糊猜测”,而是”明确调用,轻易得到信息”。
与此同时整体耗时来到了 7s(首 Token 2.5s)。这对于游玩来说完全是不能接受的。
问题二:响应速度太慢
等待是一种用户焦虑。一定要拼尽全力减少等待。在开始,老板就告诉了我这句话。这句话不仅仅适用于游戏,还包括所有软件。并且给我定下了指标。
- 硬指标:3s 之内,第一个字必须出来。这是游戏行业真实测试的结果
- 软指标:像人一样交流——可以迟疑,但是我要知道你在做什么
在这里我从用户体验的角度倒推技术上可行的方案。发现用户焦虑的不是等待,而是没有消息的等待,也就是说对话窗口只要出现了几个字,用户的焦虑就可以被大大缓解。这也为我提供了一个可行的实现方案。
也就是API 流式输出 + 动态函数调用:先说话,后做事。即优先返回前几句”我明白了,我来查一下”,随后在后台执行函数,拉取检索数据并且同时前端显示AI调用了什么工具,再继续完整回答。
结果是首字延迟压缩到 最快 2 秒,常态 3 秒内,几乎消除了用户端的空等感。这个 2s 是这个框架的最佳表现。
问题三:情感缺失
角色说话太像机器,不像人。放在游戏中就是NPC + AI == NPC ,我们的目标是真正的朋友——张飞,俺一定要报效祖国、今天不喝两碗,俺不睡!
当时尝试了在提示词固定化的情况下、进行微调。也就是将角色的背景设计进行抽离之后,使用这段提示词固定作为角色提示词的,之后准备数据并进行TRL微调。在之后的实际调用过程当中就是始终带着这段固定的提示词,这样子模型不仅仅是经过了微调,而且本身这段所有的角色定义也能够直接被模型所使用。
角色不但能在合理上下文中表现出”担忧""讽刺""温柔”等情绪,还能针对玩家的历史行为做出八卦式的调侃,比如:“少爷,您还有笔贷款没还呢!”
这让我不禁反思:GPT-4.5 试图通过预训练来实现情感化人物化的方法,是否走错了方向?当然,我也不完全确定。但可以肯定的是,这种方式成本太高,而且情感化本身就是信息量很低的数据,用到训练当中数据本身就不适合深度学习——想要一步到位,可行的方案就是在一个经过大量精炼自然语言训练的大脑上,通过提示词使其表现出真实的感情倾向。
问题四:事件记忆不足
由于LLM本身的特性,角色始终是失忆的。我们必须在对应的对话窗口当中插入对应的背景故事。这里面对的是提问时机不同、内容不同、用户想了解的不同。所有的对话场景我们都想做成开放式,而非slg的形式。
基于上述 RAG 的设计,我们延续了结构化 CSV 文件。所有事件通过 Unity 的行为触发后开放对应预设的 CSV,Agent 则始终有查询对应 CSV 的工具。并且调用工具的次数严格限制在2次以内,一方面是为了响应速度,另一方面是从事实上来说大多数的检索可以在两步之内解决,不需要过多的调用工具。限制工具调用次数也是提示词质量的一种保证、反馈。
这种机制避免了上下文无节制地膨胀,也让 Agent 在需要”回忆”的时候才去”想起”玩家的历史行为。这样就实现了开发效率、系统开放、实时响应的目标。
结语:不是因为做得到才做,而是因为做了才知道可行
这个记录实际上是当时整个工程实践的记录。根本是看看 Unity + AI Agent,到底可以做到什么程度。的确是可以做到”像人”。
不完美,但已经足够惊人。情感、记忆、理解、调侃、迟疑——这些都可以构建。而最终呈现在玩家面前的,是一个真正意义上”会活的角色”。
以前总觉得技术的实现并不应由我来做,因为未知很多、考量很多、平衡很多。现在回顾这一趟路程发现未被实现的技术我可以实现、没有前足涉足过的领域我也可以涉足、究其根本只是多问几个为什么、怎么办。