Gameplay 玩法系统:基础AI
课程回顾与核心问题探讨
Q&A
问题一:现代游戏逻辑是否都采用基于组件(Component-Based)的架构?
核心观点: 现代游戏引擎普遍采用 基于组件的架构 (Component-Based Architecture, CBA),因为它既符合人类对世界的直观认知,又 极大地受益于现代 CPU 的并行计算能力。
-
关键术语:
- Component-Based Architecture (组件化架构): 一种将游戏对象(GameObject)视为一个空容器,通过动态添加不同功能的组件(Component)来定义其行为和属性的设计模式。
-
选择该架构的主要原因:
-
认知友好 (Intuitive & Composable):
- 这种模式非常符合人类观察和理解世界的方式。一个复杂的物体可以被看作是多个基础属性和能力的集合。
- 讲座中用“斜杠青年”来类比:一个人可以同时是“程序员 / 吉他手 / 摄影爱好者”,这些身份就像组件一样,可以自由组合,定义了这个人的特性。
- 在引擎中,一个角色可以是
TransformComponent+RenderComponent+PhysicsComponent+AIComponent的组合。
-
并行友好 (Parallelism-Friendly):
- 将一个庞大的游戏对象逻辑拆分成独立的、功能内聚的组件,非常有利于并行处理。
- 例如,物理系统可以独立地、并行地更新所有对象的
PhysicsComponent,而渲染系统可以并行地处理所有RenderComponent。这在多核 CPU 时代是巨大的性能优势。
-
-
潜在的局限性:
- 效率开销 (Performance Overhead): 相较于将所有逻辑紧密集成在一起的“一体化”设计,组件化的抽象和数据分散可能带来一定的性能开销。然而,其带来的灵活性和并行优势通常远大于这点劣势。
-
结论: 尽管存在其他实现方式(如纯 C++ 硬编码、纯脚本),但基于组件的架构已成为业界(如 Unreal, Unity)公认的最佳实践和主流标准。
问题二:如何实现蓝图(Blueprint)的多人协同开发?
核心观点: 蓝图(或任何可视化脚本)的多人协同是一个业界尚未解决的巨大难题。其根本原因在于 图形化表达的逻辑难以进行版本控制中的合并(Merge)操作,且其逻辑效果不直观。
-
关键术语:
- 蓝图 (Blueprint): Unreal Engine 中的可视化脚本系统。
- 协同 (Collaboration): 多人同时在同一个项目或资产上工作的流程。
- 合并 (Merge): 版本控制系统(如 Git)中,将不同分支或不同人的修改整合到一起的操作。
-
难点解析:为什么蓝图协同远比场景协同困难?
-
场景编辑 (Scene Editing) → 所见即所得 (WYSIWYG):
- 当一个美术师在场景中放置一栋房子时,其他协作者能立即看到这个修改的最终结果(一栋房子出现在那里)。反馈是即时且直观的。
-
蓝图编辑 (Blueprint Editing) → 非所见即所得 (Non-WYSIWYG):
- 当一个设计师在蓝图中拖动一个节点、连接一根线时,其他人只能看到图结构的变化。这个变化的具体逻辑影响是什么,必须在特定的上下文下 执行(Run) 游戏才能验证。
- 这种修改的意图和结果是 抽象的、非直观的,导致协作者很难理解彼此的修改。
-
-
生动的类比:
- 讲座中提到,合并多个程序员修改的蓝图,就像“试图将三位画家各自画的《蒙娜丽莎》合并成一幅画”一样,几乎是不可能的。
-
结论: 目前行业内没有成熟的蓝图协同解决方案。这不仅是多人协同的难题,即便是单人的版本管理(例如,对比两个版本的蓝图差异)也已经非常困难。这是可视化脚本系统面临的一个根本性挑战。
问题三:能否为游戏逻辑的事件系统(Event System)设置优先级?
核心观点: 为事件系统设置优先级的想法虽然在网络等领域很常见,但 在游戏逻辑(Gameplay Logic)中通常不被推荐,因为它可能破坏逻辑的确定性和可预测性,从而导致难以调试的 Bug。
-
关键术语:
- 事件系统 (Event System): 游戏中用于模块间解耦通信的机制。
- 优先级 (Priority): 定义消息或事件处理的先后顺序。
- 确定性 (Determinism): 在相同的输入和初始条件下,程序每次运行都产生完全相同的结果。
-
对比分析:
-
适用场景 (Good Use Case): 网络消息 (Network Packets)
- 网络环境天生是异步和不稳定的。为关键的网络包(如玩家位置更新)设置高优先级,优先于非关键包(如环境音效同步),是一种合理且有效的优化。
-
不适用场景 (Bad Use Case): 游戏逻辑 (Gameplay Logic)
- 破坏确定性: 游戏逻辑的执行顺序至关重要。例如,是先处理“玩家扣血事件”还是先处理“开启护盾事件”,其结果截然不同。如果引入优先级,这个顺序可能会变得不可预测,导致“玄学”Bug。
- 增加调试难度: 当出现问题时,开发者很难追溯是哪个事件因为优先级问题被提前或延后执行了。游戏引擎通常采用固定的帧更新循环(
Input→Logic→Physics→Render),这种严格的顺序保证了行为的可预测性。引入优先级会打破这一清晰的流程。
-
-
结论: 尽管为事件设置优先级在某些系统中很有用,但在核心 Gameplay 逻辑中,为了保证系统的 稳定、可预测和易于调试,通常会避免使用这种机制,转而依赖于引擎固定的、确定性的更新顺序。
游戏逻辑与AI基础
一、 深入探讨:游戏事件系统(Event System)的设计哲学
核心议题:游戏逻辑的事件系统是否应该引入优先级?
讲座的开篇探讨了一个深刻的引擎架构问题:我们是否应该为游戏逻辑中的事件(Event)设置优先级?讲师的观点非常明确,并从系统设计的根本原则出发进行了解释。
核心观点:强烈建议不要为游戏逻辑事件设置优先级。
虽然在网络消息等领域,优先级是一种常用且有效的机制,但在游戏逻辑中引入它,会带来一系列深远的设计问题。
反对引入优先级的关键理由
-
破坏解耦(Decoupling)原则:
- 关键术语: 发布者-订阅者模式 (Publisher-Subscriber Pattern)
- 在理想的发布-订阅模式中,事件的发布者(Publisher)不应关心谁是订阅者(Subscriber),更不应假设订阅者的处理顺序。
- 一旦引入优先级,就等于在系统中引入了隐式的执行顺序假设。发布者开始依赖于“某个高优先级的系统会先处理我的事件”,这导致发布者与订阅者之间产生了强耦合,违反了系统设计中最重要的解耦原则。
-
与并行化(Parallelism)架构冲突:
- 核心挑战: 现代游戏引擎架构正朝着大规模并行化的方向发展。在多核、多线程环境下,我们无法也根本不应该去保证任务(或事件处理)的精确执行顺序。
- 关键概念: 顺序无关性 (Order Independence)。一个设计良好的并行系统,其最终结果不应该依赖于事件的处理顺序。
- 如果系统的正确性依赖于事件A必须在事件B之前处理(即依赖优先级),那么这个系统将极难并行化,或者在并行化后会产生大量难以调试的竞态条件(Race Condition)问题。
-
增加系统的脆弱性与维护成本:
- 引入优先级会带来大量的 隐藏假设(Hidden Assumptions)。当新功能加入或旧功能修改时,开发者很难理清这些隐含的顺序依赖,极易引发意想不到的Bug。
- 这使得引擎的可扩展性、可维护性和鲁棒性(Robustness)都大打折扣。
现实中的权衡与妥协
讲师也承认,在一些引擎的实践中,确实存在某种形式的“顺序”。但这通常是 宏观层面、系统级别 的同步点,而非微观的事件优先级。
- 示例: 先
fork出所有物理计算的job,等待它们全部完成后(join),再开始处理所有动画系统的job。 - 本质区别: 这是一种粗粒度的系统间依赖,而非细粒度的事件优先级。讲师认为,这仍然是一种不够彻底的并行化架构。
结论:设计的黄金法则是“解耦”
游戏引擎是一个极其复杂的系统。保持各个子系统之间的低耦合,让它们像“黑盒”一样独立工作,是确保系统长期 可维护、可扩展、高鲁棒性 的根本原则。
二、 课程正文:游戏人工智能(Game AI)入门
课程结构调整:为何将AI分为两部分?
讲师解释了本次课程内容的特殊安排。由于AI部分的内容异常丰富、有趣且富有想象力(准备了180页的课件),为了保证深度和质量,决定将其拆分为两节课。
-
第一部分:基础AI (Basic AI)
- 目标: 奠定所有高级AI行为的基础。
- 核心内容:
- 寻路 (Pathfinding)
- 导航 (Navigation)
- 群体模拟 (Crowd Simulation)
- 环境感知 (Environmental Perception)
- 基础决策算法 (Basic Decision-making Algorithms)
-
第二部分:高级AI (Advanced AI)
- 目标: 探讨现代游戏中更复杂的AI架构和前沿话题。
- 核心内容:
- 基于目标的AI (Goal-Oriented AI)
- 基于计划的AI (Planning-based AI)
- 深度学习 (Deep Learning) 在游戏AI中的应用(例如:DeepMind如何用AI玩星际争霸)。
AI的基石:让角色在世界中“动”起来
在探讨任何复杂的“智能”行为之前,我们必须解决一个最根本的问题: AI角色如何理解并在这个虚拟世界中移动?
- 类比: 人类通过视觉和本能就能判断哪里是路、哪里是障碍物。但在游戏中,这一切都需要精确的计算。
- 核心问题: 即使我们已经有了世界的几何信息(渲染)、物理规则(物理引擎)和角色动画(动画系统),角色本身并不知道“哪里可以走,哪里不能走”。
- AI的第一个基础 (Foundation): 导航系统 (Navigation System)。
- 它的核心任务就是解决角色在复杂游戏世界中的移动问题,为所有上层的AI决策(去哪里、做什么)提供最基础的空间认知能力。这是构建一切智能行为的起点。
AI导航系统基础
一、 AI导航系统 (Navigation System) 的重要性
AI导航系统是游戏AI的基石,它解决了AI角色如何在游戏世界中移动的根本问题——即 判断哪些区域可通行,并找到从A点到B点的路径。
- 核心观点: 即使在鼓励玩家自由探索的开放世界游戏中,为AI角色提供自动寻路能力也是必不可少的。玩家可以没有自动寻路,但AI必须有。
- 关键术语:
- Navigation System (导航系统): 允许AI在复杂游戏世界中进行路径规划和移动的整套系统。
- Pathfinding (寻路): 在导航系统中寻找有效路径的核心算法过程。
二、 导航系统的三个核心步骤
一个完整的导航系统通常由三个逻辑上递进的阶段构成,将一个简单的“去那里”的指令,转化为平滑、可信的角色移动。
-
世界表达 (World Representation)
- 核心观点: 游戏世界对AI来说本身是不可理解的。我们必须先将复杂的3D场景转换成一种 AI可以理解的、简化的数据结构,用以描述哪些地方可以走,哪些地方不能走。这是后续所有寻路算法的基础。
-
路径寻找 (Pathfinding)
- 核心观点: 在已经建立好的“世界表达”数据结构上,运行寻路算法(如A*等),找到从起点到终点的理论上最优或可行的路径。
- 常见问题: 这个阶段生成的路径通常是“折线”状的(原文称"ZC"),由一系列直线段构成,在视觉上非常生硬和机械。
-
路径平滑 (Path Smoothing / Path Motion)
- 核心观点: 对
Pathfinding阶段生成的折线路径进行后处理,将其 优化成一条更自然、更平滑的曲线,使其符合生物(如人或动物)的真实运动习惯。
- 核心观点: 对
三、 深入解析第一步:世界表达 (World Representation)
这是导航系统中最基础也最容易被忽略的一环。它本身又包含两个子问题:定义可通行区域和选择数据结构。
3.1 定义可通行区域 (Workable Area)
Workable Area 是AI可以活动的“舞台”,它并不仅仅等同于游戏世界的物理几何体。
- 核心观点:
Workable Area是由设计师基于多种规则定义的逻辑空间,而不只是美术创建的场景。 - 决定
Workable Area的因素:- 物理碰撞 (Physical Collision): 最基础的规则,AI不能穿墙。
- 角色能力 (Character Capabilities):
- 攀爬高度 (Climb Height): AI可以越过低于特定高度的障碍物(如台阶)。
- 跳跃距离 (Jump Distance): AI可以跳过特定宽度内的沟壑。
- 特定AI类型 (Agent-Specific Rules):
- 关键洞察: 不同的AI类型在同一个物理世界中,拥有完全不同的
Workable Area。 - 示例: 步兵、坦克、马匹的可通行区域各不相同。坦克无法通过狭窄的巷子,但步兵可以;马匹可以跳过沟壑,但普通人不行。
- 关键洞察: 不同的AI类型在同一个物理世界中,拥有完全不同的
- 设计意图 (Designer Overrides):
- 关键术语: 空气墙 (Air Wall)。设计师为了限制玩家或AI的活动范围、优化性能或引导流程,会手动设置不可见的障碍。
3.2 表达Workable Area的数据格式
将设计师定义的Workable Area转化为计算机可处理的数据结构,是实现寻路的前提。现代游戏引擎通常会内置支持以下一种或多种方案。
- 常见的数据结构:
- 路点网络 (Waypoint Network)
- 网格 (Grid)
- 导航网格 (Navigation Mesh / NavMesh)
- 稀疏体素八叉树 (Sparse Voxel Octrees)
3.3 经典方法:路点网络 (Waypoint Network)
这是一种早期游戏中非常流行和经典的方法。
-
核心概念: 将可通行区域抽象成一个 由点(Waypoint)和边(连接点的路径)组成的图(Graph)。设计师在关键位置(如门口、拐角、桥梁)手动放置路点,形成一个网络。
-
工作流程:
- 进入网络: AI从当前位置移动到路点网络中最近的一个点或边。
- 网络内寻路: 在路点图上执行图搜索算法,找到一条从入口点到出口点的路径。
- 离开网络: AI从网络中的终点移动到最终的目标位置。
-
绝佳类比: 地铁交通系统。
- 你的家/公司 = 起点/终点。
- 地铁站 = 路点 (Waypoints)。
- 地铁线路 = 连接路点的边。
- 出行过程 = 从家走到最近的地铁站 → 乘坐地铁 → 从目标地铁站走到公司。
-
优点 (Pros):
- 实现简单: 易于理解和编码。
- 性能高效: 将复杂的3D空间简化为稀疏的图结构,寻路计算开销小。
-
缺点 (Cons):
- 维护成本高: 严重依赖设计师手动放置和调整路点。当地图发生修改时,路点网络需要手动同步更新,非常耗时且容易出错。
- 运动不自然: AI倾向于沿着网络的中心线移动,无法充分利用整个可通行区域的宽度,导致所有AI“扎堆走直线”,除非路点铺设得极其密集。
- 应用现状: 由于其缺点,这种方法在现代游戏中已逐渐被更先进的技术(如NavMesh)所取代。
空间表达的三种主流方法
在上一部分我们了解了寻路问题的基本框架后,这一部分我们将深入探讨解决寻路问题的基石: 如何向 AI 描述和表达游戏世界的可通行空间。选择不同的空间表达方式,会直接影响寻路算法的效率、内存占用、AI 行为的真实感,以及对动态环境的适应能力。
本次讲座主要对比了三种主流的空间表达方法:路网(Waypoint Network)、网格(Grid)和导航网格(Navigation Mesh)。
1. 路网 (Waypoint Network) - 点的抽象
这是一种相对早期和简单的空间表达方式。
-
核心观点:将整个可通行区域抽象成一个 图(Graph),其中的 节点(Node) 就是预先放置的 路点(Waypoint),而 边(Edge) 则代表了路点之间可以直线连通的路径。寻路过程就变成了在这个图上寻找两个节点间的最短路径。
-
关键术语: 路网 (Waypoint Network)
-
主要缺点:
- 维护成本高:当地图发生变化时(例如,设计师移动了一块石头),需要手动更新路网,非常繁琐且容易出错,导致路网与实际场景不匹配。
- AI 行为不自然:AI 会倾向于沿着预设的路径(图的边)行走,尤其是在开阔地带,AI 看起来就像是“走钢丝”,行为显得非常机械和不真实,缺乏灵活性。
- 应用场景局限:由于上述缺点,路网在现代大型游戏中已很少作为主要寻路方案,但在一些场景固定、移动规则简单的游戏中(如某些回合制战棋游戏)仍有应用价值。
2. 网格法 (Grid-Based) - 单元的抽象
为了克服路网表达能力不足的问题,一个非常自然的想法就是用更细致的单元来覆盖整个空间。
-
核心观点:将游戏世界(通常是2D平面)划分成一个由 均匀单元(Cell) 组成的 网格(Grid),最常见的是正方形网格,也有六边形网格(如《文明》系列)。每个单元格被标记为“可通行”或“不可通行”。这个过程非常类似于渲染中的 光栅化(Rasterization)。
-
关键术语: 网格 (Grid), 光栅化 (Rasterization)
-
优点:
- 实现与调试简单:逻辑直观,数据结构简单(通常是一个二维数组),非常容易实现和调试。可以通过直接打印网格状态来清晰地观察寻路过程。
- 动态更新友好:当环境中出现新的障碍物时,只需将其“光栅化”到网格上,更新对应单元格的状态即可。这个过程非常快速,对动态环境适应性好。
-
缺点:
- 存储开销巨大:对于大世界和高精度寻路,网格单元尺寸必须很小,导致网格数量爆炸式增长(可达千万甚至上亿级别),占用大量内存。
- 性能瓶颈(缓存不友好):虽然逻辑简单,但性能可能不佳。在内存中,二维数组是线性存储的。当AI在网格中“向下”移动一行时,在内存地址上可能是一个巨大的跳跃 (
current_address + width)。这极易导致 CPU 缓存未命中 (Cache Miss),频繁从主内存读取数据,影响访问效率。 - 无法表达层叠结构:一个标准的2D网格无法描述三维空间中的重叠区域,例如一座桥和桥下的道路。一个
(x, y)坐标的单元格只能有一种状态(可通行或不可通行),无法同时表达桥上和桥下都是可通行的。
3. 导航网格 (Navigation Mesh) - 面的抽象
Navigation Mesh(简称 NavMesh)是现代游戏引擎中最主流、最标准的寻路解决方案,它结合了前两者的优点,并规避了它们的许多缺点。
-
核心观点:用一组 连续且互相连接的凸多边形(Convex Polygons) 来精确地覆盖所有可通行的地面区域。寻路不再是“从点到点”或“从格子到格子”,而是“从多边形到多边形”。
-
关键术语: 导航网格 (Navigation Mesh / NavMesh), 非流形 (Non-manifold)
-
核心优势:
- 空间表达效率高:对于一个广阔的开放区域(如广场或长廊),NavMesh 可能只需要一个或几个大的多边形就能表示,而网格法可能需要成千上万个单元格。这极大地节省了内存。
- AI 行为更自然:AI 在一个大的多边形内部拥有完全的移动自由,可以轻松实现躲闪、迂回、侧移等战术动作,行为表现远比路网和网格法来得生动和智能。
- 完美支持复杂3D结构:可以轻松处理桥梁、斜坡、多层建筑等复杂地形。桥上和桥下可以生成两层独立的 NavMesh,并通过特定的连接边(例如斜坡)将它们在拓扑上连接起来。这种存在重叠和T型连接的几何结构在图形学中被称为 非流形(Non-manifold) 结构。
NavMesh 的关键细节:凸多边形与传送门
NavMesh 的构建有一个非常重要的约束:组成它的必须是 凸多边形 (Convex Polygon)。
-
为什么必须是凸多边形?
-
路径有效性保证:凸多边形有一个关键数学性质: 其内部任意两点之间的连线,完全包含在该多边形内部。
-
规避凹多边形陷阱:如果使用凹多边形,算法在判断“从A点到B点”时,可能会规划出一条直接穿过障碍物(凹陷部分)的无效路径。
上图清晰地展示了在凹多边形中,从A到B的直线路径会穿过不可通行区域,而凸多边形则无此问题。
-
-
多边形走廊 (Polygon Corridor) 与传送门 (Portal)
- 当 A* 等算法在 NavMesh 上找到一条路径后,其结果是一系列相邻的多边形序列,这个序列被称为 多边形走廊 (Polygon Corridor)。
- 两个相邻多边形共享的那条边,被称为 传送门 (Portal)。
- 由于所有多边形都是凸的,任意两个相邻的多边形之间 有且仅有一个 Portal。这个唯一的 Portal 属性为后续的路径平滑(Path Smoothing)算法(如 Funnel Algorithm)提供了极大的便利。
总结对比
| 特性 | 路网 (Waypoint Network) | 网格法 (Grid-Based) | 导航网格 (NavMesh) |
|---|---|---|---|
| 抽象层级 | 点 (Graph Nodes) | 单元 (Grid Cells) | 面 (Polygons) |
| AI 行为 | 机械、不自然 | 较为生硬(逐格移动) | 灵活、自然 |
| 内存占用 | 低 | 非常高(随精度指数增长) | 相对较低,表达效率高 |
| 动态更新 | 困难,需手动 | 非常简单、快速 | 较复杂,需重新生成部分网格 |
| 3D 结构 | 有限支持 | 几乎不支持(层叠结构) | 完美支持 |
| 现代应用 | 简单或老式游戏 | 特定场景、原型开发 | 业界标准 |
总而言之,NavMesh 以其高效的空间表达、对复杂地形的强大支持以及促成更自然的 AI 行为,成为了当今游戏开发中处理寻路问题的首选方案。
游戏世界中的寻路(Pathfinding)技术深入
一、导航网格(Navigation Mesh)的深入探讨
在上一部分的基础上,我们继续深入探讨 导航网格(Navigation Mesh, NavMesh) 这一成熟且高效的世界表达方式。
1.1 NavMesh 的核心优势
- 支持三维重叠空间:与二维网格(Grid)不同,NavMesh 可以轻松表达桥梁、立交桥等多层结构,允许路径在垂直方向上重叠。
- 高效率的表达:对于广阔、平坦的可通行区域(如走廊、广场),NavMesh 仅需一个或少数几个凸多边形(Polygon)即可表示。相比之下,Grid 模式需要成千上万个单元格,因此 NavMesh 在存储和寻路计算速度上都有巨大优势。
- 灵活性:能够很好地贴合复杂的游戏场景几何体。
1.2 NavMesh 的两个主要挑战
-
生成过程复杂:
- 从任意的游戏关卡几何体(Mesh)自动生成高质量的 NavMesh 是一个复杂的计算几何问题。
- 理解生成原理至关重要:虽然现在有许多成熟的开源库(如 Recast/Detour)可以完成这项工作,但作为引擎开发者,了解其背后的原理,可以帮助我们更好地:
- 调试和修改:当自动生成的结果不符合游戏设计意图时,可以进行手动干预和修正。
- 参数调优:通过调整生成参数(如角色高度、坡度限制等),产出更符合游戏需求的导航数据。
-
表达能力的局限性——仅限于“表面”移动:
- NavMesh 本质上是一种 2.5D 的表达,它描述的是角色可以在哪些“表面”上行走。
- 它无法表达真正的三维空间自由移动,例如:
- 飞行单位(鸟、飞机、无人机)
- 太空飞船
- 可以将其理解为“像蚂蚁一样贴着地面行走”的导航模式。
二、超越2D:三维空间中的寻路
当游戏需要支持空中或水下等真三维空间的 AI 导航时,NavMesh 就不再适用。这时需要采用能够表达三维连通空间的结构。
- 核心技术: 稀疏体素八叉树(Sparse Voxel Octree)
- 基本原理:
- 将整个三维空间看作一个巨大的立方体(Voxel)。
- 递归地进行“一分八”:如果一个立方体内完全是可通行区域(没有障碍物),则它就是一个大的叶子节点。
- 如果立方体内包含障碍物,则将其均匀地细分为 8 个更小的立方体,并对每个小立方体重复此过程。
- 持续细分,直到达到预设的精度阈值,或立方体内不再有障碍物。
- 应用场景:
- 太空空战游戏(如 EVE Online 的某些场景)
- 无人机模拟
- 飞行模拟游戏
- 主要缺点:
- 存储开销大:需要消耗较多的内存来存储整个树状结构。
- 寻路算法相对复杂:在八叉树结构上进行高效的路径搜索比在二维图上更具挑战性。
三、寻路的本质:图搜索(Graph Search)
无论是 Waypoint、Grid 还是 NavMesh,它们在用于寻路时,都可以被抽象为同一种数据结构—— 图(Graph)。
-
抽象过程:
- Waypoint:每个路点是一个 节点(Node),路点间的连接是 边(Edge)。
- Grid:每个格子中心是一个节点,与相邻格子的连接是边。
- NavMesh:每个多边形(Polygon)的中心是一个节点,多边形间的公共边是边。
-
寻路问题的核心:在构建好的图结构上,从一个起始节点找到一条通往目标节点的路径。
寻路算法需要解决的两个核心问题:
- 可达性(Reachability):必须准确判断是否存在一条从起点到终点的有效路径(回答 Yes/No)。
- 最优性(Optimality):在所有可达路径中,找到一条“最好”的。通常指路径最短。
游戏开发中的“最优”哲学
与纯粹的数学问题不同,游戏开发中的“最优”往往带有妥协性质。
核心观点:在游戏中,一个 “看似合理” 的解,往往比一个计算开销巨大的 “绝对最优解” 更有价值。玩家通常无法分辨一条路径是否是 100% 的数学最短路径,但他们能轻易察觉到 AI 的寻路决策是否“愚蠢”(如绕远路、撞墙)。因此,算法的选择需要在效果和性能之间取得平衡。
四、经典寻路算法回顾
寻路的本质是图搜索问题,最基础的搜索算法有两类:
4.1 基础搜索算法
-
深度优先搜索(Depth-First Search, DFS)
- 策略:“一头扎到底”,沿着一条路径不断深入,直到无法前进再回溯。
- 特点:通常认为是用时间换空间。能快速找到一条解,但不保证是最近的。
-
广度优先搜索(Breadth-First Search, BFS)
- 策略:“层层扩散”,从起点开始,先访问所有相邻的节点,再访问邻居的邻居。
- 特点:通常认为是用空间换时间。在无权重的图中, BFS 保证能找到边数最少的路径。
-
局限性:对于复杂的、带权重的图(例如,在沼泽里移动比在平地上慢),DFS 和 BFS 为了找到最优解,往往会探索大量无关节点,导致效率低下。
4.2 Dijkstra 算法:最短路径的基石
为了解决带权重图上的最短路径问题,大名鼎鼎的 Dijkstra 算法 应运而生。
- 核心目标:计算从单个源点到图中所有其他节点的最短路径。
- 算法思想(高层概述):
- 初始化:创建一个列表,记录从起点到所有其他节点的预估最短距离。将起点的距离设为 0,其他所有节点设为无穷大。
- 迭代选择:从所有未访问过的节点中,选择一个当前已知距离最小的节点作为“当前节点”。
- 邻居松弛(Relaxation):对于“当前节点”的所有邻居,检查是否可以通过“当前节点”抵达它们,从而获得一条更短的路径。
if (distance[start] -> distance[current] + weight(current, neighbor)) < distance[start] -> distance[neighbor]- 如果路径更短,则更新到这个邻居的距离,并记录下“当前节点”是它的“前驱节点”。
- 标记与重复:将“当前节点”标记为已访问,然后回到第 2 步,直到目标节点被访问过,或者所有可达节点都被访问。
Dijkstra 算法是许多更高级寻路算法(如 A*)的基础,它通过一种贪心策略,系统性地扩展搜索范围,保证了最终找到的路径是最优的。
游戏中的寻路算法 (Pathfinding)
从 Dijkstra 到 A* 的演进
在游戏世界中,让 AI 角色智能地从 A 点移动到 B 点是至关重要的功能。这背后依赖于强大的寻路算法。本次内容将从经典的 Dijkstra 算法出发,逐步深入到在游戏业界被广泛应用的 A* 算法,并探讨其在不同场景下的实现细节。
一、 经典寻路算法:Dijkstra 算法
Dijkstra 算法是图论中解决单源最短路径问题的经典算法。它的核心目标是找到从一个起始点到图中所有其他点的理论最短路径。
核心思想
Dijkstra 算法采用一种贪心策略。它维护一个尚未访问的顶点集合,并总是从中选择一个距离起点最近的顶点进行探索。
- 关键术语:
- 已访问集合 (Visited Set): 已经找到最短路径的顶点。
- 未访问集合 (Unvisited Set): 尚未找到最短路径的顶点。
- 成本/距离 (Cost/Distance): 从起点到当前顶点的路径长度。
算法流程(直观理解)
讲座中通过一个动画生动地展示了其过程,可以总结为以下步骤:
- 初始化: 将起点的成本设为 0,其他所有顶点的成本设为无穷大。
- 选择节点: 从所有未访问的顶点中,选择一个当前成本最低的顶点(我们称之为
current)。 - 探索邻居: 遍历
current顶点的所有邻居。对于每个邻居,计算从起点经过current到达它的新路径成本。 - 更新成本: 如果新路径的成本比该邻居记录的旧成本更低,则更新该邻居的成本。
- 循环: 将
current顶点标记为已访问,然后重复步骤 2、3、4,直到目标点被访问,或者所有可达的顶点都被访问。
路径重建
Dijkstra 算法本身只计算出最短距离,并不能直接告诉我们路径。为了重建路径,我们需要在更新成本时额外记录一个信息:
previous vertex(前驱顶点): 当我们通过顶点 U 更新了顶点 V 的最短距离时,我们就记录 V 的前驱顶点是 U。当算法结束后,从目标点开始,沿着前驱顶点一路回溯,就能得到完整的路径。
游戏中的局限性
尽管 Dijkstra 算法能保证找到理论上的最优解,但在游戏开发中存在一些问题:
- 性能开销: 它是一种发散式的搜索,会探索所有方向,直到找到目标,计算量较大。
- “不自然”的路径: 理论上的最优解在游戏中可能看起来很机械,不符合人类的直觉行为。人类寻路时并不会精确计算,而是朝着目标的大致方向前进。
二、 启发式寻路:A* (A-Star) 算法
为了解决 Dijkstra 算法的局限性,业界引入了 启发式算法 (Heuristic Algorithm),其中最负盛名的就是 A* 算法。它在游戏引擎中是寻路功能的基石。
核心思想
A* 算法是 Dijkstra 算法的扩展。它在选择下一个要探索的顶点时,不仅考虑已经走过的成本,还引入了一个对未来成本的预估,这个预估就是所谓的 启发函数 (Heuristic Function)。
通过结合“已知的过去”和“猜测的未来”,A* 能够更有方向性地进行搜索,优先探索那些“看起来”离终点更近的路径。
关键公式
A* 算法为每个顶点计算一个评价值 f(n),并总是优先选择 f(n) 最小的顶点进行探索。
f(n) = g(n) + h(n)
g(n)(实际成本): ** 从 起点到当前顶点n的实际路径成本**。这部分与 Dijkstra 算法中的成本计算完全相同。h(n)(启发成本): 从当前顶点n到终点的预估路径成本。这是 A* 算法的灵魂,h代表 Heuristic。f(n)(总评价值): 对从起点经过n到达终点的总成本的估计值。
与 Dijkstra 的关键区别
- 搜索方向性: Dijkstra 只看
g(n),是盲目地向外扩张;A* 看f(n) = g(n) + h(n),会优先朝着目标方向扩张,搜索范围更聚焦。 - 终止条件: 经典的 Dijkstra 算法需要将目标点设置为
current并完成一次探索后才能保证最短路径。而 A* 算法只要 第一次到达(reach)目标点,就可以认为找到了足够好的路径并终止,这在实践中极大地提升了效率。
启发函数 (Heuristic Function) 的设计
启发函数 h(n) 的设计至关重要,它直接影响 A* 算法的效率和准确性。其设计与世界的表示方式紧密相关。
-
网格世界 (Grid World):
- 启发函数: 通常使用 曼哈顿距离 (Manhattan Distance),因为它计算简单且符合网格中“只能走格子”的规则。
- 公式:
Distance = |x1 - x2| + |y1 - y2| -
导航网格 (Navigation Mesh / NavMesh):
g(n)的计算: 路径由一系列连续的多边形(Polygon)组成。g(n)的长度通常是通过连接这些多边形 公共边 (Portal) 的中点来累加计算的。h(n)的计算: 在 NavMesh 中,精确计算h(n)非常复杂。因此,业界采用了一种非常实用但“粗暴”的方法:直接计算当前位置到目标点的 欧氏距离 (Euclidean Distance),即两点间的直线距离。- 为什么有效: 尽管这种估算忽略了所有障碍物,但它提供了一个强有力的方向指引,引导搜索算法“抄近路”。由于
h(n)永远不会高估实际距离(在没有障碍的情况下,直线最短),它能保证 A* 找到的路径是正确的。
A* 算法的优势
- 高效: 在游戏场景中,尤其是在开阔地带,A* 算法的启发式引导使其能够极快地收敛,有时甚至能“一条直线”冲向目标,避免了大量不必要的计算。
- 平衡: 它在算法效率和路径质量之间取得了绝佳的平衡,是游戏 AI 寻路的事实标准。
三、 启发函数的影响
讲座最后提到了一个经验之谈:启发函数 h(n) 的估算精度会影响算法的行为。
- 当
h(n)估值偏低 (Underestimate) 时:- 算法会进行更多的探索,搜索范围更广,类似于 Dijkstra。
- 结果: 搜索速度变慢,但更有可能找到理论上的最短路径。
- 当
h(n)估值偏高 (Overestimate) 时 (讲座内容在此中断,但我们可以推断):- 算法会变得非常“贪心”,可能过于相信启发信息而错过实际更优的路径。
- 结果: 搜索速度极快,但找到的路径不保证是最优解。
在游戏中,只要 h(n) 不会高估实际最短距离(即所谓的“可采纳启发式 Admissible Heuristic”),A* 就能保证找到最优路径。而欧氏距离恰好满足这个条件。
总结: 本次内容清晰地展示了游戏寻路技术从理论到实践的演进。Dijkstra 算法为我们提供了寻找最优解的基础,而 A* 算法通过引入巧妙的启发函数,在保证路径质量的同时,极大地提升了运算效率,完美契合了游戏引擎对性能和效果的平衡需求。理解 A* 的核心公式 f(n) = g(n) + h(n) 以及如何根据场景设计 h(n),是掌握现代游戏 AI 寻路的关键。
高级寻路技术:路径平滑与导航网格生成
在上一部分我们了解了 A* 算法作为寻路核心的基础。这一部分,我们将深入探讨两个在实际游戏项目中至关重要的高级主题:如何让寻路结果看起来更自然,以及支撑这一切的导航网格(Navigation Mesh)是如何被自动创建的。
一、 A* 算法启发函数 (Heuristic) 的经验之谈
在深入新内容之前,讲座分享了一个关于 A* 算法启发函数(Heuristic)选择的行业经验,这对于算法微调非常有价值。
- 核心观点: 启发函数
h(n)的估算倾向会影响寻路算法的行为和效率。- 低估未来距离 (Underestimate): 当你倾向于低估从当前点到终点的实际距离时(即
h(n)的值偏小),A* 算法会探索更多的节点。- 优点: 更容易找到理论上的最短路径。
- 缺点: 算法迭代次数更多,计算速度较慢。
- 高估未来距离 (Overestimate): 当你倾向于高估未来距离时(即
h(n)的值偏大,但注意:为保证找到解,h(n)不能超过真实最短距离,否则 A* 会退化为贪心算法,不保证最优解),算法会更“功利”地朝向终点。- 优点: 算法收敛更快,迭代速度快。
- 缺点: 可能找不到真正的最短路径,而是找到一条“看起来很短”的次优路径。
- 低估未来距离 (Underestimate): 当你倾向于低估从当前点到终点的实际距离时(即
实践建议: 在绝大多数游戏场景中,使用经典的曼哈顿距离或欧氏距离作为启发函数已经足够优秀,能够满足性能和效果的需求。上述经验可作为特定场景下进行性能优化时的参考。
二、 路径平滑 (Path Smoothing): 从“机器人”到“智能体”
A* 算法为我们找到了一条由路点(Waypoint)组成的路径,但如果直接让角色沿着这些点移动,效果会非常不自然。
1. 问题:Z字形(Zig-Zag)路径
- 核心问题: A* 算法直接在网格(Grid)或 NavMesh 中心点上生成的路径,往往是生硬的、不自然的 Z字形(Zig-Zag) 路径。
- 表现: 角色移动时会频繁地进行急转弯,尤其是在拐角处,它会先走到拐角中心点,然后突然转向,完全不像一个智能生物,更像一个笨拙的机器人。
- 期望效果: 我们希望角色能像人一样,在拐角处“切弯”,走一条平滑、流畅的弧线或直线。
2. 解决方案:漏斗算法 (Funnel Algorithm)
为了解决Z字形问题,业界引入了路径平滑技术,其中最经典和有效的就是 漏斗算法(Funnel Algorithm)。
-
核心思想: 将 A* 找到的 多边形走廊(Polygon Corridor) (即路径经过的一系列连续多边形)想象成一个通道,然后从起点开始,试图找到一条尽可能“直”的视线穿过这个通道,直到视线被通道的“墙壁”(即多边形的边)挡住为止。这个不断收窄的视线范围,形态上很像一个 漏斗(Funnel) 或 烟囱(Chimney)。
-
算法直观步骤:
-
构建初始漏斗: 从起点出发,以连接第一个和第二个多边形的 公共边(Portal) 的两个端点为边界,形成一个初始的扇形区域(这就是我们的“漏斗”)。
-
向前迭代并收紧漏斗: 依次处理路径上的下一个多边形。
-
如果下一个多边形的 Portal 完全在当前漏斗的扇形区域内,则用这个新的 Portal 的两个端点来 收紧(Tighten) 漏斗的左右边界。这意味着我们的直线视线可以继续向前延伸。
-
如下图所示,从起点
Start出发,Portal 1构成了初始漏斗。Portal 2在其内部,于是我们用Portal 2的顶点更新并收紧了漏斗。
-
-
遇到拐点: 当下一个 Portal 不再完全位于当前漏斗内部时,说明我们遇到了一个无法直接“看穿”的拐角。
- 此时,漏斗的一侧边界(左或右)会“越过”另一侧,漏斗发生“交叉”。
- 我们需要在漏斗的 顶点(Apex) (即上一个拐点或起点)和这个造成交叉的 Portal 之间做出选择,确定新的路径点。通常选择距离更近的那个漏斗边界顶点作为新的路径拐点。
- 例如上图中,当处理到
Portal 3时,它的一部分在漏斗之外,算法判断出需要沿着绿色的边(左边界)前进,于是在该边界的顶点处生成了一个新的路径点。
-
重置漏斗并重复: 以这个新生成的路径拐点为新的起点,用它和下一个 Portal 重新构建漏斗,重复上述过程。
-
直达终点: 如果在迭代过程中, 终点(End) 出现在了当前漏斗的扇形区域内,那么说明从当前路径点到终点之间已经没有障碍,可以直接连接一条直线。
-
-
最终效果: 经过漏斗算法处理后,原本由十几个路点组成的复杂路径,可能被简化为只有一两个拐点的平滑路径,极大地提升了角色移动的真实感。
3. 从2D到3D的挑战
- 关键差异: 教学演示中的漏斗算法通常是基于 2D 共面 多边形。但在真实的游戏引擎中, Navigation Mesh 是由 不共面的 3D 三角形 组成的。
- 解决方案: 在实际应用中,需要对算法进行扩展。一种常见的做法是在处理多边形走廊时,将这些不共面的三角形 在局部“拉平”或投影到一个合适的平面上 进行计算,但这会引入更多的几何运算和边界情况处理,是实现时的一个难点和重点。
三、 导航网格的自动生成 (Automated Navigation Mesh Generation)
我们一直在讨论如何在 NavMesh 上寻路,但这个 NavMesh 本身是如何从复杂的、由美术师创建的三维游戏场景中产生的呢?
- 早期方法: 手动创建。由关卡设计师(Level Designer)在 3D 建模软件(如 3ds Max, Maya)中手动“拉”出可通行的网格。这种方法效率低下、易出错,且难以维护。
- 现代方法: 自动化生成。现代游戏引擎普遍采用算法来自动分析场景几何体,并生成 NavMesh。
著名开源库:Recast Navigation
-
Recast 是业界最著名、应用最广泛的导航网格生成开源库,被许多商业引擎和自研引擎所采用。
-
它的工作流程非常经典,主要分为以下几个步骤:
-
第一步:体素化 (Voxelization)
- 概念: 将整个连续的三维游戏场景,转换成一个由大量微小立方体(体素 Voxel)组成的离散化世界,类似于《我的世界》(Minecraft) 的风格。
- 目的: 将复杂的、任意形状的场景几何体,简化为规则的、易于计算的栅格结构。这是后续所有计算的基础。
-
第二步:标记可行走区域 (Marking Walkable Areas)
- 概念: 在体素化的世界中,根据预设的参数(如角色的高度、半径、可攀爬的坡度等),标记出所有角色可以站立和通过的体素。
- 结果: 得到一个清晰的、表示所有 可通行空间(Walkable Space) 的体素集合。
-
讲座在这里为后续内容埋下了伏笔,接下来的部分将会继续讲解 Recast 是如何基于这些可行走区域,最终构建出我们所需要的由多边形组成的 Navigation Mesh 的。
Navigation Mesh 的生成与高级应用
在上一部分我们了解了寻路的基本概念,现在我们将深入探讨现代游戏引擎中应用最广泛的寻路技术—— 导航网格(Navigation Mesh, NavMesh)。这部分内容将聚焦于 NavMesh 的生成流程、在实际项目中的高级应用以及它所面临的挑战。
一、 NavMesh 的生成:从体素到多边形
将一个复杂的游戏世界场景转换成可供 AI 使用的、简洁的 NavMesh,是一个多步骤的复杂过程。计算机无法像人眼一样直观地识别可行走区域,它需要一套严谨的算法流程。
1. 空间体素化 (Voxelization)
第一步是将连续的游戏世界几何体离散化,转换成一个由 体素(Voxel),即三维像素,组成的网格世界。
- 核心观点:将复杂的场景模型(如高低不平的地面、建筑)近似为一个巨大的、类似《我的世界》的方块集合。这是后续所有计算的基础。
2. 可行走区域标记 (Walkable Area Marking)
在体素化的基础上,引擎需要筛选出所有 AI 角色可以站立和移动的体素。
- 核心观点:通过分析相邻体素间的几何关系来判断连通性。
- 关键判断依据:
- 最大坡度 (Maximum Slope):相邻体素间的垂直高度差不能超过一个阈值(例如 45° 或 60°)。这与角色控制器中的爬坡能力设定一致,用于排除过于陡峭的峭壁。
- 连通性:排除那些悬空或被障碍物隔断的区域。
- 经过这一步,我们就得到了一片片蓝色的 可行走区域 (Workable Area)。
3. 区域划分:距离场与洪水算法
为了将零散的可行走体素组织成有意义的大块区域,引擎会采用一种非常巧妙的方法。
-
步骤 1: 边缘检测 (Edge Detection)
- 首先,找到所有位于可行走区域边缘的体素。一个体素如果其邻居中至少有一个是不可行走的,那么它就被定义为边缘体素。
-
步骤 2: 生成距离场 (Distance Field)
- 核心观点:距离场是一个极其强大的概念,在渲染、动画、物理和 AI 中都有广泛应用。在这里,它的作用是为空间划分提供依据。
- 实现:计算每一个可行走体素到最近的边缘体素的距离。这样,越是位于区域中心的体素,其距离值就越大。
-
步骤 3: 区域分割 (Region Partitioning)
- 以距离场中距离值最大的点(即离所有边缘最远的点)为“种子”,使用类似 洪水填充 (Flood Fill) 或分水岭算法,向外进行区域扩张。
- 当不同“种子”扩张的区域相遇时,便形成了区域的边界。
- 目标:通过这个过程,将整个复杂的行走空间,自动分割成若干个近似 凸多边形 (Convex Polygon) 的区域。
4. 多边形化 (Polygonization)
最后一步,是将这些分割好的体素区域,转换成最终的、由多边形(通常是三角形)组成的 NavMesh。
- 核心观点:这是一个相对复杂的几何算法过程,旨在用最少的顶点和多边形来精确表达可行走区域。
- 工程实践:由于算法复杂且容易出错,大多数引擎会使用成熟的开源库(如 Recast/Detour)来完成这一任务。
二、 NavMesh 的高级应用与挑战
生成基础的 NavMesh 只是第一步。要让它在商业级游戏中真正发挥作用,还需要处理更多复杂情况。
挑战:更新与手动修复的冲突
- 核心问题:NavMesh 的生成过程是破坏性的。如果美术更新了地图,整个 NavMesh 必须重新生成。这意味着设计师之前对 NavMesh 进行的任何手动微调和修复都会全部丢失。
- 影响:这是一个巨大的工作流挑战,限制了设计师对寻路细节的精细控制。
高级应用 1:区域标记 (Polygon Flags)
- 核心观点:可以为 NavMesh 上的不同多边形打上 标签 (Flag),赋予它们不同的属性。
- 实现方式:通常不是让设计师直接在生成的 NavMesh 上标记,而是在原始地图上绘制区域,然后将这些区域属性映射到生成的 NavMesh 多边形上。
- 应用场景:
- 材质区分:标记草地、沙地、河流等,触发不同的脚步声和粒子特效。
- 寻路成本:为不同区域设置不同的移动成本。例如,让沼泽地的寻路成本高于公路,AI 会优先选择公路。
- 区域禁用:对于某些特定类型的 AI,可以动态地将某些区域(如水域)在寻路时视为不可通行。
高级应用 2:动态障碍与分块更新 (Dynamic Obstacles & Tiled Generation)
- 面临的挑战:在大型开放世界中,场景是动态变化的(如倒塌的墙壁、停放的载具)。在游戏运行时完整地重新计算 NavMesh 是不现实的,性能开销极大。
- 解决方案:分块 (Tiling)
- 核心观点:将整个大世界切分成固定大小的 网格块 (Tile) (如 64x64 米)。
- 工作流程:
- 为每一个 Tile 单独生成 NavMesh。
- 当一个动态障碍物出现或消失时, 只重新计算受影响的少数几个 Tile 的 NavMesh。
- 技术难点:必须确保相邻 Tile 边界处的 NavMesh 顶点能够完美对齐和缝合,否则 AI 在跨越 Tile 边界时会出现寻路中断。
高级应用 3:手动连接与离网链接 (Off-Mesh Links)
- 核心局限:自动生成的 NavMesh 只能表达“在表面行走”的行为,无法理解游戏世界中特殊的移动方式。
- 解决方案:离网链接 (Off-Mesh Links)
- 核心观点:由设计师手动在两个 NavMesh 多边形之间创建特殊的连接,以代表人无法直接走过去、但可以通过特殊方式到达的路径。
- 应用实例:
- 攀爬点:连接地面和高台。
- 传送门 (Teleport):连接两个不相邻的区域。
- 滑索 (Zipline):如《死亡搁浅》中从电线杆滑下。
- 跳跃点:标记一个可以跳过的小间隙。
三、 总结:从算法到商业级引擎系统
讲座强调,一个商业级游戏引擎与一个简单的算法实现有着本质区别。它不仅要实现核心算法,更重要的是必须构建一个能够支撑复杂、多变、且充满特例的商业游戏开发需求的系统。
这意味着引擎必须具备:
- 鲁棒性:能够处理各种复杂的场景几何。
- 高效性:支持大型世界和动态变化。
- 扩展性:允许设计师通过手动标记和链接来丰富 AI 的行为,满足游戏玩法的特殊需求。
NavMesh 技术正是这一理念的绝佳体现。它从一个纯粹的几何算法出发,逐步演化成一个融合了自动化处理、设计师手动配置和高性能运行时更新的复杂系统。
从寻路算法到商业级游戏引擎的思考
这部分内容是前面关于 Navigation (寻路) 话题的总结与升华。讲座的核心观点从具体的技术实现,转向了对商业级游戏引擎设计哲学的探讨,强调了理论算法与工程实践之间的差距。
1. 超越基础算法:动态世界的寻路挑战
讲座首先指出了一个在真实游戏项目中普遍存在的复杂情况:寻路环境的动态性。
-
核心观点:教科书或基础教程中的寻路算法(如 A*)大多基于一个 静态的导航网格 (NavMesh) 或寻路图。然而,在实际的商业级游戏中,世界是动态变化的,这给寻路系统带来了巨大挑战。
-
关键挑战:动态连接点与连接线
- 在复杂的关卡中,寻路图的 节点 (连接点) 和 边 (连接线) 并非一成不变。
- 例子: 移动平台、可被摧毁的桥梁、动态开启/关闭的门等。
- 这些动态元素意味着寻路系统不能仅仅在游戏开始时预计算一次路径,而必须能够 实时地、高效地 应对寻路图的变化。
2. 商业级游戏引擎的核心特质
基于上述挑战,讲座引申出了关于“什么才是一个合格的游戏引擎”的深刻思考。
-
核心观点:一个真正的、能够支撑开发的商业级游戏引擎,其价值不仅在于实现了多少基础算法,更在于其体系的完备性与开放性,能够从容应对商业级游戏的复杂度。
-
关键特质:
- 支持开放性 (Extensibility):引擎的各个子系统(如此处的寻路系统)必须是 可扩展、可定制 的。它不能是一个封闭的黑盒,而应提供接口和机制,让开发者能够根据项目特定的复杂需求(如动态寻路)进行二次开发和功能增强。
- 体系完备性 (Robust Architecture):一个能“work”的引擎,其底层架构必须是健全和完整的。它能够将各个复杂的子系统(渲染、物理、动画、AI、寻路等)有机地整合在一起,并稳定运行。
- 目标导向:引擎的最终目标是支撑一款复杂的商业级游戏的开发。如果一个引擎只能用来做简单的 Demo,那么它的体系就是不完备的。
3. 总结与展望:从“如何移动”到“为何移动”
最后,讲座对已讲解的 Navigation 内容进行了阶段性总结,并清晰地指出了AI领域的下一层级问题。
-
核心观点:到目前为止,长达近一个小时的 Navigation 内容,仅仅解决了 AI 角色“如何移动 (How to move)”的问题。这本身已经是一个足够复杂的技术领域。
-
知识分层:
- Navigation (寻路): 属于 运动规划 (Motion Planning) 的范畴。它为 AI 提供了在复杂世界中从 A 点移动到 B 点的能力和路径。这是 AI 的底层行动基础。
- Decision Making (决策): 这是更高层次的 AI 问题,它决定了 AI “为何要移动 (Why to move)”。例如,AI 的目标是什么?为什么要从 A 点去 B 点(是为了攻击、躲避还是执行任务)?这通常由行为树、状态机等技术来管理。
-
展望:讲座明确指出,解决了底层的“移动”问题后,下一步才会进入更上层的“决策”与“思考”领域,这预示了后续内容的走向。
Steering 行为详解
一、 核心概念:从寻路 (Pathfinding) 到转向 (Steering)
讲座首先区分了两个在 AI 移动中至关重要的概念:寻路和转向。
-
寻路 (Pathfinding): 负责在宏观层面找到一条从 A 点到 B 点的理想路径(通常是最优路径),例如使用 A* 算法在导航网格 (NavMesh) 上找到的路径点序列。它关注的是“去哪里”。
-
转向 (Steering): 负责在微观层面,指导一个单位(Agent)如何 真实地、物理地 沿着寻路给出的路径移动。它关注的是“怎么走”。
为什么需要 Steering?
寻路给出的路径是理想化的,但游戏世界中的角色,特别是载具,受到 物理约束 (Physical Constraints) 的限制,无法完美地执行这条路径。Steering 的核心就是为了模拟这些约束,让移动看起来更真实、更可信。
关键的物理约束包括:
- 加速度/减速度: 物体不能瞬间达到最大速度或瞬间停止,需要一个加速和减速的过程。
- 转弯半径 (Turning Radius): 物体不能瞬间改变方向,尤其像汽车这样的载具,转向需要一个平滑的弧线过程。
一个常见的游戏 Bug: 很多游戏中 NPC 会卡在墙角不停抖动,这往往是 Steering 系统导致的。寻路系统告诉 NPC 可以通过,但由于其物理约束(如转弯半径过大),Steering 系统在实际执行时无法转过那个狭窄的弯角,导致其在一个小范围内反复尝试失败,最终卡死。
二、 Steering 的三大基本行为
讲座将复杂的 Steering 行为归纳为三种最基本的构建模块。几乎所有复杂的移动AI都是由这三种行为或其变体组合而成。
1. 追逐与逃跑 (Seek and Flee)
这是最直观、最基础的 Steering 行为。
- 核心观点: 根据自身位置和目标位置,持续计算一个加速度向量,从而驱动自己朝向目标(Seek)或远离目标(Flee)。
- 实现方式: 在引擎的每一帧(Tick)中,重新评估自己与目标的位置关系,并更新自己的加速度。
- 行为变种:
- 追踪 (Pursuit): 预测移动目标未来的位置并朝该位置移动。
- 巡逻 (Patrol): 将一个动态生成的、在自己前方的随机点作为追逐目标。
- 跟随方向场 (Flow Field Following): 这是一个更高级的应用。在空间中预先定义一个 方向场 (Vector Field),每个点都有一个期望的运动方向。将单位放入场中,它就会自然地跟随场的方向流动。这种技术常用于大规模群体(如鱼群、人群)的模拟,效率很高。
2. 速度匹配 (Velocity Matching)
这个行为关注的不仅是“到达”目标点,更是“如何到达”。
- 核心观点: 在接近目标的过程中,平滑地将自身速度调整到与目标速度一致。一个常见的场景是,平稳地在目标点减速至零。
- 关键挑战: 如何精确控制加速和减速,使得单位在速度降为零的瞬间,恰好停在目标位置上。
- 类比: 讲座中用“火星探测器”做了个精彩的比喻。探测器飞到火星附近最难的一步,不是加速飞过去,而是要消耗宝贵的燃料进行精确减速,使自己的速度与火星的公转/自转速度相匹配,从而成功入轨而不是直接撞上或飞过。
- 算法思路:
- 简单情况: 如果目标是静止或匀速直线运动,可以通过简单的物理公式(如 )反推出需要的加速度/减速度。
- 复杂情况 (目标非线性运动): 解决复杂的积分方程在游戏中不现实。更实用、也更符合生物直觉的是采用 分步式/迭代式算法 (Iterative Approach):
- 在当前帧,假设目标在接下来极短的时间内会保持匀速直线运动。
- 基于这个假设,计算出自己当前帧所需的加速度。
- 在下一帧,根据目标的实际新位置和新速度,重复步骤1,重新计算并调整加速度。
- 效果: 这种迭代调整的方式虽然不是全局最优解,但其产生的行为非常自然,因为它模拟了真实生物(包括人类)在追逐目标时不断观察、预测和调整自身行为的过程。
3. 朝向对齐 (Alignment)
这个行为处理的是单位的旋转,而非位移。
- 核心观点: 将自身的朝向(Forward Vector)与目标朝向或期望的移动方向对齐。
- 应用场景:
- 鱼群/鸟群: 群体中的个体会自发地与领头者或周围同伴保持方向一致。
- 飞机编队: 编队中的飞机会调整姿态,与长机保持同向飞行。
- 实现的关键细节 (Pro-Tip!):
- 与线速度一样,角速度的变化也必须是平滑的。要引入 角加速度 (Angular Acceleration) 和 角减速度 (Angular Deceleration) 的概念。
- 错误的做法: 直接将单位的角度(Rotation)设置为目标角度。
- 正确的做法: 施加一个“力矩”,让单位产生角加速度,使其朝向目标角度转动;在接近目标角度时,施加反向的“力矩”进行角减速,最终平滑地停在目标角度。
为什么这个细节至关重要? 如果没有平滑的角速度变化,AI 的转向会显得极其机械和不自然,就像一个机器人一样瞬间“咔咔咔”地转身。这种突兀的、不连贯的旋转会极大地破坏玩家的沉浸感,是早期或廉价游戏中常见的 AI 问题。实现平滑的转向是提升 AI 行为质感的重要一步。
转向系统与群体模拟
在上一部分的基础上,我们继续深入探讨如何让 AI 的行为更加真实可信。这一部分的核心是两个紧密相连的概念: 转向系统 (Steering System) 和 群体模拟 (Crowd Simulation)。它们是解决角色移动“机械感”和实现大规模动态场景的关键技术。
一、 转向系统 (Steering System):告别机器人式的移动
很多时候,即使我们实现了完善的寻路(Pathfinding)和决策(Decision Making)系统,AI 角色的移动依然显得非常僵硬。其根本原因在于缺少一个模拟真实物理动态的中间层——转向系统。
-
核心问题: 如果没有平滑处理,AI 会进行瞬时转向。想象一个角色在移动中需要改变方向,它会“啪”地一下瞬间转过来,像一个机器人,完全不符合生物或载具的运动直觉。
-
核心观点: 转向系统通过模拟物理约束,为 AI 行为注入“灵魂”。它将上层决策系统(“我要去那里”)翻译成底层的、符合物理规律的力(“我该如何加速/转向以到达那里”)。
-
关键实现:
- 角加速度/角减速 (Angular Acceleration/Deceleration): 这是转向系统的精髓。任何转向都不是瞬间完成的,而是有一个加速和减速的过程。这使得角色的朝向变化平滑、自然。
- 物理属性约束: 每个单位都应有其物理限制,例如:
- 最大加速度 (Max Acceleration)
- 最大转向速度 (Max Turning Speed)
- 惯性 (Inertia)
- 重要性: 在引擎架构中,转向系统是一个容易被忽略但至关重要的模块。缺少它,即使上层 AI 逻辑再复杂,最终表现出的行为也会大打折扣,尤其是在模拟载具、奔跑的生物或人类时。
二、 群体模拟 (Crowd Simulation):从个体到生态
转向系统的一个“大客户”和最直观的应用场景,就是群体模拟。当成百上千个单位需要同时在场景中移动时,如何高效、真实地模拟它们的行为,就成了一个核心挑战。
比如,主角走过广场,受惊飞起的鸽群;城市街道上,熙熙攘攘、各自行动的人群。这些都不是简单地为每个个体单独执行寻路和决策,而是通过一个统一的群体系统来管理的。
群体模拟的先驱 Craig Reynolds 提出了著名的 Boids 算法,并奠定了该领域的基础。通常,群体模拟的实现思路可以分为三类:
1. 微观方法 (Microscopic / Bottom-up)
这种方法不关心宏观的路径,而是为每个个体定义一套简单的、局部的交互规则。群体的宏观行为会从这些简单的局部规则中 自发涌现 (Emergent Behavior)。
-
核心观点: 通过定义个体间的简单交互规则,来形成复杂的宏观群体现象。
-
经典三原则 (Boids 算法):
-
分离 (Separation): 避免与邻近的个体挤在一起(斥力)。
-
对齐 (Alignment): 调整自己的方向,与邻近的个体方向保持一致。
-
内聚 (Cohesion): 朝着邻近个体群的中心位置移动,避免掉队(引力)。
类比: 这套规则非常像一个弹簧系统。靠得太近,产生斥力推开;离得太远,产生引力拉回。
-
-
优缺点:
- 优点: 规则简单,能生成非常自然、随机、生动的群体效果(如鱼群、鸟群)。
- 缺点: 结果难以精确控制,不适合需要遵循严格路线的场景(如城市交通)。
2. 宏观方法 (Macroscopic / Top-down)
与微观方法相反,宏观方法首先定义一个全局的、结构化的引导系统,然后让群体中的个体遵循这个系统的规则移动。
-
核心观点: 通过预设的全局路径或区域规则,来约束和引导群体行为,使其高度可控。
-
关键技术:
- 通道/路径网络 (Lanes): 在场景中预先定义好可通行的路径,例如人行道、车道。AI 个体会被约束在这些 Lane 上移动。
- 区域图 (Zone Graph): 将大世界划分为不同的区域,并定义区域间的连接关系(如十字路口),AI 在区域间进行选择和切换。
- 规则体系: 需要额外的规则来处理特殊情况,例如:
- 如何避免个体在相邻的逆向 Lane 之间频繁切换。
- 在交叉路口如何选择新的 Lane。
-
应用场景:
- 现代游戏中模拟城市行人、交通流等。因为人类在城市中的行为模式(如靠右行走)本身就遵循着一套宏观规则。
- Unreal Engine 5 的 MassAI 就是一个典型的宏观群体模拟系统。
3. 混合方法 (Hybrid)
结合微观和宏观方法的优点,既有宏观的目标引导,又保留个体的自主行为。
-
核心观点: 宏观指令设定团队目标,微观规则处理个体间的局部交互。
-
典型案例: 即时战略 (RTS) 游戏 (如《星际争霸》、《帝国时代》)。
- 宏观层面: 玩家框选一队士兵,下达一个攻击或移动指令(“到地图的那个点去”)。这是一个全局的目标和路径。
- 微观层面: 在向目标点移动的过程中,每个士兵会根据周围的战友和地形,自主进行避让、保持阵型等局部行为。这些行为遵循的是类似 Boids 的微观规则。
三、 核心挑战:高效的碰撞避免 (Collision Avoidance)
无论是哪种模拟方法,一个共同的难题是:如何让成千上万个单位在移动时避免互相重叠或撞上障碍物,并且计算开销要足够低?
方法一:基于力的方法 (Force-Based)
- 原理: 最直观的方法。当两个物体靠得太近时,给它们施加一个斥力,将它们推开。
- 优点: 简单、直观。
- 缺点: 在处理大量静态障碍物时,效果和性能不佳。
方法二:基于距离场的方法 (Distance Field-Based)
这是一种更先进、更高效的解决方案,尤其适用于处理大量单位与静态环境的避障。
-
核心问题: 为成千上万个单位分别执行 A* 这样的寻路算法来绕过障碍物,计算成本是无法接受的。
-
解决方案:
- 预计算: 对整个场景的静态障碍物,预先计算出一个 距离场 (Distance Field)。场中的每个点存储了该点到最近障碍物的距离。
- 实时查询: 在运行时,每个 AI 单位只需查询自己所在位置在距离场中的值和 梯度(方向)。
- 施加斥力: 离障碍物越近,距离场的值越小,AI 受到的斥力就越大。这个斥力的方向由场的梯度决定,天然指向远离障碍物的方向。
-
优势: 将复杂的、每个单位都要重复进行的寻路计算,转化为一次性的预计算和极低开销的实时查询。这使得大规模群体在复杂环境中进行平滑、自然的避障成为可能。
应用拓展:数字孪生 (Digital Twin)
这些群体模拟与避障技术不仅用于游戏,在数字孪生等领域也有重要应用。例如,在规划一座新城市或大型场馆时,可以利用这套系统模拟大量人流的疏散情况,从而评估和优化设计方案。
从群体运动到环境感知
在深入探讨 AI 的决策核心之前,我们必须先为其建立两个关键的基础设施:一是如何让 AI 个体在世界中真实、合理地移动并避免碰撞;二是如何让 AI 感知并理解它所处的环境。本部分将深入探讨这两个主题。
一、 群体行为与碰撞避免 (Crowd Simulation & Collision Avoidance)
当场景中存在大量 NPC 时,如何让他们在移动时既能表现出群体行为的真实感,又能避免互相穿插,是一个核心问题。这不仅仅是游戏的需求,在 数字孪生 (Digital Twin) 等领域(如模拟火灾时的人群疏散)也有着至关重要的应用。这个问题通常由一个独立的 碰撞避免 (Collision Avoidance) 系统来高效解决,而非依赖高成本的 AI 决策。
1. 基于力的方法 (Force-Based Method)
这是一种非常经典且实用的方法,核心思想简单粗暴但有效。
- 核心观点: 将每个个体视为一个带电粒子,它们之间存在一种斥力。当两个个体距离过近时,会产生一个强大的、与距离成反比的排斥力,将它们推开。
- 关键算法:
- 斥力的大小与个体间的距离
d强相关,通常可以简化为: - 这意味着距离
d越小,斥力F越大,从而自然地避免碰撞。
- 斥力的大小与个体间的距离
- 优点:
- 实现简单: 逻辑清晰,代码量少,不易出错。
- 调试方便: 参数直观(如斥力系数),效果容易调整。
- 性能较好: 计算开销相对较低。
- 缺点:
- 效果不够优雅: 表现上可能出现抖动或不自然的突然转向,无法在理论上找到“最优”的避让路径。
2. 基于速度的方法 (Velocity-Based Method)
这是一类更高级、理论上更优的算法,它从速度空间的角度来解决避让问题。
-
核心观点: 每个移动的物体都会在我的 速度域 (Velocity Space) 上形成一个障碍区域。我的任务是调整自身的速度向量,使其不落入这个障碍区域内,从而避免未来的碰撞。
-
关键术语与演进:
-
速度障碍 (Velocity Obstacle, VO):
- 这是该方法的基础。它计算出如果我保持当前速度,在未来一段时间内会与另一个物体 B 发生碰撞的所有速度集合,这个集合就是 B 在我的速度空间中产生的“速度障碍”。
- 避让是单向的:只有我(A)会主动调整速度来避开 B 产生的障碍。
-
相互速度障碍 (Reciprocal Velocity Obstacles, RVO):
- 对 VO 的改进,引入了“公平性”原则。
- 核心思想: 碰撞是双方的责任。我和你(A 和 B)都应该为避免碰撞做出努力。通常的做法是,双方各承担一半的避让责任。
- 例如,A 发现即将与 B 碰撞,决定向左调整速度;同时 B 也做出同样的判断,向自己的左侧调整,从而双方优雅地错身而过。
-
最优相互碰撞避免 (Optimal Reciprocal Collision Avoidance, ORCA):
- 对 RVO 在多智能体场景下的优化。
- 面临的问题: 在一个拥挤的环境中(例如蜂群),我为了避开 B 而做的速度调整,可能会导致我撞上 C。
- 核心思想: 将问题转化为一个全局优化问题。它在速度空间中为每个个体计算出一个允许的安全速度区域(一个凸多边形),然后寻找一个对所有人都最优(或最公平)的速度调整方案,使得每个人的新速度都落在各自的安全区内。
- 实现: 数学上非常复杂,涉及到闵可夫斯基和 (Minkowski Sum) 等高等数学概念。
-
3. 实践中的权衡与选择
- 理论 vs. 现实: 尽管 RVO/ORCA 算法在理论上能得到非常平滑、优雅的避让效果(如视频中演示的圆形智能体阵列对穿),但在真实的商业引擎开发中,它们存在明显缺点:
- 实现复杂: 算法涉及复杂的几何和数学计算,非常容易出错。
- 计算量大: 尤其是在大量智能体共存的情况下,性能开销显著高于基于力的方法。
- 引擎开发者的推荐: 对于大多数游戏场景,优先考虑使用基于力的方法。它的简单、稳定和易于调试的特性,使其在工程实践中更具优势。商业引擎通常会同时提供这两种方案,让开发者根据具体需求进行选择。
二、 AI 决策的基础:环境感知 (The Foundation of AI Decisions: Perception)
所有复杂的 AI 决策都不是凭空产生的,它们必须基于对世界的有效 感知 (Perception / Sensing)。AI 像人一样,需要通过“看”、“听”和“感受”来收集信息,才能做出合理的反应。
1. 内部状态感知 (Internal State Awareness)
这是最直接、获取成本最低的信息。
- 核心观点: AI 需要了解自身的实时状态。
- 关键信息:
- 生命值 (Health): 我是否受伤?是否需要寻找治疗?
- 弹药量 (Ammo): 我的武器是否还有子弹?是否需要换弹或寻找补给?
- 当前速度、姿态等物理状态。
- 技术关联: 游戏引擎中的 Gameplay 框架(如 Unreal 的蓝图、Unity 的 Visual Scripting)必须能够方便地让 AI 访问和读取这些自身状态变量,这是构成决策逻辑的基本前提。
2. 外部环境感知 (External Environment Awareness)
这是 AI 感知中更复杂、也更重要的部分,尤其是对空间信息的理解。
-
核心观点: AI 需要理解它所处的关卡结构、战术价值和可交互元素。这些信息共同构成了 AI 的“世界观”。
-
关键的静态空间信息 (Static Spatial Information):
-
可通行区域 (Walkable Areas):
- 这是最基础的空间信息,通常由
Navigation Mesh提供。它告诉 AI 哪里可以走,哪里不能走。
- 这是最基础的空间信息,通常由
-
战术地图 (Tactical Map / Txt Map):
- 核心价值: 在可通行区域之上,增加一层由设计师手动标注的、具有战术意义的数据层。
- 解决的问题: 仅仅知道“能走”是不够的。例如,一条河上的桥,虽然两岸都是开阔地,但桥头是兵家必争的咽喉要道。AI 需要知道这一点。
- 应用: AI 在做决策时,会优先考虑抢占或利用这些被标记为高价值的战术点。
-
智能物件 (Smart Objects):
- 定义: 场景中被特殊标记的、AI 知道如何与之交互的物体。
- 示例:
- 一个梯子:AI 知道可以爬上去。
- 一堵可被破坏的墙:AI 知道可以用特定武器将其打破,开辟新的路径。
-
掩体点 (Cover Points):
- 定义: 场景中被标记为可以提供掩护的位置。
- 应用: 这是现代射击游戏中 AI 行为真实性的关键。AI 不再是到处乱跑的“无头苍蝇”,而是会像真人玩家一样寻找掩体进行躲避和射击。
- 经典案例: 《战争机器》(Gears of War) 系列的 AI,其出色的掩体使用行为在当时给玩家留下了深刻印象。
-
AI 的感知与决策基础
在构建聪明的 AI 之前,我们必须先为它打下坚实的地基。这一部分内容将深入探讨 AI 赖以生存的两大基础:它如何理解自己所处的空间环境,以及它如何像生物一样感知世界。这些是所有高级决策算法能够有效运行的前提。
一、AI 感知世界的基础:空间信息
AI 的所有行为都发生在游戏世界这个空间中。因此,对空间信息的有效组织和利用,是 AI 系统设计的起点。这些信息可以分为静态和动态两类。
1. 静态空间信息:预设的战场元素
这类信息通常由关卡设计师在地图中预先“标注”好,为 AI 提供固定的战术参考。
- 核心观点:AI 的智能感很大程度上来源于其对预设战术点的有效利用。
- 关键术语: 掩护点 (Cover Points)
- 实例讲解:讲座中提到了一个经典案例——《战争机器》(Gears of War)。与早期 FPS 游戏中只会跳来跳去的 AI 不同,《战争机器》的 AI 会主动寻找掩体进行躲避和反击。这种行为之所以能够实现,就是因为设计师在地图中标注了大量的掩护点信息,AI 系统可以查询并利用这些点来执行战术动作,从而给玩家带来了“AI 很智能”的印象。
2. 动态空间信息:实时变化的战场态势
战场是瞬息万变的,AI 必须能够感知并适应这些动态变化。
-
核心观点:动态空间信息是 AI 摆脱固定脚本、做出适应性行为的关键。引擎需要提供高效的机制来管理和查询这些信息。
-
关键术语与技术:
- 影响力图 (Influence Map):
- 也常被称为 热力图 (Heatmap),是 AI 感知战场态势的核心数据结构。
- 它是一个多维度的网格数据,用于量化地图上各个区域的特定“属性”。最常见的维度是威胁度。
- 应用场景:当一个区域敌人增多时,该区域在“威胁度”维度上的数值就会升高。一个残血的 AI 在规划路径时,会查询这张图,并倾向于避开高威胁区域,而不是盲目地走直线。AI 可以根据威胁度的高低,决定是绕行、强攻还是消灭威胁源。
- 动态寻路 (Dynamic Pathfinding):
- 当游戏中的物体(如障碍物、其他角色)移动或发生关键事件(如桥梁被摧毁)时,传统的静态寻路网格(NavMesh)可能失效。
- AI 需要能够根据世界的实时变化,动态地调整其移动路径。
- 动态视野 (Dynamic Line of Sight):
- 视野信息同样是动态的,会受到移动物体、烟雾等因素的影响。
- 影响力图 (Influence Map):
-
对游戏引擎的要求:
- 引擎本身不一定需要实现所有复杂的动态信息系统(如 Influence Map 的具体逻辑,这通常与具体游戏玩法强相关)。
- 但是,引擎 必须提供一个通用、可扩展的接口,让游戏逻辑(Gameplay)可以方便地更新这些数据(如 Influence Map),并让 AI 的决策系统(如行为树)能够高效地查询这些数据。这是衡量引擎 AI 模块设计好坏的重要标准。
二、AI 的“五感”:世界感知系统 (World Sensing)
如果说空间信息是 AI 的“地图”,那么世界感知系统就是 AI 的“眼睛”和“耳朵”。它决定了 AI 能“看到”和“听到”什么,是 AI 获取动态物体信息的主要来源。
1. 核心理念:避免“全知全能”的作弊 AI
- 核心观点:一个好的 AI 应该像人一样,其感知能力是受限的,而不是拥有“上帝视角”。这能为玩家提供更公平、更有趣的战术对抗体验。
- 反面教材:早期一些粗糙的 CS Bot,它们无视战争迷雾和障碍物,永远知道玩家藏在哪里。这种“开图”的 AI 会让玩家的任何战术(如躲藏、偷袭)都变得毫无意义,游戏体验极差。
2. 模拟人类的感知
为了让 AI 行为更可信,我们需要模拟生物的感知方式。
- 视觉 (Vision):
- AI 应该有明确的朝向和 视野范围 (Field of View)。
- 当 AI 背对玩家时,玩家可以尝试从背后偷袭,这为游戏玩法提供了战术深度。
- 听觉 (Hearing):
- 通常是 360 度的感知范围。
- 声音的传播会受到 障碍物遮挡 (Occlusion) 的影响。例如,躲在厚墙后面发出的声音,和在开阔地发出的声音,被 AI “听到”的效果是不同的。
- 嗅觉 (Smell) 与其他感知:
- 虽然目前游戏中较少使用,但理论上可以实现更独特的 AI 行为。
- 例如,设计一只猎犬 AI,它不仅听觉灵敏,还能根据玩家留下的“气味”轨迹进行追踪。
3. 工程挑战与优化
感知系统的计算开销非常大,是 AI 模块主要的性能瓶颈之一。
- 核心挑战:如果场景中每个 AI 在每一帧都对周围所有物体进行复杂的感知检测(如射线检测、范围查询),会消耗巨大的 CPU 资源。
- 引擎层面的优化策略:
- 开放控制接口:引擎应允许开发者根据不同情况,灵活调整 AI 感知的 频率、精度和范围,从而在效果和性能之间取得平衡。
- 共享感知数据 (Shared Perception Data):对于位置相近的一组 AI(例如一队小兵),可以由一个“领队”进行感知计算,然后将结果共享给其他成员,避免大量重复计算。
三、AI 的“大脑”:决策算法概览 (Decision Making)
当 AI 通过感知系统收集了足够的信息后,就进入了最核心的环节——决策。讲座在此部分对主流的决策算法进行了梳理和分类,为后续深入讲解做铺垫。
- 核心观点:前面讨论的所有信息和感知系统,都是为决策算法服务的 “数据基础” (Foundation)。没有这些基础,再高级的算法也无的放矢。
1. 经典决策算法列举
- 有限状态机 (Finite State Machine, FSM):最基础、最经典的决策模型。
- 行为树 (Behavior Tree, BT):现代游戏中应用最广泛的核心决策模型之一。
- 分层任务网络 (Hierarchical Task Network, HTN):一种更侧重于复杂任务规划的算法。
- 目标导向行为规划 (Goal-Oriented Action Planning, GOAP):AI 拥有明确的目标,并动态规划一系列行为去达成该目标。
- 蒙特卡洛搜索树 (Monte Carlo Tree Search, MCTS):在拥有巨大决策空间的游戏(如棋类)中表现出色。
- 深度学习 (Deep Learning):利用神经网络进行决策,是当前的研究热点。
2. 两大决策思路的初步划分
讲座者提出了一个个人观点,将上述算法大致分为两类:
- 前向规划 (Forward Planning):
- 特点:更偏向于响应式和一步一步的决策过程。AI 根据当前状态选择一个行为,执行后再根据新状态选择下一个行为。
- 生动比喻: “脚踩西瓜皮,走到哪儿是哪儿”。
- 代表算法: 有限状态机 (FSM) 和 行为树 (BT)。
- 目标导向规划 (Goal-Oriented / Backward Planning):
- 特点:更侧重于从最终目标出发,反向推导或规划出达成目标所需的步骤序列。
- 代表算法: HTN 、 GOAP 等。
这一部分的介绍为我们接下来的学习指明了方向,下一部分将重点深入探讨现代游戏中最核心的决策算法之一:行为树。
游戏 AI 决策算法:从状态机到行为树
本节笔记将聚焦于讲座中提到的两种经典的“前向规划” (Forward Planning) AI 决策算法:有限状态机 (Finite State Machine) 和行为树 (Behavior Tree)。这两种算法的共同特点是,AI 的决策过程更像是“脚踩西瓜皮,走到哪算哪”,根据当前情况一步步做出反应,而不是预先规划好一个完整的、以终为始的宏大计划。
一、 AI 决策算法的两种思路
在深入具体算法之前,讲座首先将 AI 决策算法划分为两大类,这有助于我们建立一个宏观的认知框架。
-
前向规划 (Forward Planning):
- 核心思想: AI 根据当前状态和外部条件,决定下一步该做什么。这是一种响应式的、自下而上的决策模式。
- 代表算法:
- 有限状态机 (Finite State Machine, FSM)
- 行为树 (Behavior Tree, BT)
- 特点: 直观,易于实现,非常适合处理即时反应。
-
目标驱动的反向算法 (Goal-Driven Backward Algorithm):
- 核心思想: AI 首先确定一个最终目标,然后反向推导出为了达成这个目标所需要执行的一系列动作序列。这是一种规划式的、自顶向下的决策模式。
- 代表算法: HTN, GOAP, 蒙特卡洛树搜索 (MCTS), 深度强化学习 (Deep RL) 等。(这些将在后续课程中讲解)
- 特点: 更具“智能感”,能完成更复杂的长期任务。
二、 经典方法:有限状态机 (Finite State Machine, FSM)
FSM 是最经典、最符合直觉的 AI 模型之一。
核心思想
AI 的所有行为都可以被抽象为一系列离散的 状态 (State)。AI 在任意时刻都处于且仅处于一个状态,并通过 转移 (Transition) 在不同状态间切换。
- 状态 (State): 代表 AI 正在做某件事或处于某种情况,例如“巡逻”、“攻击”、“逃跑”。
- 转移 (Transition): 连接两个状态的“边”。
- 条件 (Condition): 触发转移的前提。当某个条件满足时,AI 就会从当前状态切换到下一个状态。
案例:吃豆人 (Pac-Man) AI
一个简单的吃豆人 AI 可以用 FSM 完美地描述:
-
三个核心状态:
闲逛 (Wander)逃离幽灵 (Flee Ghost)追逐幽灵 (Chase Ghost)
-
主要的转移和条件:
Wander→Flee Ghost: 条件 = 发现附近有幽灵,且自己不是无敌状态。Wander→Chase Ghost: 条件 = 发现附近有幽灵,且自己是无敌状态。Wander→Seek Power-up: 条件 = 发现能量豆。Flee Ghost→Wander: 条件 = 幽灵消失。Chase Ghost→Wander: 条件 = 无敌时间结束。
这个简单的 FSM 模型已经能构建出一个看起来有基本智能的吃豆人 AI。
FSM 的核心问题:状态爆炸与可维护性
尽管 FSM 直观易懂,但在开发商业级游戏时,其缺点会变得非常致命。
- 状态爆炸 (State Explosion): 随着游戏逻辑复杂度的提升,角色的状态数量会急剧增加(从几个到几十甚至上百个)。
- 可维护性灾难: 每增加一个新状态,开发者都必须考虑它与所有其他现有状态之间的潜在关系和转移条件。这导致状态之间的连接线像一团乱麻,极难管理和调试,被称为 “意大利面条式代码”。
- 高反应性 (Reactivity) 的代价: 为了让 AI 反应迅速,需要在许多状态之间建立直接的转移路径,这进一步加剧了连线的复杂性。
解决方案:分层有限状态机 (Hierarchical FSM, HFSM)
为了解决 FSM 的可维护性问题,业界提出了 HFSM。
- 核心思想: 将功能相近或逻辑相关的状态组织成一个 子状态机 (Sub-FSM)。整个 AI 的 FSM 由多个子状态机嵌套或连接而成。
- 优点:
- 提升了可读性和模块化: 将复杂的逻辑封装在局部,使得顶层逻辑更清晰。
- 缺点:
- 降低了反应速度: 如果 AI 需要从一个子状态机深处的某个状态,快速跳转到另一个子状态机深处的特定状态,它可能需要逐层退出,再逐层进入,过程相对缓慢。强行“跳飞线”又会破坏其结构化的优势。
三、 现代主流:行为树 (Behavior Tree, BT)
行为树是现代游戏 AI 中非常流行的一种技术,它通过一种不同的思维模式解决了 FSM 的许多痛点。
历史背景与思想起源
- 业界标杆: 行为树的流行很大程度上归功于 《光环2》(Halo 2) 的成功应用。
- 思想祖先: 行为树的根源可以追溯到商业和军事领域的 决策树 (Decision Tree)。决策树是一种将复杂决策流程形式化、结构化的工具,用于制定标准作业程序 (SOP)。
核心思想:从“状态跳转”到“思维模式”
行为树与状态机的根本区别在于抽象的层面不同。
- FSM 抽象的是“逻辑”: 它关注的是
IF condition THEN state = newState这样的逻辑关系。 - BT 抽象的是“人类的思维模式”: 它模拟了一个决策流程,更像是在描述“我该做什么?”的思考过程。它将复杂的决策分解成一个树状的分支结构。
一个简单的思维过程示例:
“我现在没事干,先看看周围有没有敌人。如果有敌人,我再判断一下自己是不是无敌状态。如果是无敌的,我就去追击;如果不是,我就逃跑。如果一开始就没敌人,我就继续巡逻。”
你会发现,这段描述天然就是一种树状结构,而这正是行为树的设计哲学。
行为树的基本构成:节点 (Node)
行为树由不同类型的节点构成,讲座中首先介绍了最底层的叶子节点。
- 执行节点 (Execution Nodes): 位于行为树的叶子节点,是实际执行具体逻辑的地方。主要分为两类:
- 条件节点 (Condition Node):
- 功能: 用于进行判断,例如“敌人是否在视野内?”、“我的血量是否低于30%?”。
- 特点: 瞬时完成 (Instantaneous),立即返回成功或失败。
- 动作节点 (Action Node):
- 功能: 执行一个具体的动作,例如“移动到目标点”、“播放攻击动画”、“逃跑”。
- 特点: 通常需要一段时间来执行 (Takes time to execute)。在动作完成前,它会处于“运行中 (Running)”的状态。
- 条件节点 (Condition Node):
游戏 AI 行为树 (Behavior Tree) 核心概念
在现代游戏 AI 设计中,行为树 (Behavior Tree, BT) 是一种极其强大和流行的工具。它通过一种直观的、模块化的树状结构来描述和控制 AI 的行为逻辑,相比于复杂的状态机(State Machine),它在构建复杂行为时更具可读性和扩展性。
一、 行为树的基本构成:节点 (Nodes)
行为树由不同类型的节点构成,这些节点是行为逻辑的基本单元。我们可以将其分为两大类: 可执行节点 (Execution Nodes) 和 控制流节点 (Control Flow Nodes)。
1. 可执行节点 (Execution Nodes)
这是行为树的“叶子”,代表了 AI 可以执行的具体操作或判断。
条件节点 (Condition Node)
- 核心观点: 用于进行瞬间的逻辑判断,类似于代码中的
if语句。它会立即返回一个结果,通常是成功或失败。 - 关键作用: 作为决策的依据,控制行为树的执行路径。
- 示例:
IsGhostClose?(判断幽灵是否在附近)IsHealthLow?(判断自身血量是否过低)IsDoorLocked?(判断门是否上锁)
动作节点 (Action Node)
- 核心观点: 用于执行一个可能需要持续一段时间的动作。这是行为树与简单逻辑判断最核心的区别,它引入了时间维度。
- 关键特性: 动作节点并非瞬间完成,因此它拥有三种返回状态,这对于管理耗时任务至关重要。
- 成功 (Success): 动作已顺利完成。例如,寻路到达了目的地。
- 失败 (Failure): 动作执行失败,无法完成。例如,寻路时发现目标点不可达。
- 运行中 (Running): 动作正在执行,尚未完成。这是行为树处理耗时任务(如移动、攻击动画、技能吟唱)的关键状态。当一个动作节点返回
Running时,行为树会在后续的更新(tick)中继续执行这个节点,直到它返回Success或Failure。
- 示例:
ChaseGhost(追逐幽灵,需要调用导航和移动系统)Patrol(在指定路线上巡逻)OpenDoor(播放开门动画)
二、 行为树的核心:控制流节点 (Control Flow Nodes)
这是行为树的“树枝”,它们不执行具体动作,而是根据其子节点的返回状态来组织和决定执行流程。最核心的控制流节点有以下几种。
1. 顺序节点 (Sequencer)
- 核心观点: 用于执行一系列必须按顺序完成的任务,如同一个预设的计划 (Planning)。它的图标通常是一个带箭头的方块
→。 - 运行逻辑:
- 从左到右(或按预定顺序)依次执行其所有子节点。
- 如果一个子节点返回
Failure,则该 Sequencer 节点** 立即停止 **并向其父节点返回Failure。后续的子节点将不会被执行。 - 如果一个子节点返回
Running,则该 Sequencer 节点** 立即停止 **并向其父节点返回Running。在下一次更新时,将从这个返回Running的子节点继续执行。 - 只有当所有子节点都依次执行并返回
Success时,该 Sequencer 节点才会向其父节点返回Success。
- 应用示例: AI 开门并进入房间的完整流程。
Sequencer: Enter RoomCondition: IsDoorLocked?(判断门是否锁着?如果锁着则失败,整个序列失败)Action: WalkToDoor(走到门前,这个过程会返回Running)Action: UnlockDoor(执行解锁动作)Action: OpenDoor(执行开门动作)Action: WalkThroughDoor(穿过门)
- 与状态机的对比: 使用 Sequencer 可以非常自然地表达一个线性的任务流。如果用状态机实现,每个步骤的失败都需要一条单独的转换线指回失败处理状态,这将使状态图变得非常混乱。
2. 选择节点 (Selector)
- 核心观点: 用于实现带优先级的决策,它会从一系列选项中尝试执行,直到找到一个可行的为止。可以理解为
if-else if-else逻辑链。它的图标通常是一个带问号的方块?。 - 运行逻辑:
- 从左到右(或按预定优先级)依次执行其子节点。
- 如果一个子节点返回
Success,则该 Selector 节点立即停止并向其父节点返回Success。后续的子节点将不会被执行。 - 如果一个子节点返回
Running,则该 Selector 节点立即停止并向其父节点返回Running。在下一次更新时,将从这个返回Running的子节点继续执行。 - 只有当所有子节点都依次执行并返回
Failure时,该 Selector 节点才会向其父节点返回Failure。
- 应用示例: 一个士兵的战斗AI逻辑。
Selector: Soldier Combat Logic- (最高优先级)
Sequencer: Attack EnemyCondition: IsEnemyInAttackRange?(敌人在攻击范围内吗?)Action: Attack(执行攻击)
- (第二优先级)
Sequencer: Chase EnemyCondition: IsEnemyVisible?(能看见敌人吗?)Action: Chase(追击敌人)
- (最低优先级/默认行为)
Action: Patrol(在区域内巡逻)
- (最高优先级)
3. 并行节点 (Parallel)
- 核心观点: 允许同时执行其所有的子节点。这使得 AI 可以同时处理多个任务,例如一边移动一边向目标射击,这在状态机中很难实现(因为状态机在同一时间只能处于一个状态)。
4. 装饰器节点 (Decorator)
- 核心观点: 它只有一个子节点,作用是包装并改变其子节点的行为或返回结果。例如,可以用来循环执行一个动作N次、反转一个节点的成功/失败结果等。
行为树 (Behavior Tree) 的高级概念与引擎实现
在了解了行为树的基础节点后,本部分将深入探讨其相对于传统状态机的优势、更高级的节点类型、核心的引擎执行机制(Ticking),以及一系列旨在提升其表达能力和简洁性的设计模式。
一、 核心优势:直观性与可读性
讲座通过一个经典的 AI 案例,生动地对比了行为树(BT)与有限状态机(FSM)在设计复杂行为时的差异。
-
核心观点: 行为树通过其层级结构,提供了一种远比状态机更符合人类思维逻辑、更易于阅读和维护的 AI 行为设计范式。
-
经典案例 (敌人 AI 逻辑):
- 最高优先级: 玩家是否在攻击范围内? → 是 → 攻击 (Attack)
- 次高优先级: 是否能看见玩家? → 是 → 追逐 (Chase)
- 默认行为: 以上皆否 → 巡逻 (Patrol)
-
对比分析:
- 行为树实现: 使用一个 选择器 (Selector) 节点,按优先级从左到右排列上述三个行为分支,结构清晰,一目了然。
- 状态机实现: 需要定义
Attack,Chase,Patrol三个状态,并创建复杂的、布满条件的双向转换弧。当行为逻辑增加时,状态机的连线会迅速变成难以理解的“蜘蛛网”,而行为树只需增加新的分支即可。
二、 高级控制节点:并行 (Parallel)
传统的 Selector 和 Sequence 节点是串行执行的,但在复杂 AI 设计中,我们常常需要同时执行多个任务。
-
核心观点: 并行 (Parallel) 节点允许其下的多个子树同时执行,完美地模拟了现实世界中“一心多用”的行为模式,这是传统状态机难以优雅实现的。
-
关键术语:
Parallel(并行节点) -
生动类比: 想象一个士兵的行为:
- 主任务: 从 A 点移动到 B 点支援队友。
- 并发任务: 在移动过程中,持续保持警戒,左右观察,并在发现敌人时开火。
这两个行为是同时发生的。使用
Parallel节点,我们可以将“移动到B点”和“警戒射击”作为两个子树同时运行,极大地丰富了 AI 的行为表现力。
三、 引擎核心:Ticking (心跳更新) 机制
这是理解行为树在引擎中如何运作的关键,也是其与状态机在执行模型上的根本区别。
-
核心观点: 经典的行为树模型在每一帧 (Tick) 都会从根节点 (Root) 开始重新进行遍历和评估。这种机制保证了 AI 的高响应性,能够随时中断低优先级任务以响应突发的高优先级事件。
-
执行流程:
- 每一帧开始,执行流程从树的 根节点 启动。
- 根据
Selector,Sequence等节点的规则,遍历树的各个分支。 - 决定当前帧应该执行哪个或哪些(在使用
Parallel时) 动作 (Action) 节点。
-
为何如此设计?——为了实现“行为抢占”:
- 场景: 一个 AI 正在执行一个无限循环的“巡逻”任务。
- 突发事件: 玩家突然出现在其视野中。
- 行为树响应: 在下一帧,由于是从根节点重新评估,高优先级的“发现敌人 → 追逐”分支的条件被满足,它会立即获得执行权。之前正在
Running的“巡逻”任务会被 中断 (Interrupt)。 - 对比状态机: 在一个简单的状态机中,AI 可能会“卡”在巡逻状态,直到当前巡逻路径点走完,才会检查转换条件,导致反应迟钝。
-
性能考量与优化:
- 问题: 每帧从头遍历对于非常庞大的行为树来说,开销巨大。
- 引擎优化方案: 一些现代引擎采用混合模式。默认只从当前处于
Running状态的节点开始Tick,但引入了 事件驱动 (Event-Driven) 机制。 - 事件驱动: 当一个重要的外部事件(如“收到指挥官命令”、“被子弹击中”)发生时,该事件会强制行为树 从根节点重新评估,或者直接激活/禁用某个特定的子树。
- 优化的代价: 这种优化虽然提升了性能,但也给设计师带来了心智负担。如果忘记配置相应的事件来触发行为切换,就可能导致 AI 行为不符合预期(例如,士兵埋头砍人,却忽略了指挥官“跟我走”的命令)。
四、 行为树的扩展与增强
为了让行为树更简洁、更强大,社区和引擎开发者引入了 Decorator 和 Precondition 等概念。
-
核心观点: 通过 Decorator (修饰器) 和 Precondition (前置条件) 等附加节点,可以在不改变行为树核心结构的前提下,极大地简化逻辑、增强可读性。
-
1. 修饰器 (Decorator)
- 定义: 一种特殊的节点,它只有一个子节点,作用是 包装 (Wrap) 并 修饰 其子节点的行为或结果。
- 常见类型:
Loop: 将子节点的行为重复执行 N 次或无限次。Wait: 在执行子节点前,等待指定的时间。Inverter: 反转子节点的执行结果(Success变为Failure,反之亦然)。Cooldown: 限制子节点的执行频率。
- 示例: 实现“移动到A点,等待1秒,再移动到B点”。
- 传统方式:
Sequence→MoveToA→Wait(1s)→MoveToB - 使用 Decorator:
Sequence→ (Wait(1s)Decorator →MoveToA) → (Wait(1s)Decorator →MoveToB)。这种方式将等待逻辑附着在动作本身,逻辑更内聚。
- 传统方式:
-
2. 前置条件 (Precondition)
- 定义: 附加在某个节点或分支上的一个判断条件。只有当该条件满足时,其下的分支才有机会被执行。
- 作用: 极大地 “剪枝” 和简化行为树。它将大量的条件判断节点从主干逻辑中分离出来,使树的结构更加清晰,专注于“做什么”而非“在什么条件下做”。
- 革命性的简化 (吃豆人案例):
- 原始树: 可能需要复杂的
Selector和Sequence嵌套来判断“是否吃了大力丸”、“是否看见鬼”等。 - 使用 Precondition 的树:
Selector ├── [Precondition: IsPowerPillActive?] -> Chase Ghost ├── [Precondition: IsGhostVisible?] -> Flee from Ghost └── [Default] -> Find Pills
- 原始树: 可能需要复杂的
五、 总结:行为树的设计哲学
- 核心价值: 行为树最了不起的成就,并非其数据结构上的创新,而是它提供了一套 高度符合人类直觉的设计与思考模型。
- 最终目标: 让 AI 设计师能够像写故事大纲一样设计 AI 行为,使其易于构建、理解、调试和迭代,这正是行为树在游戏工业界大行其道的核心原因。
行为树的深层解读与未来展望
这是对图形学技术讲座第八部分的学习笔记。本部分深入探讨了行为树(Behavior Tree)的设计哲学、核心组件,并对其局限性进行了深刻的剖析,最后引出了下一代AI决策技术的核心思想——目标导向规划。
一、 行为树 (Behavior Tree) 的核心价值与设计哲学
讲座首先强调,行为树在数学上并非颠覆性创新,其真正的革命性在于它提供了一种 高度符合人类思维、易于设计和维护AI行为的框架。
- 核心观点: 行为树最大的贡献是工程学上的美感和实用性,它将复杂的AI逻辑拆解为设计师可以直观理解和编辑的模块化结构。
- 设计美学: 仅通过五种基本类型的节点(序列、选择、并行、装饰、叶节点),行为树就能组合出几乎所有我们能想到的复杂AI行为。这体现了其设计的优雅与强大。
- 类比: 这种分支决策的模式,与现实世界中的军事决策或企业战略规划中使用的决策树模型非常相似,都遵循一种“如果...那么...”的逻辑流。
二、 行为树的关键组件:黑板 (Blackboard)
为了让行为树的不同部分能够共享信息,引入了黑板这个关键概念。
- 核心观点: 黑板 (Blackboard) 是一个全局的、动态的数据中心,充当行为树中各个独立节点之间信息交换的桥梁。
- 解决的问题: 行为树的执行是分层和分支的。当一个分支的行为需要影响另一个分支的决策时,就需要一个共享的上下文。
- 示例: 一个“攻击”节点成功击杀敌人后,需要通知其他节点。它可以在黑板上写入一个状态,如
lastKillTime = now()。后续一个“庆祝”节点可以读取这个状态,来决定是否播放庆祝动画,并在播放后清除该状态。
- 示例: 一个“攻击”节点成功击杀敌人后,需要通知其他节点。它可以在黑板上写入一个状态,如
- 实现方式: 通常是一个 键值对(Key-Value)存储系统,类似于我们熟悉的 事件系统(Event System)。通过一个唯一的ID(Key)关联各种类型的数据(Value),不同的行为树节点可以根据需要“生产”或“消费”这些数据。
三、 行为树的优势与局限
1. 核心优势
- 高度符合人类认知: 结构清晰,逻辑直观。
- 易于维护与扩展: 模块化的节点设计使得添加、删除或修改AI行为变得非常方便。
- 强大的调试能力: 行为树的执行过程可以被可视化。例如,在Unreal Engine中,开发者可以实时看到哪些节点正在被激活、哪个分支正在运行,极大地简化了Debug过程。
2. 核心局限
- 性能开销: 如果不进行优化,行为树在 每一帧(Tick)都会从根节点开始遍历,这在AI角色数量众多或行为树非常复杂时,会造成不小的性能浪费。
- 根本性的设计缺陷:缺乏“目标”: 这是讲座中提出的最深刻的洞见。无论是状态机还是行为树,其本质都是 反应式AI (Reactive AI)。
- 关键术语: 反应式AI,讲座中生动地描述为“神挡杀神,佛挡杀佛”。AI只是根据预设的规则对当前环境做出反应,它没有长远的、内在的“目的”或“追求”。它像一个没有灵魂的机器人,只是在执行指令。
四、 AI决策的未来:从“反应式”到“目标导向”
行为树的局限性引出了AI决策的下一个演进方向。
- 核心观点: 高级的、更像人类的AI,不应该是被动反应,而应该是主动为了达成某个目标而进行规划。
- 思维模式的转变:
- 传统方法 (行为树/状态机): 正向推导 (Forward Derivation)。 “因为发生了A,所以我执行B”。这是一种“脚踩西瓜皮,滑到哪是哪”的模式。
- 未来方法 (目标导向): 逆向规划 (Inverse Planning)。 “我的 目标 (Goal) 是考上大学,为了这个目标,我需要 规划 (Planning) 出学习数理化、做习题等一系列动作”。AI会评估所有 可选动作 (Actions),并根据世界状态和最终目标,选择最优的行动序列。
- 关键术语与技术:
- 目标导向AI (Goal-Oriented AI): 以最终目标为驱动,而不是以当前刺激为驱动。
- 规划 (Planning): 制定一系列行动以达成目标的过程。
- 搜索 (Search): 在所有可能的行动空间中寻找最优解的算法。
- 深度学习 (Deep Learning): 可用于状态评估、动作选择等复杂决策场景。
五、 问答环节 (Q&A) 精选
-
Q: 行为树和
if-else的本质区别是什么?- 核心回答:
if-else可以看作是行为树的一种极简特例(一个只包含选择器和条件的树)。 - 本质区别在于表达力和工程美学。行为树提供了一套更高级、更结构化的语言来组织复杂的逻辑流,特别是处理“运行中”状态、中断和恢复等场景。如果强行用
if-else模拟复杂的行为树,代码会充斥着大量的goto或jump语句,变得难以维护,形成“意大利面条式代码”。
- 核心回答:
-
Q: AI如何从环境中感知(Sensing)和提取数据?
- 核心挑战: AI感知面临的最大挑战是处理 异构数据 (Heterogeneous Data)。
- 数据类型举例:
- 简单的布尔值 (
true/false) 或数值 (敌人血量)。 - 空间坐标 (敌人位置)。
- 数据集合 (视野内所有敌人的ID数组)。
- 更复杂的数据结构,如一张 影响力图 (Influence Map),AI需要对其进行查询和分析。
- 简单的布尔值 (
- 这意味着一个强大的AI系统必须有能力整合和理解这些来源和格式完全不同的数据。
总结: 这一部分内容从行为树的实用价值出发,深入到了其设计的哲学层面,并敏锐地指出了其作为“反应式AI”的根本局限。最终,为我们揭示了通往更智能、更人性化AI的道路——目标导向的规划与决策,为后续学习更前沿的AI算法奠定了坚实的理论基础。
AI感知与寻路难题
在本次讲座的最后一部分,我们聚焦于AI系统在实际游戏开发中遇到的两个核心难题:AI如何高效、统一地感知复杂的游戏世界,以及如何处理导航网格(NavMesh)在特殊地形下的生成问题。
一、AI 感知(Perception)的设计挑战
AI做出决策的第一步是“读取”环境信息。这个看似简单的过程,在工程实践中会遇到两大挑战。
1. 挑战一:异构数据的统一表达与获取
AI需要从游戏世界中读取各种类型的数据,这些数据在结构和类型上千差万别。
-
核心观点:AI系统必须设计一套统一的语法和接口,来处理和查询来自游戏世界的 异构数据(Heterogeneous Data)。
-
关键术语:异构数据
-
数据类型举例:
- 基础变量: 数值(如生命值)、布尔值(如
isAlive)。 - 空间信息: 坐标点(如敌人位置)。
- 集合/数组: 可见敌人的ID列表。
- 复杂结构: 图像/纹理,例如需要进行采样查询的 影响力图(Influence Map)。
- 基础变量: 数值(如生命值)、布尔值(如
-
解决思路:
- 可以借鉴引擎中已有的 反射(Reflection)系统 或 事件系统的Payload设计。这些机制本身就是为了处理和传递不同类型的数据而设计的,可以扩展应用于AI的数据感知层,从而实现统一的访问接口。
2. 挑战二:大规模感知数据的查询效率
当游戏中存在大量AI单位时,数据感知的性能开销会急剧上升,成为严重的性能瓶颈。
-
核心观点:AI在进行决策时需要查询海量的复合数据,单纯的暴力轮询会导致巨大的性能问题。因此,AI的感知系统必须进行精细的规划与优化。
-
关键术语: 数据查询(Data Query) 、 性能瓶颈(Performance Bottleneck)
-
场景分析:
- 问题: 在一个有数百个AI单位的战场中,每个AI可能同时感知到几十个敌人。
- 计算:
数百个AI * 每个AI感知几十个敌人= 每帧数千甚至上万次的数据查询请求。 - 查询内容: 每次查询不仅是获取一个ID,还包括一系列复合数据:
- 敌我关系(Team ID)
- 当前状态(如攻击、防御、眩晕)
- 装备情况(是否有武器)
- 空间关系(距离远近、是否在攻击范围内)
- 健康状况(是否残血)
-
优化方向:
- 必须对AI感知世界的行为进行指令的规划和处理,而不是简单地让每个AI独立、实时地查询所有信息。可以考虑使用空间划分、分时更新(Time Slicing)、缓存等技术来降低查询压力。
二、导航网格(Navigation Mesh)的垂直面处理难题
这是一个在路径规划(Pathfinding)中非常经典且棘手的问题,尤其是在涉及攀爬、跳跃等垂直移动的游戏中。
1. 问题描述:垂直临界面的生成困境
标准的NavMesh生成算法在处理陡峭的垂直或近乎垂直的表面(如墙壁、悬崖)时,通常表现不佳。
-
核心观点:自动生成的NavMesh无法自然地表达需要特殊动作(如攀爬)才能通过的垂直连接,导致AI路径断裂。
-
关键术语: Navigation Mesh, 垂直临界面(Vertical/Near-Vertical Surfaces)
-
标准算法的行为:
- 较矮的垂直面: 可能会被算法忽略,直接生成一个连续的可通行网格,AI会直接“走”过去。
- 较高的垂直面: 算法会认为这里是障碍物,导致路径中断。最终会在底部和顶部生成 两个相互独立的、不连通的NavMesh。
2. 解决方案:手动标记与数据持久化
既然自动生成无法满足需求,业界的通行做法是结合工具链,通过手动标记的方式来“弥补”算法的不足。
-
核心观点:通过在编辑器中手动标记特殊连接点(如攀爬点),并将这些信息存储在世界空间坐标中,以实现数据的持久化,从而在NavMesh更新后依然能够重建正确的寻路连接。
-
关键术语: 手动标记(Manual Tagging) 、 世界空间坐标(World Space Coordinates) 、 数据持久化(Data Persistence)
-
实施步骤:
- 提供设计工具: 引擎需要提供一套工具,让关卡设计师可以在场景中标记可攀爬或可跳跃的特殊点(例如墙壁的顶部和底部边缘)。
- 触发特殊行为: 当AI寻路到这些特殊标记点时,其行为和动画状态会发生切换。例如,从“行走”状态切换到“攀爬”状态,收起武器,播放攀爬动画,到达顶部后再播放翻越动画。
- 数据存储策略:
- 关键原则: 不要将这些手动标记点的信息存储在NavMesh数据本身中。
- 正确做法: 将这些标记点的位置信息以世界空间坐标的形式,保存在关卡数据或其他独立的数据结构里。
- 原因: NavMesh在关卡迭代中会频繁地重新生成,如果标记信息存在于NavMesh内部,每一次重新生成都会导致这些手动工作全部丢失。存储在世界坐标系中则保证了其持久性。
- 路径重建: 每次NavMesh生成后,引擎会读取这些保存在世界坐标中的标记点,并用它们来“缝合”或“连接”断开的NavMesh。这可以通过添加 离网链接(Off-Mesh Links) 或自定义的 跳跃点(Jump Point) 来实现。
三、总结与展望
AI系统的健壮性不仅取决于算法的先进性,更依赖于底层引擎设计的巧思。无论是统一处理异构数据的感知系统,还是应对复杂地形的寻路解决方案,都体现了游戏引擎开发中充满挑战与权衡的现实。这些看似“脏活累活”的工程问题,正是将AI技术真正落地并创造出可信角色的关键所在。