首页 / 资讯中心 / 文章详情

Unity AI Demo完整实现:基于有限状态机与NavMesh的巡逻追击搜索闭环

Unity AI Demo完整实现:基于有限状态机与NavMesh的巡逻追击搜索闭环 ★ FEATURED ARTICLE
简介面向Unity开发者的AI功能集成演示包通过完整工程案例展示导航网格、行为树、机器学习等核心AI模块帮助初学者理解角色自动寻路、环境感知与复杂决策的实现方式也适合需要快速上手Unity AI工具链的进阶开发者。压缩包共178个文件以Unity工程结构组织包含场景资源asset、C#逻辑脚本cs、Unity序列化配置meta/unity以及DLL运行库等类型整体仅1.35MB便于直接加载项目对照代码学习。已有266人学习下载内容重点拆解NavMesh导航网格的烘焙与Agent参数设置、行为树节点的状态切换、ML-Agents强化学习智能体的训练流程同时涉及物理碰撞检测、视线与声音传感器、多智能体协作、动画状态机衔接以及性能优化技巧。读者结合场景配置与脚本逻辑能够掌握从感知、决策到动作执行的完整AI链路并可直接在项目基础上二次开发。1. 需求与目标一个能走完“巡逻-追击-搜索”闭环的Unity AI Demo很多人的第一个Unity AI Demo是“敌人看见玩家就直线冲过去”。但把这种Demo拿去给导师过目时大概率会被一句话问住“如果玩家绕到墙后面这个AI打算怎么办”答不上来是因为它缺少感知范围、缺少状态切换、缺少丢失目标后的搜索逻辑——问题不在“AI会不会追人”而在“AI到底有没有一套完整的决策链”。这套方案选择用“感知、决策、行为”三层结构基于有限状态机和NavMesh寻路把AI敌人完整串成“巡逻→发现→追击→丢失→搜索→回到巡逻”的行为闭环。它适合要交Unity AI课设或毕设的同学也适合想直接参考一套可扩展AI框架的初级开发者。2. 总体架构设计感知、决策、行为三层拆解与状态机选型如果只是图快最省事的是一个Update里同时写检测和追逐十几行代码就能让敌人“看起来会追人”。这个写法的后果是每加一个新行为都像拆炸弹比如想加“丢失目标后搜索”就得在追逐逻辑里塞条件判断最后整个Update变成一坨没人敢动的代码。所以这个Demo从一开始就把AI拆成三层。2.1 三层结构感知、决策、行为为什么必须拆开感知层只回答一个问题我有没有看到或听到玩家它不关心看到了该干嘛。决策层根据感知结果回答现在该巡逻、追击、搜索还是攻击它不关心路怎么走。行为层负责把决策执行出来用NavMeshAgent寻路、用Animator播放动画。三层各管一段替换代价很小——比如你想把导航方案从NavMeshAgent换成别的寻路插件决策层和感知层代码一行都不用动。我一般会把这种三层关系写进项目的架构说明里因为它不仅是代码结构也是答辩时的讲述顺序。实际编码时感知层的入口是EnemyPerception脚本决策层的入口是EnemyFSM脚本行为层由NavMeshAgent和Animator负责。三个组件挂在同一个敌人预制体上彼此通过公开方法调用不互相持有对方实现细节。如果不拆开会怎样最常见的情况是感知逻辑散落在各个状态里每个状态都自己写一段距离判断和射线检测改视野半径要找遍全项目搜索状态下想复用“能否看见玩家”的检测又得复制一遍代码。分层之后感知逻辑只有一处所有状态统一调同一个方法参数也集中在Inspector面板上改起来非常直观。2.2 选型对比有限状态机 vs 行为树 vs 效用系统设计AI决策层之前先做选型对比。这套Demo最终采用有限状态机原因是项目的核心行为链路——巡逻、追击、丢失、搜索、回归——是一条典型的线性时序用FSM表达最自然答辩时画一张状态转移表就能讲清全部逻辑。选型实现难度扩展方式答辩讲解成本适合场景有限状态机低枚举加switch即可加状态和转移条件状态多了会乱低状态图就是设计图行为链路清晰的课设/毕设首版行为树中需要节点基类和框架加节点逻辑高度复用中节点优先级容易成为黑匣子需要大量并行行为的完整AI系统效用系统/GOAP高需要评分策略和规划器加目标与评分项高推导过程难讲清楚策略型游戏NPC不适合课程设计在判断“够不够用、能不能落地”这两件事上FSM对这个Demo的复杂度来说是刚好够用的。行为树更强但它引入的节点框架、黑板数据和优先级调度会让一个本可以半小时讲完的课设变成两小时都讲不清楚的黑匣子。我的建议是首版用FSM交作业答辩被问到“怎么扩展”时说清楚未来可以在哪些状态节点上改造成行为树分支这比一上来就套行为树更经得住追问。2.3 状态流转巡逻、追击、搜索、攻击的完整闭环状态流转是这套AI的设计核心它决定了AI看起来“聪不聪明”。最普通的Demo只有两个状态看见就追追到就打玩家跑出视野就当无事发生。这套方案加入了一个容易被忽视的节点——“最后已知位置”玩家在追击途中丢失视野后AI并不直接回到巡逻而是先跑到玩家最后出现的位置再原地搜索几秒钟搜索无果才回去继续巡逻。这一个细节就能让AI从“脚本怪”变成“有记忆的角色”。完整的状态转移关系如下当前状态退出条件目标状态Patrol感知层检测到玩家ChaseChase与玩家距离小于等于攻击范围AttackChase持续loseSightTime秒未检测到玩家SearchSearch感知层重新检测到玩家ChaseSearch搜索计时结束PatrolAttack与玩家距离大于攻击范围Chase任意状态自身血量小于等于0Dead这个表格里最关键的是Search的两个退出条件一个是“看到了就继续追”一个是“没看到就回岗”。这两个条件必须同时存在否则AI会陷入两个极端——要么永远在搜索区发呆要么一进搜索就彻底忘记玩家。设计时我会把这张表直接放进项目文档的第一页因为所有代码分支、所有答辩提问都围绕这张表展开。3. 核心模块实现感知、状态机与NavMesh导航的落地代码从这一章开始进入具体实现。整个Demo由三个核心脚本构成EnemyPerception负责感知EnemyFSM负责决策NavMeshAgent负责移动Animator负责表现。先写感知模块因为它决定了决策层的输入质量。3.1 感知模块实现视野、听觉与遮挡检测感知模块不能只做一个“距离判断就看见”的伪检测。一个合格的敌人感知至少要有三重过滤距离判断、角度判断、遮挡判断。判断顺序按计算成本从低到高排列——距离是数学运算角度是三角函数射线检测最贵放在最后。如果先做射线检测每个敌人每帧都会对全场景发射射线帧率直接被拖垮。using UnityEngine; public class EnemyPerception : MonoBehaviour { [Header(视觉参数)] [Tooltip(总视野角度90表示正前方左右各45度)] public float viewRadius 12f; public float viewAngle 90f; [Header(听觉参数)] public float baseHearRadius 4f; [Header(检测层)] public LayerMask obstacleMask; public bool CanSeeTarget(Transform target) { if (target null) return false; Vector3 dirToTarget target.position - transform.position; float distance dirToTarget.magnitude; if (distance viewRadius) return false; float angle Vector3.Angle(transform.forward, dirToTarget); if (angle viewAngle * 0.5f) return false; // 射线起点抬高到头部高度避免被地面遮挡导致误判 Vector3 startPoint transform.position Vector3.up * 1.5f; if (Physics.Raycast(startPoint, dirToTarget.normalized, distance, obstacleMask)) return false; return true; } public bool CanHearTarget(Transform target, float noiseRadius) { if (target null) return false; float totalRadius baseHearRadius noiseRadius; return Vector3.Distance(transform.position, target.position) totalRadius; } }视觉参数里有个最常见的误区viewAngle是按总视野算还是按半角算。这套代码里写得很明确——viewAngle是总视野90度代表正前方左右各45度代码里用viewAngle * 0.5f做比较。很多人把这个参数理解为半角结果设置成60度却得到了120度的视野范围AI背对着玩家都能看到人。听觉参数noiseRadius由外部传入调用方根据玩家的移动速度计算噪声值站着不动为0走路为2左右跑步为5左右。可以用Mathf.Clamp(controller.velocity.magnitude * 0.5f, 0f, 6f)来映射。这个设计的价值在于能演示“潜行绕过AI”的核心玩法——玩家只要在视野盲区缓慢移动AI就听不到会让整个Demo的可玩性上一个台阶。3.2 状态机核心枚举驱动与统一感知入口决策层采用枚举加switch的结构。为什么不直接用抽象状态类因为这个项目的状态数量是六个枚举版本一个Update就能看完所有状态分支可读性极高抽象状态类适合状态数量超过十个、每个状态还自带复杂逻辑的大项目。课设和毕设阶段简单直观比架构上的优雅更重要。public enum EnemyState { Idle, Patrol, Chase, Search, Attack, Dead } public class EnemyFSM : MonoBehaviour { public EnemyState currentState EnemyState.Idle; [Header(引用)] private NavMeshAgent agent; private Animator animator; private EnemyPerception perception; private Transform player; [Header(核心参数)] public float loseSightTime 3f; public float searchDuration 5f; public float attackRange 2f; public float patrolWaitTime 1.5f; private float stateTimer 0f; private float loseSightTimer 0f; private int patrolIndex 0; private Vector3 lastKnownPlayerPos; private void Start() { agent GetComponentNavMeshAgent(); animator GetComponentAnimator(); perception GetComponentEnemyPerception(); player GameObject.FindWithTag(Player).transform; currentState EnemyState.Patrol; } private void Update() { stateTimer Time.deltaTime; // 统一在入口处计算一次感知结果各状态下不再重复检测 bool canSee perception.CanSeeTarget(player); bool canHear perception.CanHearTarget(player, player.GetComponentISoundEmitter().NoiseRadius); switch (currentState) { case EnemyState.Patrol: UpdatePatrol(canSee); break; case EnemyState.Chase: UpdateChase(canSee); break; case EnemyState.Search: UpdateSearch(canSee); break; case EnemyState.Attack: UpdateAttack(); break; } animator.SetFloat(Speed, agent.velocity.magnitude); } }有一个设计点值得专门说明感知结果在Update入口统一计算而不是各状态内部自己算。如果巡逻时算一次、追击时又算一次会产生一帧内前后感知结果不一致的问题状态切换会变得难以排查。统一入口的效果是所有状态看到的是同一帧的感知快照状态转移条件之间不会互相矛盾。loseSightTimer的设计也在这里埋下了核心逻辑Chase状态下玩家出视野不立即切换而是先计时连续loseSightTime秒看不到才算丢失。这个计时防止了玩家在墙角来回进出的瞬间AI在Chase和Search之间高频抖动。3.3 各状态行为落地巡逻、追击、搜索、攻击巡逻状态用巡逻点数组驱动。判断“是否到达目标点”时不要直接比较距离要用NavMeshAgent自带的remainingDistance和stoppingDistance这对组合。remainingDistance表示路径剩余长度它会绕开障碍物计算真实路径距离比直线距离更准确。private void UpdatePatrol(bool canSee) { if (canSee) { ChangeState(EnemyState.Chase); return; } if (agent.pathPending) return; if (agent.remainingDistance agent.stoppingDistance) return; // 到达巡逻点等待片刻后去下一个点 if (stateTimer patrolWaitTime) { patrolIndex (patrolIndex 1) % patrolPoints.Length; agent.SetDestination(patrolPoints[patrolIndex].position); stateTimer 0f; } }追击状态是整套Demo里最需要细抠逻辑的一段。它接收canSee作为参数如果仍然能看到玩家就持续更新lastKnownPlayerPos并清零loseSightTimer如果看不到就累计计时。当累计时间超过阈值先把Agent的目的地设成最后已知位置再切换状态。这个“先设目的地、再切状态”的顺序不能反因为Search状态的第一件事是判断是否到达目的地如果没设Search阶段会卡在“原地搜索”而不是“先赶过去”。private void UpdateChase(bool canSee) { if (canSee) { lastKnownPlayerPos player.position; loseSightTimer 0f; } else { loseSightTimer Time.deltaTime; } if (loseSightTimer loseSightTime) { agent.SetDestination(lastKnownPlayerPos); ChangeState(EnemyState.Search); return; } float distToPlayer Vector3.Distance(transform.position, player.position); if (distToPlayer attackRange) { ChangeState(EnemyState.Attack); return; } agent.SetDestination(player.position); }搜索状态逻辑上是Pursuit的延续先移动到最后已知位置到达后原地旋转观察searchDuration秒后如果还没有发现玩家就重置巡逻索引并切回Patrol。这里要再次注意canSee的处理——搜索期间如果玩家重新进入视野必须立即切换回Chase否则就会出现“玩家站在AI面前AI还在原地转圈”的返工现场。private void UpdateSearch(bool canSee) { if (canSee) { ChangeState(EnemyState.Chase); return; } // 还没走到最后已知位置继续走 if (agent.remainingDistance agent.stoppingDistance || agent.pathPending) return; // 到达后原地旋转观察 transform.Rotate(0f, 45f * Time.deltaTime, 0f); if (stateTimer searchDuration) { patrolIndex 0; ChangeState(EnemyState.Patrol); } }攻击状态用一个独立的EnemyAttack组件承载不在FSM里写伤害逻辑这是为了让FSM只关注状态流转。攻击组件用Time.time配合冷却计时比协程更直观也不容易在脚本失活时漏掉协程的清理。public class EnemyAttack : MonoBehaviour { public float damage 10f; public float coolDown 1.2f; private float lastAttackTime -99f; public bool TryAttack(Transform target) { float dist Vector3.Distance(transform.position, target.position); if (dist 2f) return false; if (Time.time - lastAttackTime coolDown) return false; lastAttackTime Time.time; // 在此处调用玩家扣血接口和攻击动画触发 return true; } }3.4 动画同步与表现决策层与表现层解耦动画处理上最容易犯的错是“让动画状态跟着AI状态走”——AI的状态切到攻击就立刻播放攻击动画AI切回巡逻又强行打断当前动画。这种强耦合会导致动作穿模、动画闪烁。正确做法是Animator自行根据动画参数管理切换AI代码只负责更新参数。核心的同步参数是Speed用agent.velocity.magnitude映射表示当前移动速率。攻击用SetBool(IsAttacking, true)配合面板过渡死亡用SetTrigger(Death)。这套方案在Unity默认的Animator中Base Layer放移动动画Idle/Walk/RunAttack放在独立一层这样移动和攻击可以叠加播放过渡时间建议调到0.1秒以内避免攻击动作切得拖泥带水。4. 场景搭建与配置空场景到可演示Demo的完整步骤代码写完之后场景配置决定了这个Demo能不能真正演示出“闭环”。很多人的AI逻辑没问题但场景是一马平川的平地玩家永远处于AI视野内丢失目标后的搜索逻辑根本触发不了——这才是整段演示最大的翻车点。4.1 场景布局与层级结构回字形地图与巡逻点摆放测试场景不要做成空旷平地应该做成“回字形”走廊布局。回字形的核心价值是提供墙角——有了墙角玩家才能快速脱离AI视野AI才会触发Search状态整个行为闭环才完整。地图尺寸建议控制在20米乘20米太大导致玩家找AI困难太小导致AI的视野范围覆盖全图。场景层级建议保持这样的组织方式GameManager -- 负责玩家重生、AI重置 ├── Player -- 第三人称角色控制器挂ISoundEmitter ├── Enemy -- 敌人预制体实例 ├── Ground -- 地面勾选Navigation Static ├── Walls -- 回字形墙体勾选Navigation Static ├── Obstacles -- 可破坏箱子挂NavMeshObstacle组件 └── PatrolPoints -- 3到4个空物体作为巡逻点均匀分布在走廊巡逻点一定要用空物体而不是直接在代码里写死坐标。这样在Inspector里拖动巡逻点位置就能调整AI路线不需要改代码。巡逻点至少3个均匀分布在走廊两侧模拟保安巡查路线。4.2 NavMesh烘焙静态障碍物与烘焙参数调法NavMesh烘焙是Unity AI Demo里最容易被跳过但最影响体验的一步。未烘焙的场景里NavMeshAgent会直接掉到地面以下或原地不动。烘焙操作在Navigation窗口完成选中所有静态障碍物和地面勾选Navigation Static然后点击Bake。新版Unity建议直接使用NavMeshSurface组件挂载到Ground物体上配置后自动生成导航面推荐工程使用这种方案因为配置会跟随场景保存不容易丢失。烘焙参数有四个需要关注Agent Radius导航半径、Agent Height导航高度、Max Slope最大坡度、Step Height台阶高度。对课设级Demo一组稳妥的起步值是Radius 0.3到0.5、Height 1.8、Max Slope 45、Step Height 0.4。如果AI频繁绕远路说明Radius设置得偏大如果AI在斜坡上反复腾挪说明Max Slope太小。动态障碍物的处理逻辑要分清玩家和敌人都是动态移动的不需要烘焙它们依赖Agent移动会移动的箱子、门才需要挂NavMeshObstacle组件在运行时动态雕刻导航面。这个区别在答辩时经常被问到建议提前背熟。4.3 敌人预制体组件挂载顺序与赋值清单敌人预制体的组件挂载顺序影响代码运行时的GetComponent查找路径。推荐按这个顺序组织组件类型关键参数AIControllerEnemyFSM脚本loseSightTime3, searchDuration5, attackRange2EnemyPerception脚本viewRadius12, viewAngle90, baseHearRadius4EnemyAttack脚本damage10, coolDown1.2NavMeshAgentUnity组件Speed3.5, Angular Speed360, Stopping Distance1.2AnimatorUnity组件挂载人形或低模角色动画CapsuleColliderUnity组件碰撞体Center抬高到1米左右避免卡地这里有个极易踩坑的参数NavMeshAgent的Stopping Distance不要设成0。设为0会让Agent在到达目标点后还在反复调整位置站在原地轻微抖动看起来像发了癫痫。常规做法是设为1.0到1.5让AI在目标点附近稍微留出一点空隙动画过渡也更自然。4.4 玩家控制与数值调优怎么让AI的难度能讲明白玩家控制脚本不需要复杂一个带重力检测的CharacterController就够用。关键在AI数值与玩法感受的对应关系需要调参时知道调哪个参数会产生什么效果。调整目标调整参数调整方向让AI更容易发现玩家viewRadius增大到15以上让AI侧后方不再容易被偷袭viewAngle从90度增大到120度让潜行玩法更明显baseHearRadius减小到2跑步声依然暴露让丢失后搜索的演示更充分loseSightTime设置成2到3秒太短会抖动让搜索看起来更执着searchDuration增大到8秒以上调参建议用白盒场景固定跑三遍玩家从同一位置出发分别记录“被AI发现的时间”“被追到墙角的时间”“能否通过蹲行绕后成功”。三组数据一对比参数效果一目了然答辩时还能拿出实测数据做底气比说“手感不错”有说服力得多。5. 避坑指南AI Demo里最容易翻车的五个问题与排查方法这套方案在实现过程中遇到过几个高频问题列出来给读者做一个排查清单。每个问题都按“现象→原因→解决”的顺序写方便遇到时快速定位。5.1 敌人在墙角原地抖动不肯走向玩家现象追击阶段AI站在墙角附近不断小幅度前后晃动看起来像卡住动画在走与站之间反复横跳。原因NavMeshAgent与Rigidbody同时控制位置。NavMeshAgent更新位置时带有刚体的碰撞体被物理系统推回产生“刚体推走、Agent拉回”的拉锯式抖动。另一个常见原因是Stopping Distance设成了0Agent到达目标点后还在不停修正位置。解决如果敌人不需要物理受力就不要挂Rigidbody纯用NavMeshAgent驱动位置和旋转。必须在代码中把agent.updatePosition和agent.updateRotation保持默认开启关闭它们会让Agent完全失去导航效果。这个坑的血泪经验是先检查组件再怀疑代码八成是组件配置问题而不是逻辑Bug。5.2 感知模块明明写了角度判断AI还是背对着玩家也能看到人现象把viewAngle设置为60度后AI背对玩家时依然能触发追击好像角度判断没有生效。原因对总视野和半角的概念理解颠倒了。如果代码里用angle viewAngle做判断那么viewAngle60表示正前方左右各60度实际视野为120度当viewAngle填了120度实际就变成了240度接近全向。另一个隐藏原因是Transform.forward没有对齐模型正脸方向模型的前方与胶囊体朝向偏转。解决统一约定viewAngle是总视野角在代码里用viewAngle * 0.5f做半角比较同时在这个位置加注释防止自己以后改错方向。检查模型根节点的前方是否与正脸一致不一致时手动调整模型旋转不要让代码迁就模型的朝向问题。5.3 Chase和Search疯狂横跳AI在原地抽搐现象玩家绕到墙边之后AI在追击和搜索两个状态之间快速切换每秒换好几次完全看不出决策逻辑。原因丢失目标的判定没有做延迟。玩家只是短暂经过视线盲区比如走过一扇窗AI立即判定丢失并切到Search下一秒玩家又出现在视野里又切回Chase。状态切换本身没有保护时间导致抖动。解决在Chase状态中引入loseSightTimer连续loseSightTime秒没检测到玩家才进入Search。这个计时清零的时机很关键只要重新看到玩家立即清零。我在第一版里就犯了没清零的错结果AI一旦进入搜索后即使重新看到玩家也要等计时结束表现像是无视敌人。调试时用Debug.Log在状态切换处打印状态名能最快定位抖动是哪个方向在反复切换。5.4 烘焙后的NavMesh把高台阶当悬崖AI绕远路现象AI在平地走到台阶前停下宁可绕一大圈也不走台阶整个巡逻路线被截断效率极低。原因Max Slope和Step Height设置不合理。NavMesh烘焙时会把超过Max Slope的斜面剔除把高于Step Height的台阶判定为不可通行。如果地图里有30度的缓坡却把Max Slope设成20烘焙结果就会断出一条路。解决先查看导航面板的可视化结果确认哪些区域没有导航面再根据地形调整参数。正常室内场景起步推荐Max Slope 45、Step Height 0.4。注意修改参数后必须重新Bake不要只调参数不改数据这是新手很常见的翻车操作——参数改完了导航网格还是旧的。5.5 动画慢半拍AI已经被打死跑步动画还在播放现象敌人血量为0状态机已经进入Dead但模型还在播放跑步或攻击动画过了一两秒才切换。原因Animator的参数没有在状态切换的瞬间更新。FSM里只改了EnemyState枚举没有同步把Animator的Speed设为0也没有触发Death的Trigger。Animator在默认过渡下需要等当前动画片段播放到退出时间才会切换。解决在FSM的状态切换入口增加一个表现层刷新方法当切换到Dead时调用animator.SetTrigger(Death)并且animator.SetFloat(Speed, 0f)一般做法是写一个SyncWithAnimator(EnemyState state)方法所有状态切换都走它避免某处忘记同步。Attack层的动画也要设置过渡时间不超过0.1秒否则攻击动作会拖到天荒地老。6. 验收与答辩把Demo讲成一套能经得住追问的完整方案6.1 三个必测场景与通过标准演示前的验收不能只在编辑器中随便跑两步就结束。我建议准备三个固定测试场景每个场景对应一条核心行为链路当场逐个演示每一步都有明确的通过标准。测试场景操作方式通过标准开阔直线追击玩家从AI正前方20米处跑过AI在视野范围内立刻追击保持3米内跟随不撞墙不穿模障碍物绕行玩家跑到墙体后方AI沿NavMesh路径绕行墙体不走直线穿墙路径平滑回字形丢失搜索玩家绕过墙角后原地蹲下不动AI追到墙角后到达最后已知位置原地搜索5秒未发现后回巡逻点第三个场景是整场演示的高光点。演示前要刻意在PatrolPoints旁边留一个墙角让玩家绕过去之后AI恰好丢失视野完整呈现“追击→丢失→搜索→回归”这条链路。如果演示场地是空旷平地这条链路永远触发不了答辩时就只能讲“会追人”这一个点。6.2 答辩梳理与高频问题应对答辩时的演示顺序建议固定为三分钟讲完一条故事线先打开状态转移表讲清楚AI有哪些行为和它们的触发条件再进游戏演示“发现玩家→追击→墙角丢失→搜索→回归”的完整过程最后切到代码面板带看感知层的三重过滤和状态机的统一感知入口。不要从代码开始讲评审关注的是“设计的思路”不是“代码的堆叠”。高频问题和对应的应答要点为什么选FSM不选行为树因为当前行为是线性时序FSM的状态图本身就能当设计文档如果后续需求升级可以在Search状态上挂一个子行为树保留扩展接口。感知为什么不是每帧对全场景做检测三层条件过滤按计算成本排序距离判断最廉价先执行射线检测最昂贵放最后。这个顺序写进代码注释里答辩时直接看注释就能讲。动态物体怎么处理场景静态物体靠烘焙移动的箱子和门挂NavMeshObstacle做动态雕刻敌人之间的碰撞靠RVO避障或Agent Radius控制。整套Demo默认参数表要提前打印或记在笔记本里viewRadius12viewAngle90baseHearRadius4loseSightTime3searchDuration5attackRange2。这些数值随手能报出来会显得对项目的掌握程度完全不同。我之前做这套Demo时也翻过车巡逻点数组忘记在Inspector里指定结果AI在原点疯狂原地转圈排查了半小时才发现patrolPoints长度为0。从那以后我每次给AI加新行为都强制先画一遍状态流转表、把所有进入条件和退出条件列清楚再动手。代码可以乱表格必须先干净这个习惯帮我避掉了无数个返工夜。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站