中EN
精修课概念页

局部避障:走廊里两个人怎么不跳舞

Local Collision Avoidance

更新于 2026-10-19 约 13 分钟游戏AI局部避障RVOORCA群体移动

走廊里的「舞蹈」

你一定在走廊里遇到过这种尴尬:迎面来了一个人,你往左让,他也往左;你改往右,他也往右……游戏里的 NPC 同样会这样,而且更夸张。

上一课的寻路解决不了这个问题。全局寻路假设世界基本是静止的,每个角色独占自己的路径;可一群角色同时走时,彼此就是不断移动的障碍。处理「附近的人怎么挤得开」的,是单独的一层:局部避障 (Local Collision Avoidance)。

两层分工

局部避障不替代寻路,而是接在它后面:全局路径给出一个「想往哪走、走多快」的期望速度 (Preferred Velocity),局部避障看看身边的邻居,把它修正成这一帧真正使用的速度。

flowchart LR
  G["全局路径 / 转向"] --> V["期望速度"]
  V --> O["局部避障:ORCA / RVO 求解"]
  N["邻居的位置和速度"] --> O
  O --> R["这一帧的实际速度"]
层负责不负责
导航网格 + A*绕开静止的墙、找到门、上对楼层每帧和上百人互相挤
局部避障近距离互相让路、躲开短时间的动态障碍发现「这条街堵死了,该换一条」这种全局重新寻路

速度障碍:会撞上的那些速度

要理解避障算法,先换一个视角:不看角色在哪,而看它能选哪些速度。把所有可能的速度画在一张图上,每个点代表一个速度,这张图叫速度空间。

速度障碍 (Velocity Obstacle, VO) 回答一个问题:假设 B 保持现在的速度不变,A 选哪些速度,将来会撞上 B?这些速度在速度空间里刚好围成一个锥:

BA世界:B 放大成半径 rA + rB 的圆v_Bv_A0速度空间:锥平移到 v_B
左:在世界里,把 B 放大成半径为 rA + rB 的圆,A 就可以当成一个点。从 A 出发、穿过这个圆的方向都会撞,它们夹成一个锥。右:B 自己也在动,所以在速度空间里,整个锥要平移到 B 的速度 v_B 上。A 现在的速度 v_A 落在锥里,保持下去就会撞。

要避开碰撞,A 只要选一个锥外的速度就行。如果期望速度刚好在锥里,就挑锥外离它最近的那个速度——改动最小,也最不突兀。

动手:在速度空间里避让

下面的画布就是 A 的速度空间。B 在 A 的前方稍微偏上,正迎面走过来。橙色的锥是会撞的速度,冰蓝实心点是 A 现在的速度,空心点是 B 的速度,小黄点是锥顶。鼠标是 A 的期望速度(白圈),大黄点是避障算法最终选出的速度。

先把鼠标放进锥里,看黄点怎么被推到锥的边上;再把「模式」切到 1,看锥顶跳到哪里去了。

速度空间:VO 与 RVO 的锥
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

拖大「半径之和」,锥会张得更开:要让开的速度范围变大了。

为什么 VO 会「跳舞」

用 VO,A 和 B 都假设对方不会变。于是两个人同时往旁边让;下一帧,各自发现对方已经让开了,原来的路线看起来又安全了,于是同时改回来;再下一帧,又同时让……这就是走廊里的那支舞,算法上叫振荡。

互惠速度障碍 (Reciprocal Velocity Obstacle, RVO) 改了一个假设:对方也会避让,而且双方各承担大约一半的避让责任。每个人只让一半,两边加起来正好让开,谁也不用来回改主意。van den Berg、Lin 和 Manocha 在 2008 年的 ICRA 论文里证明了,在论文的条件下,这样做既安全又不振荡。在画布上,这就是模式 1:锥顶挪到了两人速度的中点,A 需要改的量明显变小了。

ORCA:把约束变成半平面

RVO2 库里的最优互惠碰撞避免 (Optimal Reciprocal Collision Avoidance, ORCA) 又往前走了一步:每个邻居对 A 的约束,不再是一个锥,而是速度空间里的一个半平面——「速度要选在这条线的这一边」。所有邻居的半平面叠在一起,剩下的就是可行区域;再用一个低维的线性规划,求出可行区域里离期望速度最近的那个速度。

