ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Unity 6中自研轻量EQS框架:从零实现动态环境查询与AI决策

Unity 6中自研轻量EQS框架:从零实现动态环境查询与AI决策 “如果 AI 只会走固定路径那遇到动态掩体和多目标威胁时基本等于裸奔。”这句话是我在 Unity 社区里看到次数最多的一句吐槽也是 Unreal EQS 被反复提起的原因。这次我们来看一套在 Unity 6 中复刻 Unreal Engine EQSEnvironment Query System环境查询系统的完整思路与代码框架。先说明白这不是让你下载一个现成插件而是从零搭建一个可以在运行时动态评估场景、对候选位置打分排序、最终让 AI 做出最优决策的模块。如果你熟悉 Unreal一定知道 EQS 的工作方式AI 发起一次查询系统根据上下文生成一批候选点再用距离、可见性、角度、路径可达性等多个维度给候选点打分最后 AI 走向分数最高的位置。Unity 官方没有直接提供这套系统传统做法是 NavMeshAgent 加上手动维护的路径点但这套方案很难应对动态场景。本文会从 Unreal EQS 的核心概念讲起在 Unity 6 中用 C# 实现上下文、生成器、测试项和查询运行器然后接入一个带 NavMeshAgent 的 AI 角色演示动态选点和掩体选择。最后补充调试可视化、性能观察、常见问题排查和工程化扩展建议。适合对 Unity AI 有一定基础、想突破固定巡逻点限制的开发者也适合从 Unreal 转向 Unity、想找回 EQS 开发体验的读者。1. 核心能力速览能力项说明目标引擎Unity 6也可用于 2022 LTS需注意 AI Navigation 包版本差异实现语言C#纯脚本实现系统类型轻量环境查询系统逻辑参考 Unreal EQS核心能力查询上下文、候选点生成、多测试项评分、最优位置排序可扩展性生成器与测试项均为抽象类可自行扩展依赖组件Unity AI Navigation 包、NavMeshAgent、Collider运行方式MonoBehaviour ScriptableObject编辑器内可视化配置外部插件不需要复用 Unity 内置 API调试能力Gizmos 绘制候选点与分数支持运行时查看性能策略查询冷却、候选点上限、后续可接 Job System 并行评分适合场景AI 掩体选择、动态巡逻点、多目标威胁评估、技能释放位置选择等这里再补充一句关于项目定位的判断Unreal 的 EQS 是一套完整且复杂的系统本文不会也没有必要在 Unity 里复刻全部细节。核心目标是复刻它的“查询-生成-测试-排序”工作流把这套决策逻辑变成 Unity 工程里可复用、可调试、可扩展的模块。从实际项目角度看这套精简版会比盲目照搬 Unreal 的完整架构更实用。2. Unreal EQS 是什么Unity 为什么需要它Unreal 的 EQS 全称是 Environmental Query System直译是环境查询系统。它不是某个具体功能而是一套通用的“让 AI 在环境中找最优位置/最优目标”的决策框架。一个标准 EQS 查询流程包含五个部分查询请求方Querier通常是 AI Controller发起一次查询请求。上下文Context描述查询的参考对象比如查询者自身、目标玩家、某个技能释放点。生成器Generator根据上下文在空间中生成一批候选点常见的有网格、圆环、圆锥、点阵。测试项Test对每个候选点执行一个或多个测试测试可以过滤候选点也可以给候选点打分。结果排序把多个测试项的分数加权合并选出最高分位置返回给 AI。Unreal 的 EQS 之所以强是因为它把“找位置”这件事从硬编码中解放出来。AI 不关心具体在哪几个点之间巡逻而是通过查询动态获取候选位置再用测试项模拟真实决策太远不要、没掩体不要、敌人能看见不要、路径走不到不要。这些规则全部可配置换一个场景或者换一种 AI只需要调整查询资产不需要改代码。Unity 的问题在于没有原生 EQS。常用 AI 方案是 NavMeshAgent 固定路径点或者运行时随机选一个 NavMesh 上的位置。固定路径点显然不够灵活随机位置又缺少合理性判断。更关键的是当场景里存在多个 AI 角色每个 AI 需要根据视野、距离、覆盖物、路径长度做不同决策时没有一套统一的“评估环境”框架代码很快就会变成一团乱麻。所以在 Unity 中复刻 EQS 的核心意义不是炫技而是补上 Unity AI 开发中缺失的这一层抽象把环境感知变成可配置、可评分、可查询的模块。这里的“可配置”非常关键它意味着策划或者搞 AI 的同事可以在 Inspector 里调整测试项权重而不是每次改逻辑都打开 IDE 改代码。3. 环境准备与 Unity 6 项目配置开始写代码之前先准备一个干净的 Unity 6 工程。后面所有实现都基于这个工程建议按下面的清单逐项确认3.1 Unity 6 版本与模块通过 Unity Hub 安装 Unity 6 正式版选择包含 Windows/Mac Build Support 的模块。创建 3DBuilt-In Render Pipeline项目即可不需要 URP/HDRP。代码编辑器推荐 VS Code 或 Rider也可以直接用 Visual Studio。3.2 AI Navigation 包Unity 6 中 NavMesh 相关功能被整理进了 AI Navigation 包。打开 Package Manager搜索AI Navigation点击 Install。这个包会提供 NavMeshSurface 组件用于在场景中烘焙 NavMesh。# 如果命令行安装可以在项目根目录执行 # 但更推荐直接在 Package Manager 中搜索安装3.3 场景基础结构搭建一个简易测试场景至少包含以下几类物件一个地面Plane 或者任意 Cube 拉伸注意加 Collider。一个烘焙好的 NavMesh挂 NavMeshSurface点击 Bake。一个 AI 角色一个 Capsule NavMeshAgent 组件。一个玩家目标一个 Cube 或 Capsule作为 Target 上下文。场景结构参考如下场景 ├── Ground │ └── NavMeshSurface ├── AI_Agent │ ├── Capsule │ └── NavMeshAgent └── Player_Target └── Capsule3.4 目录与脚本规划建议在 Assets 下建立如下目录结构保持代码和配置分离Assets/ ├── Scripts/ │ ├── EQS/ │ │ ├── Core/ │ │ ├── Generators/ │ │ └── Tests/ │ └── AI/ ├── EQSAssets/ └── Scenes/Core 放上下文、候选点、查询运行器等基础类Generators 放生成器Tests 放测试项AI 放最终调用查询的角色脚本。EQSAssets 用来存放运行时创建的 ScriptableObject 查询配置资产。4. 自研 EQS 框架的架构设计在写代码前先把架构过一遍。这套 Unity EQS 框架包含 5 个核心类型4.1 核心概念映射Unreal EQS 概念Unity 自研实现上下文ContextEQContext类保存 Querier、Target、QueryCenter候选点ItemEQItem类包含位置、总分、各测试得分生成器GeneratorEQGenerator抽象类提供候选点列表测试项TestEQTest抽象类对候选点返回分数查询配置QueryEQSQueryConfigScriptableObject 资产查询运行器EQSRunner静态类执行完整查询流程4.2 执行流程一次完整查询的执行顺序如下外部调用方AI 角色脚本构造EQContext并传入查询配置EQSQueryConfig。运行器调用生成器的Generate(context)获取候选点列表。对每个候选点创建EQItem。遍历config.tests中的每个测试项调用test.Score(item, context)按权重累加分数。对全部候选点按总分排序。截取前 N 个候选点返回给调用方。这个流程和 Unreal EQS 的“生成优先、测试评分、排序选择”一致只是处理方式更轻量。设计上生成器和测试项都用抽象类实现这样后续扩展新的生成策略或测试维度不需要改动运行器逻辑符合开放封闭原则。5. 从零实现 EQS 核心生成器与测试下面开始写核心代码。建议按小节顺序逐个创建脚本。5.1 上下文与候选点上下文是查询的输入候选点是查询的输出基础。新建Assets/Scripts/EQS/Core/EQContext.csusing UnityEngine; namespace EQSFramework { /// summary /// 查询上下文所有生成器和测试项都从这里获取参考信息。 /// /summary public class EQContext { public Transform Querier; // 查询者通常是 AI 角色 public Transform Target; // 目标通常是玩家 public Vector3 QueryCenter; // 查询中心点 } }再新建EQItem.csusing UnityEngine; namespace EQSFramework { /// summary /// 候选点一个待评估的位置。 /// Score 是多个测试项加权后的最终分数。 /// /summary public class EQItem { public Vector3 Position; public float Score; public float DistanceToTarget; public bool IsVisible; } }这里把距离和可见性作为常用字段直接放进候选点是为了方便在调试 Gizmos 中直接读取也方便后续测试项复用。实际项目中可以按需扩展但不要塞太多一次性字段否则候选点对象会变得臃肿。5.2 生成器网格与圆环生成器负责产生候选位置。先定义抽象基类EQGenerator.csusing System.Collections.Generic; using UnityEngine; namespace EQSFramework { public abstract class EQGenerator { public abstract ListVector3 Generate(EQContext context); } }网格生成器是最常用的生成器适合在某个区域内均匀撒点。新建EQGridGenerator.csusing System.Collections.Generic; using UnityEngine; namespace EQSFramework { [System.Serializable] public class EQGridGenerator : EQGenerator { public Vector2 size new Vector2(10f, 10f); public float cellSize 1f; public float heightOffset 0f; public override ListVector3 Generate(EQContext context) { ListVector3 points new ListVector3(); Vector3 center context.QueryCenter; int xCount Mathf.Max(1, Mathf.RoundToInt(size.x / cellSize)); int zCount Mathf.Max(1, Mathf.RoundToInt(size.y / cellSize)); Vector3 start center - new Vector3(size.x * 0.5f, 0f, size.y * 0.5f); for (int x 0; x xCount; x) { for (int z 0; z zCount; z) { Vector3 p start new Vector3( x * cellSize cellSize * 0.5f, heightOffset, z * cellSize cellSize * 0.5f ); points.Add(p); } } return points; } } }圆环生成器适合在角色周围呈圆形分布候选点常用于掩体选择或技能位置选择。新建EQRingGenerator.csusing System.Collections.Generic; using UnityEngine; namespace EQSFramework { [System.Serializable] public class EQRingGenerator : EQGenerator { public int count 8; public float radius 5f; public float heightOffset 0f; public override ListVector3 Generate(EQContext context) { ListVector3 points new ListVector3(); Vector3 center context.QueryCenter; for (int i 0; i count; i) { float angle (360f / count) * i * Mathf.Deg2Rad; Vector3 offset new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)) * radius; Vector3 p center offset; p.y heightOffset; points.Add(p); } return points; } } }生成器本身不关心点是否可到达这是测试项的事。这样职责分离生成策略可以随意组合测试策略。5.3 测试项距离、可见性、角度、导航测试项是 EQS 的核心。先定义抽象基类EQTest.csusing UnityEngine; namespace EQSFramework { public abstract class EQTest { public bool negate; // 取反用于做“反向过滤” [Range(0f, 1f)] public float weight 1f; // 权重 public abstract float Score(EQItem item, EQContext context); } }距离测试候选点离目标越近分越高。using UnityEngine; namespace EQSFramework { [System.Serializable] public class EQDistanceTest : EQTest { public float maxDistance 10f; public override float Score(EQItem item, EQContext context) { Vector3 targetPos context.Target ? context.Target.position : context.QueryCenter; float dist Vector3.Distance(item.Position, targetPos); item.DistanceToTarget dist; float score 1f - Mathf.Clamp01(dist / maxDistance); float result negate ? 1f - score : score; return result * weight; } } }可见性测试AI 所在候选位置能否看到目标。用 Physics.Linecast 实现注意需要排除 AI 自身碰撞体最好通过 LayerMask 处理。using UnityEngine; namespace EQSFramework { [System.Serializable] public class EQVisibilityTest : EQTest { public float eyeHeight 1.5f; public LayerMask obstacleMask ~0; public override float Score(EQItem item, EQContext context) { Vector3 from item.Position Vector3.up * eyeHeight; Vector3 to context.Target ? context.Target.position Vector3.up * eyeHeight : context.QueryCenter Vector3.up * eyeHeight; bool visible !Physics.Linecast(from, to, obstacleMask); item.IsVisible visible; float score visible ? 1f : 0f; float result negate ? 1f - score : score; return result * weight; } } }角度测试AI 朝向和候选点方向的夹角越小分越高。这个测试适合“优先选择正前方位置”的场景。using UnityEngine; namespace EQSFramework { [System.Serializable] public class EQDotTest : EQTest { public float maxAngle 60f; public override float Score(EQItem item, EQContext context) { Vector3 from context.Querier ? context.Querier.position : context.QueryCenter; Vector3 forward context.Querier ? context.Querier.forward : Vector3.forward; Vector3 dir item.Position - from; dir.y 0f; dir.Normalize(); float angle Vector3.Angle(forward, dir); float score 1f - Mathf.Clamp01(angle / maxAngle); float result negate ? 1f - score : score; return result * weight; } } }导航可达性测试候选点是否能通过 NavMesh 到达路径越短分越高。这个测试可以在一定程度上避免 AI 选中不可达位置。using UnityEngine; using UnityEngine.AI; namespace EQSFramework { [System.Serializable] public class EQPathfindingTest : EQTest { public float maxPathDistance 20f; public override float Score(EQItem item, EQContext context) { Vector3 from context.Querier ? context.Querier.position : context.QueryCenter; NavMeshPath path new NavMeshPath(); bool hasPath NavMesh.CalculatePath(from, item.Position, NavMesh.AllAreas, path); if (!hasPath || path.status ! NavMeshPathStatus.PathComplete) { float failScore negate ? 1f : 0f; return failScore * weight; } float pathLen 0f; if (path.corners.Length 1) { for (int i 1; i path.corners.Length; i) { pathLen Vector3.Distance(path.corners[i - 1], path.corners[i]); } } float successScore 1f - Mathf.Clamp01(pathLen / maxPathDistance); float result negate ? 1f - successScore : successScore; return result * weight; } } }这里有个工程细节要说明NavMesh.CalculatePath不保证终点一定在 NavMesh 上如果候选点偏到 NavMesh 外路径状态会变成PathPartial或PathInvalid。因此更稳妥的做法是在EQItem生成后首先用NavMesh.SamplePosition把候选点修正到最近 NavMesh 表面上然后再执行其他测试。后面第 5.4 节会给出这个修正步骤。5.4 查询配置与运行器查询配置使用 ScriptableObject这样可以把一组生成器和测试项保存成资产多个 AI 角色复用同一份配置。新建EQSQueryConfig.csusing System.Collections.Generic; using UnityEngine; namespace EQSFramework { [CreateAssetMenu(fileName EQSQuery, menuName AI/EQS Query)] public class EQSQueryConfig : ScriptableObject { [SerializeReference] public EQGenerator generator new EQGridGenerator(); [SerializeReference] public ListEQTest tests new ListEQTest(); public int maxItemCount 32; public bool sampleNavMeshPosition true; public float sampleNavMeshDistance 2f; } }这里使用了[SerializeReference]它的作用是让 ScriptableObject 在 Inspector 中能够序列化保存抽象类派生实例。生成器和测试列表都会在 Inspector 中显示为可折叠对象可以手动添加不同的生成器类型和测试类型。这是 Unity 对多态序列化的标准做法需要 Unity 2019.3 以上版本Unity 6 完全支持。然后编写查询运行器EQSRunner.csusing System.Collections.Generic; using UnityEngine; using UnityEngine.AI; namespace EQSFramework { /// summary /// 查询运行器负责执行一次完整的 EQS 查询。 /// /summary public static class EQSRunner { public static ListEQItem Run(EQSQueryConfig config, EQContext context) { if (config null || context null) { Debug.LogWarning(EQS 查询失败配置或上下文为空。); return new ListEQItem(); } ListVector3 points config.generator.Generate(context); ListEQItem items new ListEQItem(points.Count); foreach (Vector3 p in points) { Vector3 candidatePos p; if (config.sampleNavMeshPosition NavMesh.SamplePosition(p, out NavMeshHit hit, config.sampleNavMeshDistance, NavMesh.AllAreas)) { candidatePos hit.position; } EQItem item new EQItem { Position candidatePos, Score 0f }; foreach (EQTest test in config.tests) { if (test null) continue; item.Score test.Score(item, context); } items.Add(item); } items.Sort((a, b) b.Score.CompareTo(a.Score)); if (items.Count config.maxItemCount) { items.RemoveRange(config.maxItemCount, items.Count - config.maxItemCount); } return items; } } }到这里一套基础的 EQS 查询框架已经跑通了。它的工作流程是传入配置和上下文运行器生成候选点修正 NavMesh 位置逐项测试打分最后返回排序后的列表。接下来要验证的是这套框架能不能真正驱动 Unity 场景里的 AI 角色。6. 在 Unity 6 场景中接入 AI 角色写完框架只是第一步接入 AI 角色才能验证功能。下面给出一个可以实际运行的测试脚本并演示三种典型用法。6.1 创建 AI 角色脚本新建Assets/Scripts/AI/AISoldier.csusing UnityEngine; using UnityEngine.AI; using EQSFramework; namespace AITest { public class AISoldier : MonoBehaviour { public EQSQueryConfig moveQuery; // 移动到最优位置 public Transform playerTarget; // 目标玩家 private NavMeshAgent agent; private void Start() { agent GetComponentNavMeshAgent(); } [ContextMenu(执行一次查询)] public void ExecuteQuery() { if (moveQuery null) { Debug.LogWarning(未配置 moveQuery。); return; } EQContext context new EQContext { Querier transform, Target playerTarget, QueryCenter transform.position }; ListEQItem results EQSRunner.Run(moveQuery, context); if (results.Count 0) { agent.SetDestination(results[0].Position); Debug.Log($最优候选点{results[0].Position}分数{results[0].Score:F2}共 {results.Count} 个候选点); } else { Debug.LogWarning(查询结果为空。); } } } }在 Inspector 中把AISoldier挂到 AI 角色上创建一个 EQSQuery 资产配置好生成器和测试项然后通过 ContextMenu 右键调用ExecuteQuery就能看到 AI 角色走出来。这个验证方式对排查问题非常高效。6.2 场景一掩体选择掩体选择是 EQS 的经典用法AI 在受到威胁时需要快速找到一个“离目标足够近、目标看不见我、我能通过路径到达”的位置。测试配置建议配置项建议值生成器类型EQRingGenerator半径 6 米数量 16测试项 1EQDistanceTestmaxDistance 10weight 0.4测试项 2EQVisibilityTestnegate 勾选weight 1.0测试项 3EQPathfindingTestmaxPathDistance 20weight 0.6这里的核心是EQVisibilityTest的negate勾选。正常情况下候选点可见目标会得到 1 分勾选 negate 后可见得到 0 分不可见得到 1 分。这样 AI 会优先选择“目标看不到我”的位置。6.3 场景二动态巡逻点固定巡逻点的问题在于玩家蹲在一个固定点就能猜到 AI 路线。动态巡逻点可以让 AI 每次巡逻时通过 EQS 重新选一个当前位置周边最优的点距离和朝向的权重可以自行调整。测试配置建议配置项建议值生成器类型EQGridGeneratorsize 12x12cellSize 2测试项 1EQDistanceTestmaxDistance 15weight 0.5测试项 2EQDotTestmaxAngle 90weight 0.5测试项 3EQPathfindingTestmaxPathDistance 30weight 0.3这样 AI 会优先选择“离目标距离适中、在自身朝向前方、路径可达”的位置进行巡逻而不是每次都走向同一个固定点。6.4 场景三多目标威胁评估EQS 不仅可以评估位置也可以评估目标。思路很简单把每个目标的位置当作一个候选点用距离、可见性、目标当前状态等测试项给目标打分然后选择分数最高的目标作为攻击对象。具体做法是写一个EQTargetTest让它从context.Target之外的多目标列表中逐个取出目标构造EQItem。这里不再展开完整代码只说明思路运行时把目标列表作为候选点传入框架测试项按目标属性评分。这是 EQS 从“位置查询”向“目标查询”扩展的自然方式。7. 调试可视化与性能观察EQS 这类系统调试起来比普通 AI 逻辑更困难因为候选点可能有一百多个如果不做可视化你根本不知道分数为什么高、为什么低。所以调试可视化要优先做。7.1 用 Gizmos 绘制候选点在 AI 角色脚本中加上 Gizmos 绘制代码把最近一次查询结果显示在 Scene 视图里。新建Assets/Scripts/AI/AISoldierDebug.cs或者直接把下面的代码合并到 AISoldier 中#if UNITY_EDITOR using UnityEngine; using EQSFramework; namespace AITest { public partial class AISoldier { private void OnDrawGizmosSelected() { if (lastResults null || lastResults.Count 0) return; foreach (EQItem item in lastResults) { float t Mathf.Clamp01(item.Score / 2f); Gizmos.color Color.Lerp(Color.red, Color.green, t); Gizmos.DrawWireSphere(item.Position, 0.3f); } } } } #endif注意lastResults是需要在AISoldier类中新增的字段把ExecuteQuery中得到的results赋值给它。这样在 Scene 视图中选中 AI 角色就能看到所有候选点的分数分布。7.2 性能数据怎么看EQS 的性能瓶颈主要在于候选点数量和测试项数量相乘的评分次数。10x10 网格 100 个候选点、4 个测试项就是 400 次评分20x20 网格 400 个候选点、8 个测试项就是 3200 次评分。如果 AI 角色数量超过 10 个同步执行就可能出现瞬时卡顿。建议在查询运行器中加入耗时统计最简单的方式是使用System.Diagnostics.StopwatchSystem.Diagnostics.Stopwatch sw System.Diagnostics.Stopwatch.StartNew(); ListEQItem items EQSRunner.Run(moveQuery, context); sw.Stop(); Debug.Log($EQS 查询耗时{sw.ElapsedMilliseconds} ms);判断标准很简单单次查询耗时稳定低于 2ms可以接受超过 5ms就要降候选点数量或者测试项数量。如果 AI 数量多优先做查询冷却比如每个 AI 至少间隔 0.3 秒到 0.5 秒再发起下一次查询更进一步可以用 Unity Job System 把多个候选点的评分并行化。7.3 降低性能压力的策略降低网格密度cellSize 从 1 改成 1.5 或 2候选点数量会显著下降。缩短查询冷却时间每次查询后强制等待最短间隔。分帧执行把候选点分批一帧只测试其中一部分。结果缓存如果场景环境变化不剧烈可以缓存最近一次查询结果短时间复用。按需查询比如角色只有在进入某种状态时才触发 EQS而不是每帧都执行。8. 常见问题与排查方法下面的表格列出了使用这套 Unity EQS 框架时最可能遇到的问题和排查方向问题现象可能原因排查方式解决方案查询结果为空生成器没配置或上下文为空检查EQSQueryConfig资产和EQContext赋值确认 Querier、Target、QueryCenter 都已赋值所有候选点分数都是 0测试项权重为 0或所有测试项返回 0在 Inspector 中展开测试项检查 weight 和 negate调整 weight打开可视化逐项看分数候选点不在 NavMesh 上生成器产生的位置高度不对检查sampleNavMeshPosition是否开启勾选sampleNavMeshPosition调大sampleNavMeshDistanceAI 走向不可达位置缺少路径可达性测试查看最终选择位置是否在 NavMesh 内添加EQPathfindingTest并提高权重可见性检测结果不符合预期Linecast 检测到了 AI 自身或地面的 Collider检查obstacleMask设置把 AI 本体放到指定 Layer并在obstacleMask中排除每帧执行查询导致卡顿查询过于频繁候选点数量过大用 Stopwatch 统计耗时查看候选点数量加查询冷却降低网格密度改用 Job SystemInspector 中无法添加生成器或测试项[SerializeReference]未正确显示检查 ScriptableObject 资产是否保存点击资产面板中的 Add 按钮选择具体类型NavMesh.CalculatePath 总是返回 PathInvalid起点或终点不在 NavMesh 上分别检查起点和终点位置对起点和终点都执行NavMesh.SamplePosition修正排查时有个技巧先只保留一个测试项把其他的 weight 临时设为 0逐项验证每个测试的单独表现。如果单个测试都能跑通再叠加所有测试权重问题通常出在权重配比上。9. 最佳实践与扩展方向框架跑通只是基础工程化才是关键。下面这些实践建议来自常见的 Unity 项目经验按重要性排序查询配置资产化不要把生成器和测试项直接写死在场景里尽量用 ScriptableObject 资产。这样同一种 AI 可以复用配置也方便在版本控制中统一管理调整记录。测试项分数统一归一化框架中的每个测试项最好都返回 0 到 1 之间的分数权重再乘以这个分数。如果某个测试项返回超大值或负值整体排序会被带偏。尽量不要在测试项内部直接做复杂的分数转换。AI 数量多时引入查询冷却每个 AI 设置一个lastQueryTime字段只有间隔超过阈值才允许发起新的 EQS 查询。否则多个 AI 同时查询场景会有明显的帧率抖动。与行为树或状态机配合EQS 的价值在于“决策位置”而什么时候触发查询、查询结果如何使用应该交给行为树或状态机。不要让 AI 角色每帧都调用 EQS也不要在任何状态里无脑执行查询。运行时调试信息分层编辑器下可以用 Gizmos 画出上千个候选点但打包后这些调试代码要全部去掉。建议在#if UNITY_EDITOR中包裹所有调试绘制逻辑。注意数据合规与资源使用如果项目包含玩家位置、角色生命值、朝向等数据在接入网络同步、云端统计或对局回放功能时要注意隐私边界和数据权限。AI 查询过程中产生的候选点坐标、路径计算耗时等数据如果不做脱敏处理不建议直接上报到第三方分析平台。扩展方向走向 ECS 与 Job System。当前框架的评分循环是纯同步的候选点数量增加后可以把测试循环改成IJobEntity或IJobParallelFor用 Burst 编译加速。Unity 6 中对 ECS 的支持更成熟这个方向可以作为后续优化重点。扩展方向增加过滤阶段。Unreal EQS 支持先过滤再评分本框架目前只实现了评分阶段。如果需要高性能可以在EQTest中增加一个bool PassFilter(EQItem item, EQContext context)方法先过滤掉完全不合要求的候选点再执行得分累加能有效降低无效计算。10. 总结这套 Unity 6 自研 EQS 框架最值得尝试的点是它用不到 300 行核心代码复刻了 Unreal EQS 的完整工作流。第一次上手建议先做两件事第一用 6.1 小节的AISoldier场景跑通一次查询确认候选点能生成、分数能排序、AI 能移动第二打开 Gizmos 可视化观察不同测试项权重对最终选点的影响。最容易踩的坑有两个一是[SerializeReference]字段在 Inspector 中不显示二是 NavMesh 候选点没有修正导致路径计算始终失败。这两个问题在代码中都已经给出了解决方案直接参照即可。从项目后续扩展看这套框架的价值不在代码量而在它把环境评估从“硬编码路径点”提升到了“动态查询排序”的层面。用它可以继续做掩体、巡逻、目标威胁评估也可以和行为树、状态机、ECS 结合演变成一套更完整的 Unity AI 支撑模块。如果你正在做射击类、潜行类或者其他需要 AI 独立做空间决策的项目这套 EQS 思路值得直接拿过去用。先跑通一次查询剩下的按项目需求慢慢扩展。
返回列表