
简介这套Unity AI演示工程面向Unity开发者和游戏AI初学者以可运行的示例项目完整展示在引擎中接入人工智能的流程从角色寻路、行为决策到环境感知覆盖游戏AI的常见落地场景。压缩包共178个文件以C#脚本、Unity资源文件、配置元数据及工程文件为主同时包含少量动态库依赖整体仅1.35MB体量小巧适合直接打开对照学习。目前已有266人学习下载入门参考价值较高。项目重点演示NavMesh导航网格搭建、行为树节点设计、动画状态机切换以及传感器感知、物理模拟、碰撞检测和多智能体协作并给出ML-Agents强化学习扩展思路。通过分析场景与脚本能快速理解AI角色从感知、决策到动作执行的完整链路还可借鉴LateUpdate调用、动作预测平滑等性能优化做法迁移到自己的游戏项目中方便二次开发时直接复用。1. Unity AIdemo 到底是什么一份能跑的导航与状态机参考做 AI 逻辑的时候最怕的不是写不出行为而是调试状态机的时候一改参数就跑飞。这份 Unity AIdemo 就是把 AI 的常见套路打包好——导航网格、感知、状态机全都有导入后能直接跑适合刚接触 unity AI 或者想找人形 NPC AI 参考的开发者。它解决的是三件事让敌人追你、让 NPC 巡逻、让 AI 听到动静回头查。里面的脚本拆得比较分散刚好适合拿来当模板而不是一个大黑匣子。引擎版本建议选带内置 NavMesh 的版本避免为烘焙和 Agent 单独装包。整套 Demo 只有几个场景核心代码量不大但每一块都卡在点上移动不是凭空走而是踩在导航网格上感知不是扫一圈就完而是结合距离、角度和遮挡判断行为逻辑则是用一个常见状态机串起来。对你来说这份资源最有价值的地方是它提供了一个能立刻改参数观察结果的起点。2. 先看核心链路导航网格、AI感知与状态机的协作方式你要进入一个项目第一步不是打开场景就点 Play而是先把它分成三个互相独立的部分移动、感知、决策。这个 Demo 的好看之处就在这——三者没有互相写在同一个类里而是由 NPC_Controller 作为调度器把导航组件和感知组件的结果合并到状态机上。2.1 导航网格与 NavMeshAgent移动不是“走过去”而是“算出来”AI 角色的移动是直接用的引擎内置 NavMesh 系统。导航网格本身不是运行时生成的是一个离线烘焙出来的数据块。这个 Demo 里有一个 surface 对象表面烘焙范围覆盖整个演示房间烘焙出来的数据只允许 AI 走到标记为可行走的区域不会出现穿墙或者走到桌子底下的情况。初始化并设置第一个目标点的代码是这么写的using UnityEngine; using UnityEngine.AI; public class NPC_Controller : MonoBehaviour { private NavMeshAgent agent; public Transform[] patrolPoints; // 巡逻点Inspector里拖入 void Awake() { // 获取导航组件后面所有移动都交给它 agent GetComponentNavMeshAgent(); agent.speed 2.2f; agent.angularSpeed 240f; agent.stoppingDistance 0.4f; } void Start() { // 开场先往第一个巡逻点移动 if (patrolPoints ! null patrolPoints.Length 0) { agent.SetDestination(patrolPoints[0].position); } } }这段代码首先把移动参数、巡逻点引用前置到 Awake 中保证任何使用 agent 的模块都不会拿到空引用。speed 控制移动速度angularSpeed 影响转向灵敏度stoppingDistance 是角色到达目标点后停止的距离。注意这里的 stoppingDistance 对近战型敌人要控制在 0.3 到 0.6对远程型则要更大不然会出现 AI 贴着目标转圈但不出手的问题。知识点卡在这里SetDestination 并不是把一个点“拖”过去而是在导航网格上寻找可达路径。如果你传入的坐标点不在地网格上返回值会是 false角色原地不动。所以这个 Demo 里专门在巡逻点放置了地面标记对象确保每个目标点都被烘焙数据覆盖这也是新手最容易忽略的事。2.2 感知系统视觉锥与基于距离的听觉判断感知部分拆成两个脚本VisionSensor 和 AudioSensor。VisionSensor 负责视觉AudioSensor 处理声音事件。视觉不是用相机实现的用的是典型的“视野锥”算法每隔 0.15 秒在角色前方画一个扇形区域通过角度和距离判断目标是否在视野里。public bool IsTargetInView(Transform target, float viewRadius, float viewAngle) { if (target null) return false; // 先做距离粗筛比角度判断便宜 Vector3 offset target.position - transform.position; float sqrRadius viewRadius * viewRadius; if (offset.sqrMagnitude sqrRadius) return false; // 再做角度精确判断视线方向与目标方向夹角 float angle Vector3.Angle(transform.forward, offset.normalized); if (angle viewAngle * 0.5f) return false; // 最后做遮挡检查避免隔着墙也能看到 if (Physics.Raycast(transform.position Vector3.up * 0.5f, offset.normalized, out RaycastHit hit, viewRadius)) { return hit.transform target; } return false; }先算模长的平方减少开方调用再用 Vector3.Angle 判断是否落在视野锥内最后用射线过滤遮挡。这里把射线的起点抬高 0.5 米避免从地面扫到自己的 Collider 导致误判。viewAngle 是锥体张角传 120 就表示单侧 60 度。真正在实际调试里需要改的不是 viewRadius而是这条射线起始高度角色蹲姿和站姿对视野影响比想象大得多。听觉判断相对简单场景里放了一个事件源当玩家跑动时触发一个带半径的 SphereColliderAudioSensor 接到 OnTriggerEnter 事件后通知状态机进入搜索状态。这套实现的好处是听觉是事件驱动不是每帧轮询不消耗 Update 开销。2.3 有限状态机巡逻、追踪、搜索与返回的切换逻辑状态机是这部分的重头戏。状态定义在枚举里当前状态由 NPC_Controller 统一管理。四个状态分别是Patrol巡逻、Chase追踪、Search搜索、Return返回巡逻起点。每次 Update 都走一次状态机但不允许同一个逻辑帧内连续切换两次状态避免抖动。public enum AIStateType { Patrol, Chase, Search, Return } private AIStateType currentState AIStateType.Patrol; void Update() { switch (currentState) { case AIStateType.Patrol: PatrolUpdate(); break; case AIStateType.Chase: ChaseUpdate(); break; case AIStateType.Search: SearchUpdate(); break; } } void PatrolUpdate() { // 到达当前巡逻点后切换到下一个点 if (agent.remainingDistance agent.stoppingDistance) { MoveToNextPatrolPoint(); } } void ChaseUpdate() { // 追踪优先级最高目标丢失后不直接巡逻而是先搜索 if (!visionSensor.IsTargetInView(player, viewRadius, viewAngle)) { currentState AIStateType.Search; searchRemainTime 3.0f; } else { agent.SetDestination(player.position); } }关键在 ChaseUpdate 的丢失处理没有立刻回到 Patrol而是先进 Search 状态站在最后看到玩家的位置上原地转 3 秒再用 OnTriggerEnter 的听觉线索决定下一步。这个“丢失后先搜索”的设计比直接掉头巡逻在真实感上提升很多也是很多大厂 NPC 的标准做法。抄的时候要注意状态机里所有状态必须有超时保护比如 Search 状态倒计时跑完自动回 Patrol而不是永远卡在一个死分支上。3. 从零跑通 Demo导入工程、烘焙导航网格与关键配置这章能把前面讲的逻辑落到编辑器里。从导入到 Play一共分三步确认目录导入完整、烘焙导航网格、给角色挂组件。3.1 导入后的目录结构先把每个文件的功能对应上导入完成后你会看到以下几个关键目录。我一般建议先对照一遍避免出现脚本丢失或场景引用断掉的情况目录或文件作用是否需要改Assets/Scenes/AI_NPC_Demo.unity主演示场景所有 AI 对象都在场景里直接改Assets/Scripts/AI/NPC_Controller.cs状态机调度入口核心文件Assets/Scripts/AI/VisionSensor.cs视野锥检测参数调整Assets/Scripts/AI/AudioSensor.cs听觉触发检测一般不改Assets/Scripts/Player/Player_Move.cs玩家控制用于触发 AI 行为可换导入后如果看到场景里的 AI 角色是粉红色大多是材质或者着色器不兼容。这时候先别急着去装额外资源在 Project 面板找到预设体材质把着色器换成引擎内置的 Standard 即可。这个坑在 Demo 资源里出现概率极高不是代码问题。3.2 烘焙 NavMesh 的三个关键步骤打开 Window - AI - Navigation选择 Navmesh 烘焙页面。这个 Demo 已经提前把场景里的地面、墙壁、障碍物标记为 Navigation Static 了所以你只需要做三步在 Bake 面板确认 Agent Radius 为 0.5Agent Height 为 1.8这个 Demo 的角色身高默认 1.8 米。把 Step Height 设为 0.3意味着角色能正向上 30 厘米的台阶不要设得太大否则会爬上不该爬的桌子。点击 Bake看到场景上出现蓝色半透明区域就算成功。这一步会出现一个常见误会很多人以为调大了 Agent Radius 就能让 AI 走更宽的路实际上 Agent Radius 是圆柱体的半径调大了反而把窄通道堵死。你要理解这个值代表 AI 角色的“肚子”不是路的宽度。数值变成 0.8 以上后大多数门洞都会被判定不可通过AI 会绕远路甚至卡在门口。我给这个 Demo 调参数时的经验是先按角色实际尺寸调 Radius 和 Height再看看 Step Height 和爬坡角度最后才考虑偏移。顺序最好不要反不然你以为是路的问题其实是角色尺寸把它堵住了。3.3 配置 AI 角色从空物体到完整状态机场景中有一个名为 Enemy_Robot 的预设体它挂在 NavMeshAgent、VisionSensor、NPC_Controller 三个核心组件。组件配置按下面的表走组件关键属性推荐值备注NavMeshAgentSpeed / Angular Speed2.2 / 240追踪时可以动态改速度NavMeshAgentStopping Distance0.4与攻击距离匹配VisionSensorView Radius / View Angle6 米 / 120 度不要低于 5 米否则 AI 像瞎子VisionSensorDetection Interval0.15 秒越低越吃性能NPC_ControllerPatrol Points4~6 个点用空物体摆出路线挂上去以后把巡逻点拖进数组的顺序很关键。脚本是按下标顺序走通的当前点完成后寻找下一点。摆点时不要用同一个平面高度最好形成折线AI 走起来才有“在巡逻”的感觉而不是一条直线来回走。提示Play 之后如果发现 AI 往错误方向跑先检查两个地方。第一导航网格是否成功烘焙蓝色区域是否覆盖巡逻点第二巡逻点是否放在了地上而不是在半空中。巡逻点悬空是新手最容易踩的问题因为 NavMeshAgent 会找不到落点原地旋转。4. 调参落地套路速度、视野与巡逻路线怎么改不翻车在 Demo 能跑了之后调试才是大头。这一章直接对着具体场景讲参数避免你打开 Inspector 后不知道该动哪个。4.1 速度与转向参数移动的“手感”和“物理感”先把角色速度拆成两类移动速度和角速度。移动速度直接决定游戏难度角速度则影响转向时是否会出现“漂移感”。void UpdateChaseSpeed(bool isChasing) { // 追人时加速度快一点让刺激感上来 if (isChasing) { agent.speed Mathf.Lerp(agent.speed, 3.5f, Time.deltaTime * 2f); } else { agent.speed Mathf.Lerp(agent.speed, 2.2f, Time.deltaTime * 2f); } }这里用了 Mathf.Lerp 做平滑过渡不至于让速度从 2.2 瞬间跳到 3.5 出现“瞬移”感。如果你觉得角色的表现像踩了冰面主要查 angularSpeed当角速度低于 120 时角色走到目标点会经历夸张的“头先转向、身体再跟上”过程。调试小技巧把 NavMeshAgent 的 AutoBraking 关掉配合更大的 StoppingDistanceAI 会在到达目标点前减速看起来更像人而不是撞墙的机器人。速度参数不是越大越好。巡逻状态保持 2.2 左右追踪状态 3.5 是大多数演示场景的合理区间。如果地图很小3.5 会导致 AI 反复冲过目标点再回头看起来非常笨。地图大就反过来低速会让玩家觉得 NPC 在散步。4.2 视野锥与感知半径不要只调一个“视野距离”视野锥有两根轴半径和角度。追人时半径可以开大但角度不要超过 150 度因为背后还能感知到目标会显得很假。实战中我一般会额外加一个“警报延迟层”目标进入视野后需要连续多次确认才切换状态避免因为目标瞬移到一个柱子后就立刻丢失追踪。if (framesInView 3) { lastKnownPosition player.position; currentState AIStateType.Chase; } else { framesInView visionSensor.IsTargetInView(player, viewRadius, viewAngle) ? framesInView 1 : 0; }这段代码解决的是视野判断的抖动问题。当目标在边缘或障碍物后面快速进进出出时直接切状态会抖成“抽搐”至少连续 3 帧确认是一个便宜且可靠的方案。代价是玩家在快速冲刺时AI 的反应会慢一帧实际感受基本无感。缺省情况 framesInView 初始化为 0。感知半径和视野角度需要配合地图尺寸来定。6 米半径、120 度角在 10 米宽的房间里会太灵敏AI 站门口就能看到房间里所有动静。我一般把半径设成地图最长边的四分之一角度保持在 110 到 130 之间。4.3 巡逻路线设计路径点、死胡同和回归逻辑巡逻路线是这个 Demo 里最具迷惑性的部分。因为看起来只是摆点但点与点之间的距离、高度差和视线遮挡都影响最终表现。一个常见坑是把巡逻点摆成环形AI 一路走到底后回到起点形成单调循环。改成两三点折返式路线比如点 A 到点 B 到点 C 再回到点 A会让 NPC 有一种“正在检查区域”的感觉。把巡逻点做成 GameObject然后挂一个简单的 Gizmo 脚本能在场景视图中画出连线void OnDrawGizmos() { Gizmos.color Color.yellow; for (int i 0; i patrolPoints.Length; i) { if (i 1 patrolPoints.Length) { Gizmos.DrawLine(patrolPoints[i].position, patrolPoints[i 1].position); } } }这个脚本本身不参与运行时逻辑只提供可视化的调试。实际调路线时我一般会把最后一个巡逻点和第一个点重合避免 AI 巡完一圈后站在终点发呆。另一个小习惯巡逻点的空物体位置不要放在碰撞体内部不然导航网格会把它判定为不可达。5. 避坑排查五个最典型的 Unity AI 翻车点任何 Demo 跑起来不难难的是出了问题之后你知道去哪里查。这章记录我复盘这个 AI demo 时踩过的五个高频坑每个都按现象、原因、解决来拆。5.1 高频问题清单现象、原因与对应解法问题一角色到达目标点后原地转圈就是不进入下一状态。现象agent.remainingDistance 一直在 0.3 和 0.4 之间跳动目标点看起来已经到了。 原因stoppingDistance 设置得太小角色还没走到判定区内就已经被物理碰撞挡住系统反复尝试走最后一点距离。 解决把 stoppingDistance 提高到 0.5 以上或者直接用 Agent.Raycast 判断是否离目标点小于 0.3。不要在 remainingDistance 上做精确大于小于比较浮点误差会让你怀疑人生。问题二视野锥能看到目标但状态机不触发追踪。现象Gizmos 已经画出视野范围目标也在扇形内但 AI 没反应。 原因射线遮挡检查把自己身上的 Collider 挡了射线从角色中心发出时先撞到自己。 解决射线起点改为头部高度发射前用 Physics.QueriesHitTriggers 或者给自身 Collider 加忽略层。这个 Demo 里已经做了起点上移但只要你自己改过角色尺寸就要重新调。问题三AI 在追踪和巡逻之间“震荡”。现象AI 刚跑两步就回到巡逻过一秒又开始追。 原因目标位置在导航网格边缘来回变化或者是检测逻辑只有单帧确认。 解决用连续几帧确认的模式并且追踪状态下不要每帧更新 SetDestination改成 0.2 秒更新一次。同时在目标离开视野后至少延迟 1 秒再回巡逻。问题四AI 在迷宫地图里绕远路明显不聪明。现象明明有一条直线路径AI 却绕了整个房间一圈。 原因导航网格的 Agent Radius 太大或者地图上的窄通道没有被正确标记为可行走。 解决把 Agent Radius 降到 0.3 到 0.5重新烘焙。再检查一些低矮障碍物是不是被误标成了静态遮挡物把不必要的静态标记去掉。问题五加载场景后 AI 全部朝同一个方向跑。现象所有 NPC 开场直奔地图右上角完全不理会巡逻点。 原因巡逻点 GameObject 被误删脚本的 patrolPoints 数组长度为 0Start 里试图访问第一个元素导致初始化失败。 解决让 Start 函数在数组为空时使用空物体占位并用 Debug.Log 打开提示。这属于写代码时的防御性习惯Demo 里特意把这个坑留出来了。5.2 性能排查NavMesh 帧消耗与脚本轮询的取舍除了功能问题性能是用户拿来就跑的第二个坎。NavMeshAgent 本身每帧都要做路径更新如果场景里放 10 个以上 AI物理引擎和寻路计算会同时吃帧。我在测试时习惯打开 Profiler盯着两件事第一NavMeshQuery 的调用量第二Update 里的 SetDestination 调用频次。如果 Profiler 显示 CPU 时间集中在 NavMeshQuery 上优先考虑降低 SetDestination 更新频率或者把部分 AI 放到协程里做间隔更新。视野检测的轮询最容易掩盖真实性能问题实际上一秒 12 次足够不需要每帧都扫。把这些扫描操作放到协程IEnumerator VisionLoop() { while (true) { // 每0.08秒做一次视野检测降低更新频率 yield return new WaitForSeconds(0.08f); CheckVision(); } }协程与每帧 Update 的区别在于可控性。把视野检测从 Update 中拆出去后AI 角色数量多两倍也能保持稳定。记住物理 Trigger 和 Raycast 是最贵的能用距离判断的先做粗筛能按帧率的才按帧率。6. 进阶改造用一棵行为树替换固定状态机Demo 里的状态机适合作为模板但它有个明显的边界当 AI 新增要互动的行为比如开箱子、与队友配合状态机会膨胀成一张混乱的箭头网。那次我把巡逻、追踪、攻击、开箱四个行为加起来后发现状态切换条件互相重叠于是决定改成行为树。6.1 状态机的扩展性弱点状态机适合线性行为而当你有“先看有没有敌人再看有没有障碍然后决定绕路还是攻击”这类嵌套逻辑时行为树能让优先级看得一清二楚。改动时可以保留原来的 NavMeshAgent 和 VisionSensor只替换决策层。6.2 最小行为树脚本public enum NodeState { Success, Failure, Running } public abstract class BTNode { public abstract NodeState Execute(); } public class SequenceNode : BTNode { private BTNode[] children; public SequenceNode(BTNode[] nodes) { children nodes; } public override NodeState Execute() { // 顺序执行一旦失败就返回失败全部成功才成功 foreach (var node in children) { var state node.Execute(); if (state ! NodeState.Success) return state; } return NodeState.Success; } }SequenceNode 按顺序执行所有子节点遇到失败就提前返回。改造时把“巡逻到目标点”当成叶子节点把“判断视野内是否有玩家”当成条件节点根节点是一个并行或选择节点。这些最小节点段可以用 100 行内实现整个 AI 行为就完全变成了可配置的数据而不是写在状态机里的分支判断。6.3 验证改造结果改完后验证方法很简单把原来跑到场景里手动测试的时间换成在固定帧里打印状态机增量日志看每个行为的进入次数。如果一道门让 AI 绕路了 5 次但日志只有 2 次记录说明行为树的条件判断不够严。从那以后我每次进一个新的 AI Demo都会先看它是状态机还是行为树再看感知层和决策层有没有分开。不是状态机不好而是在真正要长期维护的项目里行为树的数据化表达能让我少撑很多夜。希望帮到你。本文还有配套的精品资源点击获取