news 2026/9/16 13:35:04

Unity AI逻辑笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity AI逻辑笔记

用动画状态机当AI状态机

写ai逻辑基本上都需要状态机。因为懒得手搓状态机,所以选择直接用动画状态机当逻辑状态机用。

AI的基本行为

  1. 不停检测视野范围内是否有敌人;
  2. 没发现敌人时,静止、沿设定路线巡逻或随意走动;
  3. 发现敌人后,进入警戒状态,并进入攻击状态,转向敌人,攻击;
  4. 丢失目标后,保持警戒一段时间,然后进入安全状态;
  5. 丢失目标后可以选择是否追击,追击则移动到;
  6. 受攻击后会进入警戒状态;
  7. 警戒状态随意走动,指定一个附近目的地,朝向它,前进,同时不停检测是否已经足够接近,接近了就停止,再指定目的地,循环;
  8. 警戒状态,环顾四周;
  9. 攻击状态朝向目标,开启瞄准约束,开枪一段时间,停火一段时间,然后检查没子弹就换弹,如此循环;

AI程序的特点是在一个检测状态的大循环之下,各状态有攻击、环顾、走动等小循环。

架构设计

因为敌人的根节点已经有一个animator控制动画,只能增加一个子对象AI,给它加一个animator指向逻辑“动画”状态机。还有一个脚本,用来放一些检测函数和动画事件函数。

状态图设计

所有状态都不停执行检测敌人的方法。

动画剪辑设置

动画剪辑添加一个无关紧要的属性(如Scale)来卡时间。重要的是在特定的时间执行动画事件。如在Safe一段时间后开始巡逻:

public void StartPatrolling(){ enemyController.SetBool(patrolling,true); }

攻击状态开枪几秒,停歇几秒,然后检查是否该换弹:

状态机行为脚本

主要用于在特定状态才每帧执行的代码。

在状态机行为脚本里满足某些条件时执行animator.SetXXX()改变动画参数,动画参数改变又引起状态转换,执行新的状态机行为脚本,可以达到状态机“自驱动”的效果。

public class EnemyAlert : StateMachineBehaviour { Character1 myCharacter; MyNPCAI myAI; override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex){ myCharacter=animator.transform.parent.GetComponent<Character1>(); myCharacter.PutAwayGun(); myAI=animator.GetComponent<MyNPCAI>(); myCharacter.PlayRandomClip(myAI.findAudio); myCharacter.UseRifle(); myAI.StopLookingAround(); } // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex){ if(myAI.DetectEnemy()){ animator.SetBool(MyNPCAI.foundEnemyPara,true); } } }

但是每个状态机行为脚本进入时都要获取一遍组件。这很恶心。

慢慢地我发现用协程好像也能替代状态机行为的功能,只需要状态机+动画事件+协程就

状态里的小状态

  1. Alert交替进行环顾四周、随机移动。环顾四周是一个过程,可以指定目标方向或旋转时间,移动也是类似的过程;
  2. 攻击要先转向目标,然后交替进行射击和停火,也是个子状态机;
  3. 其他状态要想更细,都会变成子状态机;

要把状态分成小状态吗?状态会多一点,多出来的转换就更多了。或者可以用协程做小状态?怎么用协程做个小状态机?可以在行为IEnumerator里开启下一个行为的协程,会不会有什么问题?

AI检测敌人

一开始我想让AI能发现前方扇形区域的敌人,没有扇形碰撞体,用代码写了一个。后来为了简单,直接在人物前方用球形范围检测了。

除了范围检测,还要用Physics.LineCast()检测中间有没有障碍物。

bool DetectEnemy() { Vector3 center = transform.position + transform.forward * detectRadius; Collider[] colliders = Physics.OverlapSphere(center, detectRadius, MyGameManager.Instance.aiDetectLayers); for (int i = 0; i < colliders.Length; i++) { CharacterBase character; if (colliders[i].TryGetComponent(out character) && !CheckBarrier(character) && character.HP > 0) { if (character.characterSide != myCharacter.characterSide) { target = character; targetLastPosition=character.transform.position; return true; } } } return false; }

AI瞄准敌人

给AI的枪绑定对象加了AimConstraint使枪对准敌人,省去了写复杂的瞄准算法。

为了使AI不每枪必中,给AimVector加了随机误差。

void AddAimError(){ gunAim.aimVector=Vector3.forward+ UnityEngine.Random.Range(-aimErrorRange,aimErrorRange)*Vector3.up+ UnityEngine.Random.Range(-aimErrorRange,aimErrorRange)*Vector3.right; }

这容易造成AI身体没指向目标时就打开瞄准约束,造成很不自然的样子。

AI攻击流程

进入攻击状态后转向目标,面向目标后交替开火和停火。攻击过程中要一直朝向敌人,没有朝向敌人不能开火。