邻居 1邻居 2邻居 3期望速度选出的速度可行区域
示意图:三个邻居各给出一条约束线,绿色是同时满足三条约束的可行区域。期望速度在区域外,算法选出区域里离它最近的点。真实的约束线怎么从两人的位置和速度算出来,资料里没有展开,这里也不画具体数值。

按 RVO2 文档的说法,ORCA 比早期靠采样的 RVO 更快、更平滑,碰撞保证也更强。

在游戏里怎么用

取舍与常见的坑

小结与练习

  • 寻路给大方向,局部避障在期望速度上做近距离修正,两层分工;
  • 速度障碍是「保持下去就会撞」的那些速度,在速度空间里是一个锥,选锥外最近的速度即可;
  • VO 假设对方不变,双方同时改速会振荡;RVO 让双方各让一半,在论文条件下安全且不振荡;
  • ORCA 把每个邻居的约束变成半平面,用线性规划求离期望速度最近的可行速度。

练习:画布里只有一个邻居 B。在代码里再加一个邻居 C(给它自己的位置和速度),让 avoid() 同时避开两个锥。当期望速度同时落在两个锥里时,「挑最近的边界点」还管用吗?再回头想想,ORCA 把每个约束都变成半平面、用线性规划求解,好处在哪里。

  1. RVO / ORCA 局部避障(笔记) van den Berg et al. RVO (ICRA 2008); RVO2/ORCA docs
  2. gamma-web.iacs.umd.edu/RVO/icra2008.pdf
  3. gamma.cs.unc.edu/RVO2/documentation/2.0/whatsnew.html
  4. Navigation System UE RVO / Detour Crowd

相关页面

游戏开发寻路与导航理论基础Pathfinding & Navigation Theory寻路是在图上找代价可接受的路径:BFS / Dijkstra / A 是同一家族;NavMesh、路点、格子是不同的图构建方式。分层(走廊↔房间、扇区↔网格)与「全局路径 + 局部避障」把大规模导航拆开。A 本身不管编队、动态挤让与动画根运动。游戏开发群体与阵型行为Group & Formation Behaviors群体行为分两路:涌现式(Boids 分离/对齐/聚合)适合鱼群鸟群与背景人流;编排式(小队命令、阵型槽位、订单/风格)适合战术 AI。F.E.A.R. 表明复杂「侧翼」常可由个体目标 + 导航涌现,不必重型小队规划器。游戏开发游戏 AI 决策管线Game AI Sense-Decide-Act Pipeline游戏 AI(与生成式 DNN 不同)通常按 感知 → 决策 → 执行 分层:底层是寻路、感知与记忆;中层是角色控制(对接动画/物理);上层是目标与决策(行为树、效用、规划等)。GEA 强调「perception is reality」——玩家看不见的内部聪明不算数。游戏开发 · UE5AI 感知、EQS 与导航AI Perception, EQS & NavigationAI 的感官层由三块组成:AI 感知 (AI Perception) 组件挂在 AIController 上监听视觉/听觉/伤害等刺激并更新黑板;环境查询系统 (EQS) 用生成器产生候选项、先过滤后打分,回答「哪里最好」;导航系统从碰撞几何生成分瓦片的导航网格 (NavMesh),按多边形代价寻路,并提供 RVO 与 Detour Crowd 避让。它们为行为树/StateTree 提供输入。游戏开发 · GodotGodot 导航与寻路Godot NavigationGodot 同时提供图点寻路(AStarD)与基于导航网格的 NavigationServerD。网格路径适合「区域内任意点」;A 适合格子/路点玩法。游戏开发空间分区Spatial Partition空间分区 (Spatial Partition) 把对象按位置存进空间数据结构,使“找出某位置附近的对象”这类查询不必遍历所有对象,把 O(n) 或 O(n^2) 的操作降下来。最简单的是固定网格;还可以选层次结构(四叉树、BSP、k-d 树、BVH),以及边界固定或随对象自适应的划分方式。代价是对象移动时要更新结构,并且要额外占用内存。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原