ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Unity 6 从零复刻 Unreal EQS:环境查询系统设计与实现

Unity 6 从零复刻 Unreal EQS:环境查询系统设计与实现 这次我们来看一个很经典但 Unity 侧没有内置的 AI 玩法Unreal Engine 里的 EQS也就是 Environment Query System环境查询系统。很多射击、潜行、生存游戏里AI 不会傻乎乎直线冲锋而是会找掩体、绕后、选制高点看起来很聪明背后就是这套“生成候选位置 多维度打分 选择最优解”的逻辑在驱动。比较麻烦的是Unity 里没有完全等价的现成系统。行为树容易找NavMesh 寻路也成熟但“让 AI 在运行环境里动态评估多个候选点再挑一个最好的点”这层能力通常要自己搭。不过思路本身不复杂完全可以在 Unity 6 里从零复刻出一套轻量 EQS接上 NavMesh、视线检测、距离评分和可视化调试让 AI 真正学会“看环境选位置”。这篇文章会给出完整的系统设计方案、核心 C# 代码、候选点生成与测试逻辑、Gizmos 调试流程、性能优化方向以及常见问题排查清单。开发环境以 Unity 6 AI Navigation 包为例不需要第三方 AI 插件核心逻辑用纯 C# 就能跑通。如果你最近在做 AI 射击、潜行、恐怖游戏或者正在研究行为树和寻路之外的“环境感知决策”这篇可以直接收藏。另外先说明一点本篇文章是结合相关 Unity 6 复刻 EQS 教程思路整理的实现方案不是原视频或付费内容的直接搬运。复现过程中的代码和场景设计都属于通用工程思路但如果你后续要引用别人的脚本、模型或视频片段仍然要注意来源授权。1. 核心能力速览能力项说明项目类型Unity 6 技术方案 / EQS 系统复刻对应引擎Unity 6推荐 6000.0 及以上版本核心依赖UnityEngine.AI、AI Navigation 包ai.navigation主要功能候选点生成、距离测试、视线测试、NavMesh 可达性测试、掩蔽评分、AI 目标选择交互方式组件配置 C# 代码调用调试方式Gizmos 可视化候选点与分数、Debug 面板硬件要求纯 CPU 计算不需要 GPU建议 8GB 内存以上是否支持批量支持多 Agent 查询但需要分帧或 Job System 优化是否支持 APIUnity 内部 C# 接口可直接被行为树或状态机调用适合场景射击 AI 找掩体、潜行 AI 规划路线、收集型 AI 选目标点这套方案的核心价值不是做一个“千人同屏 AI”而是把 Unreal EQS 的设计思路搬到 Unity先生成大量候选位置再用可配置的测试项逐个打分最后选出得分最高的点。后续加新玩法只需要新增一个 Test 子类不需要改整体框架。2. EQS 原理拆解查询、生成器、测试与评分想在 Unity 里复刻 EQS首先要理解 Unreal 那边 EQS 的五个核心组成部分。2.1 Query查询一个 Query 是完整的查询配置包含上下文、生成器和测试列表。你可以把它理解成“一次完整决策”的蓝图。AI 需要一个目标点时向 EQS 发起一次查询系统依次完成候选点生成和评分。2.2 Context上下文Context 定义查询的“基准位置”。常见的有执行查询的 Pawn 自身位置。当前追逐目标的 Actor 位置。某个自定义锚点比如玩家上次被发现的位置。多个目标的平均位置或最近位置。在 Unity 实现中我会用一个轻量的EQQueryContext类来保存这些数据。2.3 Generator生成器生成器负责产生候选点或候选 Actor。Unreal 常见类型包括同心圆生成器围绕基准点生成一圈圈均匀分布的点。格栅生成器在一定范围内生成网格状点。可点击点生成器直接收集场景中的相关 Actor。候选点的数量直接决定查询开销所以生成器通常要能配置半径、点数和分布密度。2.4 Test测试每个候选点都要经过一个或多个测试。测试有两种模式Filter 模式不满足条件直接淘汰比如“候选点必须可达”。Scorer 模式按结果打分比如“离目标越近分数越高”。Unreal 里面常见的测试有距离测试、视线测试、NavMesh 可达性测试、掩蔽测试、自热测试等。复刻到 Unity 时我会把测试设计成可继承的抽象类每一个测试只做一件事。2.5 Score评分多个测试的分数乘上各自权重后累加得到候选点的最终得分。分数越高越优。为了调试方便每个测试最好把输出归一化到 0 到 1这样权重才有对比意义。整体执行流程可以概括为获取查询上下文包括查询者位置、目标位置等。生成器根据上下文生成候选点列表。对每个候选点依次运行测试列表。Filter 测试不通过则直接排除该点。Scorer 测试得到 0 到 1 分乘权重后累加。对所有候选点按分数降序排序返回最高分点或前 N 个点。3. Unity 6 本地部署环境准备Unity 6 中接入这套 EQS 系统不需要额外安装复杂插件但环境准备要做扎实。3.1 安装 Unity 6从 Unity Hub 安装 Unity 6。一般来说使用 6000.0 及以上版本稳定性足够。安装时建议勾选 Windows Build Support 或目标平台支持方便后续打包测试。3.2 安装 AI Navigation 包后续章节里的 NavMesh 可达性测试依赖 AI Navigation 包。打开 Package ManagerWindow - Package Manager - Unity Registry - AI Navigation - Install安装完成后场景中会多出NavMeshSurface相关组件用来烘焙 NavMesh。3.3 搭建测试场景建议创建一个单独场景来验证 EQS地面一个 Plane 或 Cube 拉伸成地面。墙体几个 Cube 作为障碍物给它们单独设置 Layer比如Obstacle。AI 角色一个 Capsule挂NavMeshAgent组件作为查询发起者。目标对象一个 Sphere代表玩家或敌人作为 AI 要评估的目标位置。3.4 烘焙 NavMesh使用 AI Navigation 包时可以通过NavMeshSurface组件烘焙在地面对象上添加NavMeshSurface组件。将墙体和障碍物设为静态导航阻挡物。点击Bake按钮生成 NavMesh。烘焙完成后进入 Play ModeNavMeshAgent才能正确计算路径。3.5 推荐目录结构建议把代码按模块分离方便后续扩展。Assets/Scripts/EQS/ EQEnvironmentQuery.cs EQQueryContext.cs EQItemGeneratorBase.cs EQGenerator_RadialPoints.cs EQTestBase.cs EQTest_Distance.cs EQTest_LineOfSight.cs EQTest_NavMeshReachable.cs EQTest_Cover.cs Assets/Scripts/Demo/ EQAgent.cs4. 复刻 EQS 的 Unity 系统设计在写代码之前先把类设计说清楚。整体设计目标是“一个可配置的查询入口 一组可插拔的测试项”这样不需要后续改动查询主逻辑。4.1 核心类职责类/接口职责EQQueryContext保存查询者位置、目标位置、查询者 TransformEQItemGeneratorBase抽象生成器子类决定候选点如何分布EQGenerator_RadialPoints在圆形范围内生成均匀候选点EQTestBase抽象测试子类决定如何计算分数或过滤EQTest_Distance距离测试可设置最近/最远距离和偏好方向EQTest_LineOfSight视线测试从查询者到候选点是否可见EQTest_NavMeshReachable可达性测试候选点是否在 NavMesh 上有可达路径EQTest_Cover简化掩体测试候选点对目标是否形成遮挡EQEnvironmentQuery查询入口负责生成候选点并汇总评分EQAgent演示组件控制 AI 定期执行查询并移动4.2 执行流程设计在 Unity 中一次完整查询可以拆成五个阶段调用RunQuery(querier, targetPos)创建上下文。调用生成器的Generate方法得到候选点列表。遍历候选点依次执行所有测试。测试结果为 Fail 且模式为 Filter 时直接丢弃该点。测试结果为 Scorer 时累加加权分数最后选出最高分。这个流程的核心特点是“每个测试只负责一个维度”后续加新的评分逻辑不会污染主流程。5. 核心代码实现下面进入代码部分。我按时间线实现一个最小可运行的 EQS圆形生成器、距离测试、视线测试、NavMesh 可达性测试、汇总查询入口最后接一个移动 Demo Agent。5.1 查询上下文using UnityEngine; namespace EQS.Core { public class EQQueryContext { public Transform Querier; public Vector3 QuerierPosition; public Vector3 TargetPosition; } }5.2 生成器抽象类与圆形生成器using System.Collections.Generic; using UnityEngine; namespace EQS.Core { public abstract class EQItemGeneratorBase { public abstract ListVector3 Generate(Vector3 center, Quaternion orientation); } public class EQGenerator_RadialPoints : EQItemGeneratorBase { public int Count 16; public float Radius 10f; public override ListVector3 Generate(Vector3 center, Quaternion orientation) { ListVector3 points new ListVector3(); for (int i 0; i Count; i) { float angle (360f / Count) * i * Mathf.Deg2Rad; Vector3 dir new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)); points.Add(center dir * Radius); } return points; } } }这个生成器适合快速验证。实际项目中可以继续扩展格栅生成器、沿路径生成器、Actor 收集生成器。5.3 测试基类namespace EQS.Core { public enum EQTestMode { Filter, Scorer } public abstract class EQTestBase { public EQTestMode Mode EQTestMode.Scorer; public float Weight 1f; public abstract bool Run(Vector3 candidate, EQQueryContext ctx, out float score); } }5.4 距离测试using UnityEngine; namespace EQS.Tests { public class EQTest_Distance : EQTestBase { public float MinDistance 0f; public float MaxDistance 20f; public bool PreferCloser true; public override bool Run(Vector3 candidate, EQQueryContext ctx, out float score) { float dist Vector3.Distance(candidate, ctx.TargetPosition); if (dist MinDistance || dist MaxDistance) { score 0f; return false; } float t Mathf.InverseLerp(0f, MaxDistance, dist); score Mathf.Clamp01(PreferCloser ? 1f - t : t); return true; } } }这个测试的MinDistance和MaxDistance可以直接理解为 Filter 边界但测试仍返回 false让上层根据 Mode 决定是否丢弃候选点。5.5 视线测试using UnityEngine; namespace EQS.Tests { public class EQTest_LineOfSight : EQTestBase { public LayerMask ObstacleMask; public bool RequireVisible false; public override bool Run(Vector3 candidate, EQQueryContext ctx, out float score) { Vector3 start ctx.QuerierPosition Vector3.up * 1.2f; Vector3 end candidate Vector3.up * 1.2f; bool visible !Physics.Linecast(start, end, ObstacleMask); if (RequireVisible !visible) { score 0f; return false; } score visible ? 1f : 0f; return true; } } }RequireVisible设置为 true 时AI 只会选择能直接看见的目标点设置为 false 时AI 会选择被遮挡的掩体点。射击游戏找掩体时通常更想要“被遮挡”的点。5.6 NavMesh 可达性测试using UnityEngine; using UnityEngine.AI; namespace EQS.Tests { public class EQTest_NavMeshReachable : EQTestBase { public float SampleDistance 2f; public override bool Run(Vector3 candidate, EQQueryContext ctx, out float score) { if (!NavMesh.SamplePosition(candidate, out NavMeshHit hit, SampleDistance, NavMesh.AllAreas)) { score 0f; return false; } NavMeshPath path new NavMeshPath(); if (NavMesh.CalculatePath(ctx.QuerierPosition, hit.position, NavMesh.AllAreas, path)) { if (path.status NavMeshPathStatus.PathComplete) { score 1f; return true; } } score 0f; return false; } } }这个测试在候选点数量多时会比较贵后面性能优化章节会专门说明。5.7 简化掩体测试掩体测试的思路是从候选点看向目标如果中间有障碍物说明这个候选点对目标形成了遮挡加分。using UnityEngine; namespace EQS.Tests { public class EQTest_Cover : EQTestBase { public LayerMask CoverMask; public override bool Run(Vector3 candidate, EQQueryContext ctx, out float score) { Vector3 fromCandidate candidate Vector3.up * 1.2f; Vector3 toTarget ctx.TargetPosition Vector3.up * 1.2f; bool occluded Physics.Linecast(fromCandidate, toTarget, CoverMask); score occluded ? 1f : 0f; return true; } } }和EQTest_LineOfSight的区别是这里是“从候选点看向目标”用于判断掩体效果视线测试是“从 AI 当前位置看向候选点”用于判断路径上是否可见。5.8 EQS 查询入口using System.Collections.Generic; using UnityEngine; namespace EQS.Core { public class EQEnvironmentQuery : MonoBehaviour { public EQItemGeneratorBase Generator; public ListEQTestBase Tests new ListEQTestBase(); [Header(Debug)] public bool DrawPoints true; public bool DrawBestPoint true; private EQQueryContext _ctx new EQQueryContext(); public EQResult RunQuery(Transform querier, Vector3 targetPos) { _ctx.Querier querier; _ctx.QuerierPosition querier.position; _ctx.TargetPosition targetPos; ListVector3 candidates Generator.Generate(querier.position, querier.rotation); EQResult best null; foreach (Vector3 point in candidates) { EQResult result EvaluatePoint(point, _ctx); if (result.Passed (best null || result.Score best.Score)) { best result; } } return best; } private EQResult EvaluatePoint(Vector3 point, EQQueryContext ctx) { EQResult result new EQResult(); result.Point point; result.Passed true; result.Score 0f; foreach (EQTestBase test in Tests) { if (test null) continue; bool ok test.Run(point, ctx, out float score); if (!ok) { if (test.Mode EQTestMode.Filter) { result.Passed false; result.RejectReasons.Add(test.GetType().Name); result.Score 0f; return result; } score 0f; } result.Score Mathf.Clamp01(score) * test.Weight; } return result; } } }对应的结果类using System.Collections.Generic; using UnityEngine; namespace EQS.Core { public class EQResult { public Vector3 Point; public float Score; public bool Passed true; public Liststring RejectReasons new Liststring(); } }5.9 Demo AI Agent最后写一个演示组件让 AI 每隔一段时间执行一次查询然后走到最高分的点。using UnityEngine; using UnityEngine.AI; using EQS.Core; namespace EQS.Demo { public class EQAgent : MonoBehaviour { public EQEnvironmentQuery EQS; public NavMeshAgent Agent; public Transform Target; public float QueryInterval 1f; private float _lastTime -999f; void Update() { if (EQS null || Agent null || Target null) return; if (Time.time - _lastTime QueryInterval) return; _lastTime Time.time; EQResult best EQS.RunQuery(transform, Target.position); if (best ! null best.Passed) { Agent.SetDestination(best.Point); } } } }这个QueryInterval很重要后续优化时会反复提到。6. 可视化调试与功能测试EQS 这种系统最大的问题是“黑盒”分数不知道怎么来的。所以可视化调试是这套系统能不能用起来的关键。6.1 Gizmos 绘制候选点在EQEnvironmentQuery中添加 Gizmos 绘制逻辑private void OnDrawGizmos() { if (!Application.isPlaying) return; if (Generator null) return; ListVector3 candidates Generator.Generate( transform.position, transform.rotation ); foreach (Vector3 point in candidates) { Vector3 center point Vector3.up * 0.5f; float d Vector3.Distance(center, _ctx.TargetPosition); Color color Color.Lerp(Color.green, Color.red, Mathf.Clamp01(d / 10f)); Gizmos.color color; Gizmos.DrawWireSphere(center, 0.25f); } }这个方案比较粗糙正式项目里最好把候选点、最终选择点、各测试分数存到调试列表里然后逐项绘制。更实用的做法是在EQAgent中把best结果缓存起来持续绘制选中点private EQResult _lastBest; void Update() { // ... 省略查询逻辑 _lastBest EQS.RunQuery(transform, Target.position); } private void OnDrawGizmos() { if (_lastBest null || !_lastBest.Passed) return; Gizmos.color Color.cyan; Gizmos.DrawSphere(_lastBest.Point Vector3.up * 0.5f, 0.4f); }6.2 场景搭建步骤建议按下面的顺序验证创建地面、墙体、AI Capsule、Target Sphere。给 AI 添加NavMeshAgent和EQAgent组件。给场景中的动态对象添加EQEnvironmentQuery组件。在 Inspector 中指定生成器设置Count 16、Radius 8。添加一个EQTest_Distance设置MaxDistance 20PreferCloser true。添加一个EQTest_NavMeshReachable让它以 Filter 模式运行。点击 Play观察 AI 是否移动到离目标最近的可达点。然后再逐步添加视线测试和掩体测试观察候选点颜色和 AI 行为变化。6.3 功能验证用例测试场景输入配置预期结果空场地找最近点16 点圆形生成器距离测试AI 走向离目标最近且可达的点一堵墙挡路加视线测试RequireVisiblefalseAI 可能选择墙后掩体点墙后点不可达加 NavMesh 可达性 Filter不可达点被过滤AI 选最近可达点多个掩体对比距离 视线 掩体测试AI 选择遮挡更充分且距离合理的点无有效点所有点都被 Filter 淘汰best为 nullAI 停在原地通过这组用例可以判断系统是否真正具备“环境感知决策”能力。7. 功能扩展API 与批量任务思路EQS 本质上是 C# 内部的逻辑模块不是 Web 服务所以不需要 HTTP API。但如果你做的是服务器驱动的 AI 决策或者想让策划通过配置驱动 AI可以把查询封装成可序列化配置。7.1 配置驱动思路可以用ScriptableObject保存查询配置[CreateAssetMenu(fileName EQSQueryConfig, menuName AI/EQS Query Config)] public class EQSQueryConfig : ScriptableObject { public EQItemGeneratorBase Generator; public ListEQTestBase Tests new ListEQTestBase(); }这样策划可以创建多套查询配置比如“找掩体”“找巡逻点”“找攻击位置”运行时按状态切换。7.2 多 Agent 批量查询多 Agent 同时调用RunQuery会出现明显的 CPU 峰值因为每个 Agent 都要生成候选点、计算路径。优化思路有两个方向时间分片每个 Agent 错开查询时间避免同帧计算。空间分块同一个区域内多个 Agent 共用一份查询结果而不是各算各的。下面是一个简单的错开查询示例public class StaggeredQueryAgent : MonoBehaviour { public EQEnvironmentQuery EQS; public float Offset 0f; public float Interval 1f; void Update() { float t Time.time Offset; if (t % Interval Time.deltaTime) { // 执行查询 } } }通过给不同 Agent 设置不同Offset可以显著降低同帧计算压力。7.3 RaycastCommand 批量视线测试视线测试在候选点多的时候会产生大量 Raycast。Unity 提供了RaycastCommand做批量射线检测配合 Job System 可以并行执行。下面是一个简化示例using Unity.Collections; using UnityEngine; public static class LineOfSightBatcher { public static bool[] BatchCheck(int count, Vector3 start, Vector3[] ends, LayerMask mask) { var commands new NativeArrayRaycastCommand(count, Allocator.TempJob); var results new NativeArrayRaycastHit(count, Allocator.TempJob); for (int i 0; i count; i) { commands[i] new RaycastCommand( start, (ends[i] - start).normalized, Vector3.Distance(start, ends[i]), mask ); } var handle RaycastCommand.ScheduleBatch(commands, results, 32, default); handle.Complete(); bool[] visible new bool[count]; for (int i 0; i count; i) { visible[i] results[i].collider null; } commands.Dispose(); results.Dispose(); return visible; } }这个示例展示了批量思路实际接入时要注意RaycastCommand的初始位置不能位于碰撞体内部否则会产生误判。7.4 NavMeshPath 缓存与复用NavMesh.CalculatePath是 EQS 里最贵的操作之一。候选点越多路径计算越频繁。工程化建议只对通过距离和视线测试的点做可达性测试。把NavMeshPath对象缓存复用避免每次 new 一个。同一帧内相同起始点和终点不重复计算路径。8. 资源占用与性能观察EQS 属于 CPU 密集逻辑它的瓶颈不是 GPU而是候选点数、测试复杂度和查询频率。8.1 性能瓶颈分析假设一次查询有 32 个候选点、3 个测试那么单次查询最多执行 96 次测试。如果其中包含可达性测试最坏情况是 32 次 NavMesh 路径搜索这已经会带来几毫秒的开销。所以不要让 AI 每帧查询。一般射击游戏 AI 的决策频率是 1 到 4 Hz也就是每 0.25 到 1 秒查询一次完全够用。8.2 如何观察 CPU 峰值Unity Profiler 是主要工具Window - Analysis - Profiler重点观察PlayerLoop下的逻辑开销如果单帧耗时突然变高优先查看是否有多个 Agent 在同一帧执行路径计算。8.3 降低开销的建议手段说明降低候选点数先用 16 到 32 个点验证效果不够再加降低查询频率0.5 到 1 秒一次通常足够Filter 前置先做便宜的 Filter 测试再做贵的路径测试调整 NavMesh SampleDistance过大的采样距离会放宽候选点范围使用 Job System将视线测试批量并行化多 Agent 错帧给每个 Agent 设置随机起始偏移8.4 内存与显存这套方案是纯 CPU 计算基本不占用显存。内存方面主要开销来自测试过程中产生的NavMeshPath和候选点列表。工程上建议复用这些对象不要在高频查询时反复 new。9. 常见问题与排查方法问题现象可能原因排查方式解决方案候选点没有生成生成器未配置或 Count 为 0检查 Inspector 中 Generator 引用和 Count 值设置有效生成器并让 Count 0AI 永远停在原地所有候选点被 Filter 淘汰开启 Gizmos 看候选点颜色调整测试阈值或关闭 Filter 模式可达性测试全失败NavMesh 未烘焙查看 Navigation 窗口是否已有数据重新 Bake NavMesh视线测试结果不符合预期LayerMask 未包含墙体检查 ObstacleMask 配置让墙体使用单独 Layer游戏运行时卡顿明显每帧查询且计算路径打开 Profiler 定位耗时增加 QueryInterval减少候选点数Gizmos 不显示场景视图 Gizmos 被关闭检查 Scene 右上角 Gizmos 开关打开 Gizmos 并确认绘制代码存在多个 Agent 同帧卡顿缺乏错帧逻辑查看同时发起查询的 Agent 数量使用偏移量错开查询时间分数结果不稳定测试分数未归一化输出各测试得分在汇总前对 score 做 Clamp01Linecast 起点在物体内部起点高度太低或偏移不够调试时绘制射线把起点抬高并添加偏移候选点集中在 NavMesh 外生成器只按圆分布未贴地采样观察候选点 Y 轴高度使用 NavMesh.SamplePosition 修正高度其中最容易踩的坑是“视线测试结果全反”。原因很常见把RequireVisible配置成 true却期待 AI 找掩体。实际上找掩体时应该用掩体测试而不是视线测试。建议把每个测试的作用写在 Inspector 的 Tooltip 里避免策划配错。10. 边界与合规使用提醒这篇文章是基于通用工程思路的复现整理不是直接照搬原视频或付费内容。如果你学习某个教程后做项目请遵守下面几条边界不要直接复制教程中的付费脚本、美术资源和关卡内容。如果引用了开源实现保留原作者署名和许可证声明。使用 AI 生成素材时确认素材版权和商业使用权限。如果是团队项目涉及玩家数据、网络同步等功能需要遵守隐私和数据合规要求。正式发布前对 EQS 生成的 AI 行为做充分测试避免出现卡墙、穿墙或无限循环等异常。复刻 EQS 这类系统价值在于把“决策逻辑”从具体玩法中抽离出来。它本身不涉及敏感内容但如果 AI 行为应用到商业游戏仍然需要做完整的功能与性能验证。11. 最佳实践与后续扩展如果你决定在项目里正式落地这套 EQS建议按下面的次序推进。11.1 先跑通最小闭环不急着做掩体、视线、Job System。先让 AI 能通过“圆形生成器 距离测试 可达性测试”选出一个点并走过去。这个闭环跑通后后面所有测试项都是增量工作。11.2 每个测试单独开关在EQEnvironmentQuery里为每个测试增加enable字段或者使用[SerializeReference]让 Inspector 支持动态增删测试项。调试时只开一个测试更容易定位问题。11.3 把得分结构输出到 UI更进阶的做法是在场景里用OnGUI或 TextMeshPro 输出每个候选点的得分明细void OnGUI() { if (_lastResult null) return; GUILayout.Label($Best Point: {_lastResult.Point}); GUILayout.Label($Score: {_lastResult.Score}); }这样策划不用看代码也能理解 AI 为什么选这个位置。11.4 扩展方向这套框架可以继续扩展成更完整的 AI 决策层接入行为树把查询结果写进黑板变量行为树中的MoveTo任务直接读取目标点。增加 Actor 生成器直接收集场景中的敌人或掉落物做收集型 AI。增加“方向偏好”测试让 AI 偏向玩家背后方向实现绕后效果。增加“热度”测试通过体素热度场让 AI 避开危险区域。生成器支持组合圆形生成器生成后经过 NavMesh 采样修正高度再做一次附近掩体投影。服务端 AI 决策如果做 MOBA 或俯视角射击可以将查询逻辑放在服务器使用纯 C# 计算不依赖 Unity 物理。12. 总结与下一步这次整理的 Unity 6 EQS 复刻方案核心思路是“生成候选点 - 多维度测试 - 加权打分 - 输出最优解”。它不是一个具体插件而是一套可插拔、可调试、可扩展的 AI 环境决策框架。复制代码跑通 Demo 之后你会发现 Unity 里做“AI 找掩体”“AI 选巡逻点”“AI 绕后”这类需求都比直接写死逻辑要稳得多。接下来建议先做两件事把最小闭环跑通验证候选点生成和得分选择。配置 Gizmos 调试把每个候选点的分数可视化。如果每一步都能看到“为什么选这个点”这套 EQS 就算真正落地了。这个方向后面还能扩展很多实用玩法建议收藏备用准备做 AI 决策系统的时候直接照着搭。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进