public void TurnTo(Vector3 target) { Vector3 aimVector = target - myCharacter.transform.position; aimVector = new Vector3(aimVector.x, 0, aimVector.z); if (aimVector == Vector3.zero) { return; } Quaternion targetRotation = Quaternion.LookRotation(aimVector); myCharacter.transform.rotation=Quaternion.Lerp(myCharacter.transform.rotation, targetRotation,.2f); }

AI环顾四周

先得到一个随机方向A,然后一帧帧转过去,和方向A的偏差小于一个值后再得到随机方向,循环。

效果演示

Unity简单敌人逻辑演示:巡逻、发现、攻击、追击_演示

总结

  1. 使用状态机写AI,一开始会想分成安全、警戒、攻击等状态,但是很快会发现这样粗略的状态划分不够,大状态下必然有次级状态规定人物的行为;
  2. 人物的行为不是执行一个函数就完成,都需要持续一段时间,有的持续一段时间结束,有的持续中还要检测结束条件;

不用动画状态机写ai

首先还是状态基类、状态管理器,状态基类有进入、更新、退出。然后状态管理器持有状态的方式可以用字段,也可以用字典。

我选择直接让管理器持有每种状态作为字段,因为状态机拥有的状态数量是写死的,不需要运行中添加移出状态,反而需要一开始就保证所有需要的状态都有了。

记录当前状态的方法,可以直接维护一个状态父类字段,但是我们想在检查器看到当前状态,那么可以自定义检查器,打印当前状态的名字。如果用字典记录状态则可以弄状态枚举,枚举记录当前状态,检查器显示枚举。那样除了定义一堆状态子类还要一一定义枚举。

然后更新状态是用switch还是用多态方法。用switch状态多了会变成巨量代码。

最终我们选定的方案组合是:

  1. 字段存储状态(无动态增减状态需求,代码比定义字典然后添加状态少一些)
  2. 一个状态父类字段表示当前状态;
  3. 用当前状态的Update多态更新状态;

然后难搞的还是那几个问题:

  1. 大状态里的小状态,就拿人物射击攻击为例,分为转向、开枪、间歇。开枪和间歇循环,开枪期结束后要检查要不要换弹。这个攻击状态本身就是一个状态机。
  2. 时间控制。以人物开枪0.6秒为例,动画状态机在动画剪辑里很容易控制时间,纯程序要么计时器,要么协程。我们需要手搓一个类似Animation时间轴配置动画事件的界面。我们想到弄一个列表,列表元素里记录距上一个动作的时间,以及这个动作的方法名字。或者如果懒得填方法名字则用方法枚举,弄一个枚举-方法映射字典。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 13:32:05

Agent核心应用场景与技术发展趋势解析

作为一名还在读博的 “老油条”&#xff0c;我最怕的就是文献调研环节 —— 不是怕读论文&#xff0c;而是怕那种 “搜了半天全是废纸” 的空虚感。 以前一头扎进数据库&#xff0c;关键词调来调去&#xff0c;结果还是筛出一堆鸡肋&#xff0c;时间成本高到让人想摆烂。但 20…

作者头像 李华
网站建设 2026/9/16 13:31:49

银行协议班值不值得报?不过全退是福利还是套路

提醒大家一句&#xff1a;协议班值不值得报&#xff0c;真的不能瞎选。选对了&#xff0c;事半功倍&#xff1b;选错了&#xff0c;白花冤枉钱不说&#xff0c;还耽误备考时间。一、协议班听着很美好很多人报班&#xff0c;都会被协议班吸引。不过全退&#xff0c;听着特别美好…

作者头像 李华
网站建设 2026/9/16 13:29:56

龙岩新罗区汽车钥匙与开锁服务:连锁门店介绍与就近上门说明

# 龙岩新罗区汽车钥匙与开锁服务&#xff1a;连锁门店介绍与就近上门说明## 一、二十年的本地服务积累玖玖汽车钥匙一号店和龙岩阿龙开锁汽车钥匙都是龙岩本地经营二十年的服务门店&#xff0c;从早年的机械钥匙配齿&#xff0c;到后来的芯片钥匙、遥控匹配&#xff0c;再到现在…

作者头像 李华
网站建设 2026/9/16 13:29:06

Agent Zero 智能体框架实战:从容器启动到自建工具

Agent Zero 智能体框架实战&#xff1a;从容器启动到自建工具 【免费下载链接】agent-zero Agent Zero AI framework 项目地址: https://gitcode.com/GitHub_Trending/ag/agent-zero 凌晨两点&#xff0c;你不想自己再翻一遍网页找那个数据。Agent Zero 是一款开源智能体…

作者头像 李华
网站建设 2026/9/16 13:27:42

从技术骨干到管理者的思维蜕变与实践

1. 管理者的思维重构&#xff1a;从执行者到领导者的蜕变当我第一次从技术骨干晋升为团队管理者时&#xff0c;以为最大的挑战是学会分配任务和主持会议。直到连续三个项目出现严重延期&#xff0c;我才意识到&#xff1a;真正的管理不是简单的"管人"&#xff0c;而是…

作者头像 李华