在Unity用LLM大模型+FSM有限状态机构建智能敌人AI

记录一下之前的主导的课程作业,主要负责其中前期准备,架构设计,状态机部分代码

引入

从老师给出的题目中选出该课题后。首先便浮现以下几个问题:

  1. 要在什么类型游戏下使用LLM实现敌人的AI?
  2. AI要全权接管敌人AI,还是介入部分,又要介入哪个部分呢?
  3. 因为LLM返回结果需要一定时间,且结果有不确定性,要怎么解决?

在第一个问题上,我最终选择2d俯视角射击游戏。他对于我们而言足够简单,特别是在有AI辅助编写代码下,可以快速完成原型开发。而在关键的敌人身上也只有移动和射击两个基本操作,但是却可以设计出足够复杂的行为来体现LLM的作用。
随后我采用有限状态机混合LLM的方式来驱动敌人的行动。现阶段LLM肯定不能时时刻刻调用来确定敌人的行动,应该像人体一样:感官收集信息,由大脑进行决策,又有小脑,脊髓和肌肉记忆负责简单动作和反射。设计感知模块,决策模块,执行模块,由LLM担任敌人AI的指挥官。
最后通过提示词设计,让LLMAPI返回Json格式结果,限定了其输出,一定程度上保证了结果的准确性。同时在客户端验证结果,设计非法回复处理。

开发环境搭建

由于小组里共有三人可以负责程序的开发,我决定引入开源项目来解决敌人寻路问题。先是了解到unity的packages中有ai.navigation包集成了A*的实现,可以简化敌人寻路AI开发。又发现navigation对于2d游戏的实现不佳。通过网络搜索和询问AI最后引入了navmeshplus来支持2d寻路。
所以最终初始环境为:

项目架构设计

2d俯视角游戏基本设置

  1. PlayerController
    基本玩家控制脚本,只有移动和射击两种操作。
  2. SmoothFllowCamera
    简单相机跟踪
  3. HealthInteractor
    即可以处理玩家又可以处理敌人的血量变化和交互的脚本。
  4. Bullet
    子弹预制体的MonoBehaviour脚本

LLM API调用

  1. DeepSeekChatManager 调用DeepSeep大模型API的模块。

核心模块,敌人AI

参考人类思考和行动的方式,进行了分层设计,大致为感知,决策,执行层。

  1. 中间桥梁感知层EnvPerceiver
    通过该脚本检测环境信息,包装为自然语言传递给下流决策层进行决策,和给执行层锁定追踪对象,寻找掩体。 alt text
  2. 顶层决策层LLMPlanner
    接收感知层信息,调用LLMAPI调用模块,传达命令给执行层。
    alt text
  3. 底层执行层,共两层
    1. StrategyExecuter
      alt text
      接收命令,发出状态切换的指令。
    2. 低级执行层,预编写的各种状态
      1. IdleState待机状态
      2. RetreatState撤退状态
      3. ChaseState追击状态
      4. WanderState游荡状态

最终结果

实际开发中的难点

根据自己开发的部分和小组成员的反馈。在AI辅助开发情况下,难度不高。主要是未考虑其他条件和提供给AI的实际项目上下文不足导致的bug。如:

  • API调用没有使用异步或者协程导致阻塞主线程。
  • 空引用不存在的模块。
  • API调用超时,非法输出处理。通过引入状态延续,默认状态机制解决。
    实际最困难的部分,反而是状态机部分。撤退状态开始时涉及了掩体躲避和反击,但是用AI编写的代码不和预期,最终因为时间关系,只能改为单纯尝试远离玩家的设计。 还有一些参数的设置问题,为了控制API调用频率,降低成本。我们引入了API调用频率控制的参数。太慢显得敌人呆呆,太快消耗token。最后引入根据环境是否高压,动态控制思考调用频率,即减少token消耗,又保留敌人的高智能。

总结

这次架构设计让我深刻理解了“确定性系统”与“非确定性 AI”的结合之道。通过将 LLM 限制在“策略层”,我们既获得了智能的战术决策,又保留了传统代码的稳定性。
但是还有一些未来可以改进优化的地方。
最开始是准备引入unityPackage的ML-Agent包,在底层执行使用强化学习让敌人行为更智能。考虑到DDL要到了,需要的python方面AI编码和训练时间,最终没有加入。
目前只有一个敌人,而且代码只考虑孤军作战的情况。放多几个敌人是没有相应的合作行为。未来或许可以加入指挥官系统,考虑多ai合作。
考虑给LLM更多的权限,比如更多状态,放开状态参数调整权限,让有限状态产生更多变化。

使用 Hugo 构建
主题 StackJimmy 设计