做多智能体避碰这块,只要查过资料,最后基本都会绕到同一篇论文上:Jamie Snape等人2009年发表在CVPR上的《Reciprocal n-body Collision Avoidance》。标题里的ORCA(Optimal Reciprocal Collision Avoidance)是论文提出的核心算法,RVO2则是它的开源实现库。可以说,这篇论文加上这个库,基本定义了后来十年里多机器人、游戏AI、无人机集群避碰的主流做法。
这篇笔记不是复述论文内容,而是结合我从理论到落地过程中踩过的坑,把ORCA从“论文公式”变成“能跑的代码”之间的那些关键环节拆开讲讲。如果你正在做多机器人导航、游戏里的群体移动,或者无人机编队路径规划,这篇应该能帮你少走不少弯路。
1. 从VO到ORCA:这篇论文到底解决了什么问题
1.1 传统VO方法的两大痛点
要理解ORCA,先得知道它站在谁的肩上。在ORCA出现之前,多智能体避碰的主流思路是VO(Velocity Obstacle,速度障碍法)。VO的理念很直观:假设环境里有一个动态障碍物B,它当前速度为v_B,那么对于正在以速度v_A运动的机器人A来说,如果相对速度v_A - v_B指向某个方向,会导致A和B在未来某个时间内相撞,那这个v_A就是“危险速度”。所有危险速度的集合,就是B对A的“速度障碍”。
VO在单对单避碰时非常好用,但到了多智能体场景就露怯了。问题出在两个地方。
第一个问题是“抖振”。假设A和B相向而行,A算出来的最优避让速度是向左偏,于是A往左走。B看到A往左了,重新计算后发现自己也应该往左偏,于是B也往左。结果两个人又对上了,只能再算再偏,路线上就出现明显的来回抖动,严重的时候甚至像两个喝醉了的人互相堵路。这种振荡在视觉上非常难看,放在机器人上面就是反复修正航线,效率极低。
第二个问题是“责任分配不清”。在VO框架下,每个agent都把对方当成单纯的环境障碍物,也就是默认“对方不会让我,所以我必须让到底”。这在单障碍场景没问题,但多智能体场景里所有人都在让,反而会过度避让:明明两边各让一半就能错开,结果每个agent都让了整整一个身位,整个编队被拉得七零八落。
这两个问题的根源,是VO没有考虑“对方也是会避碰的智能体”这件事。而ORCA的核心突破,就是把这个假设写进了算法里。
1.2 RVO和ORCA:从“轮流让”到“各让一半”
为了让两个agent配合避让,2007年前后出现了RVO(Reciprocal Velocity Obstacle,互惠速度障碍)。RVO的思路是:把速度障碍的中心轴偏移到两个agent速度平均值的位置,相当于把避让责任“对半分”。既然是双方各让一半,理论上就不会出现都让到底的浪费,也让“谁让谁”有了一个约定俗成的规则。
但这个方案只是把VO的整体坐标平移了,速度障碍本身的形状没变,在实际运行中还是会出现抖动,拓扑结构也没改善。
ORCA本质上是RVO的数学重构。它不再构造一个完整的速度障碍区域,而是把“避碰约束”直接转化为一个半平面。每个agent只需要保证自己选择的速度落在所有约束半平面的交集里就够了,在这个交集里挑一个最接近自己期望速度的方向走,就是最优解。
这个改动带来的好处非常明显:约束从“一个不规则多边形区域”变成了“一组线性半平面的交集”,求解从几何求交变成了线性规划,计算速度快了不止一个量级,而且天然支持任意数量的动态障碍物——多一个agent,就多一条半平面约束,往上叠就行。
所以读ORCA的论文,最重要的不是背公式,而是理解“半平面化”这一步是怎么做到的。公式只是这个几何思想的外壳。
2. 核心算法拆解:半平面约束是怎么来的
2.1 ORCA的数学推导与几何含义
先做一个基本设定。有两个圆形agent,A和B,半径分别为r_A和r_B,当前位置分别是p_A和p_B,当前速度分别是v_A和v_B。A希望选出的新速度是v_A_new。
第一步,定义碰撞条件。如果A保持当前速度v_A、B保持当前速度v_B,它们在时间τ内会不会撞上,等价于看A相对于B的位移变化。更严谨地说,A相对于B的速度是v_A - v_B,如果从当前相对位置p_A - p_B出发,以相对速度v_A - v_B运动,在τ时间内能进入以B为圆心、r_A + r_B为半径的圆,那就会撞。
用集合语言来表达这个“危险相对速度”的集合:
VO^τ_A|B = { v | ∃t ∈ [0, τ], (v - v_B) * t ∈ D(p_B - p_A, r_A + r_B) }
其中D(c, r)表示圆心在c、半径r的圆盘。这个式子的意思是:如果相对速度v - v_B指向了以(p_B - p_A)为圆心、(r_A + r_B)为半径的圆,那么A和B在时间τ内必然碰撞。
第二步,给这个速度障碍加入“互惠”思想。RVO的经典做法是,把速度障碍的顶点平移到两个agent平均速度v_A + v_B的一半处。这个平移量的含义是:避碰不是A单方面的责任,也不是B单方面的责任,而是双方各承担一半。所以B在避碰时,也会把自己的速度从v_B主动移向某个方向,A在规划时按照B也会让一半的预期来选速度。
第三步,也就是ORCA的关键步骤,从这个平移后的速度障碍中提取“最优半平面”。假设ORCA_A|B是A需要遵守的避碰半平面,它的边界是一个过点(v_A + v_B) / 2、垂直于A到B方向u的直线。这个半平面定义如下:
ORCA_A|B = { v | (v - (v_A + v_B) / 2) · u ≥ 0 }
这里u是避碰方向上的单位向量,具体方向是从VO区域边界指向该区域内部、经过平移后的“可逃逸方向”。当A选的速度v满足这个不等式时,它就在半平面的安全侧,避开了B。
几何上更好理解:你想象两个圆在相向运动,如果它们要保持不相撞,A的速度就必须落在某个“切平面”的另一侧。ORCA把那一条临界切线直接作为约束边界,以两个速度的平均点为新原点,把整个可行速度空间一分为二。
对所有邻居agent都算一遍这个半平面,取交集,就得到A的最终可行速度集合:
可行速度 = { v | v ∈ SA, 且对任意邻居B,v ∈ ORCA_A|B }
SA表示A的物理速度限制,比如最大速度maxSpeed对应的圆。
2.2 为什么要把避让责任“对半分”
“对半分”这个设计乍一听像是拍脑袋定的,实际上背后有博弈论的考量。多智能体系统里最难的问题就是协调:没有中央调度器,每个agent只能通过局部信息做决策,必须有一种隐式的“默契”,否则就会陷入互相等待或者互相冲撞的死锁。
如果按传统VO,每个agent都假设对方不避让,那责任全在自己,看起来保守,实际上在密集场景里效率极低:所有人都让路,编队就散了。如果每个agent都假设对方会避让一半,自己也让一半,那在理想情况下,双方的航行轨迹会呈现完美的对称互补,队伍紧密又流畅。
这个“对半分”还有一个额外好处:它天然解决了通信依赖。不需要agent之间交换避碰意图,不需要协商优先级,每个agent只需要知道对方“当前在哪里、当前怎么走”,就能算出自己该让多少。这是分布式系统最喜欢的性质——信息延迟、丢包、失效都不会导致整个系统崩溃,最坏情况就是某个agent没有让足一半,那另一个agent最多在下一帧重新计算,把责任承担回来。
在我实际跑仿真的时候,这种“无通信协调”的性质比论文里吹的性能指标更有价值。真实环境的通信链路易受干扰,一旦出现丢帧,需要协商的算法就会卡死,ORCA则能优雅降级。
2.3 参数τ的含义与影响
ORCA里最重要、也最容易调坏的一个参数是τ(论文里叫timeHorizon)。它代表“向前看多久的碰撞风险”。
τ越小,比如0.5秒,agent就越“自私”,只在马上就要撞的时候才让路,平时贴着最优路径走,轨迹非常激进,适合高速高机动场景,代价是有时候会让得不充分,导致微小碰撞或频繁急转。
τ越大,比如5秒甚至10秒,agent就会提前很久开始规避,轨迹平滑但绕路严重,整体吞吐量下降,适合重型机器人或需要保持体面的低速场景。
一个直觉经验是:τ至少得大于控制周期,否则算法永远在“反应过去”,而不是“规避未来”。如果你的控制频率是20Hz,τ至少给0.5秒,实际项目里我更倾向于给1.5到2倍控制周期,留出响应余量。
另一个容易忽略的点是:τ对“对称避让”是否成立也有影响。两个agent如果τ不同,互惠假设就会失效,避让责任不再是五五开,而是τ小的一方行动更晚、τ大的一方承担更多。所以多agent系统里,所有agent最好使用统一的τ,否则要重新调责任分配。
3. 从论文到可运行代码:RVO2库的实操要点
论文看懂了,落到代码还是有一道坎。ORCA的标准实现是RVO2库,C++版本是原版,后来社区也贡献了Python、Java、C#等绑定。这里以Python版本为例,讲一下从接口到调参的完整链路。
3.1 环境准备与核心类关系
Python版RVO2库安装很简单:
pip install rvo2不是官方包,但社区维护得很好,基本覆盖了C++版的核心API。装完后先理解它的核心数据结构。
RVO2里有两个最核心的类:Simulator和Agent。Simulator是全局管理器,负责添加障碍物、推进仿真、设置全局参数;Agent是单个智能体实例,有位置、速度、半径、最大速度、邻居搜索范围等属性。
初始化一个仿真环境的经典代码是这样:
import rvo2 sim = rvo2.PyRVOSimulator( timeStep=0.25, neighborDist=15.0, maxNeighbors=10, timeHorizon=5.0, timeHorizonObst=5.0, radius=2.0, maxSpeed=2.0, velocity=(0, 0) ) # 添加两个agent agent1 = sim.addAgent((0, 0), neighborDist=15.0, maxNeighbors=10, timeHorizon=5.0, timeHorizonObst=5.0, radius=1.0, maxSpeed=2.0, velocity=(0, 0)) agent2 = sim.addAgent((10, 10), neighborDist=15.0, maxNeighbors=10, timeHorizon=5.0, timeHorizonObst=5.0, radius=1.0, maxSpeed=2.0, velocity=(0, 0))这段代码初始化的几个参数要逐个说清楚。
timeStep是仿真步长,ORCA是离散时间规划算法,每个timeStep重新计算一次最优速度。timeStep越小,轨迹越平滑,计算量越大。实际使用中0.1到0.25秒比较常见。
neighborDist是邻居搜索半径,超过这个距离的agent不参与避碰计算。这个参数直接决定计算复杂度:RVO2用的是KD树做近邻搜索,neighborDist大则每个agent要考虑的邻居多,计算量大;小则可能漏掉该避的碰撞。经验值是取maxSpeed * τ的2倍左右,保证“能看到的范围至少覆盖紧急刹车距离”。
maxNeighbors是每个agent最多考虑的邻居数量,超过则按距离取最近的那几个。这个参数是性能保险丝,防止极端密集场景下计算量爆炸。
addAgent里的radius是agent的物理半径,注意Simulator构造函数里的radius会被后续addAgent里的radius覆盖,所以构造函数里的radius其实只是默认值,实际每个agent可以单独设置。
3.2 主循环:速度更新与位置推进
配置完成后的主循环是这个样子:
# 为agent设置期望速度(偏好速度) sim.setAgentPrefVelocity(agent1, (0.5, 0.5)) sim.setAgentPrefVelocity(agent2, (-0.5, -0.5)) # 推进仿真 for step in range(100): sim.doStep()注意doStep内部做的事情其实有三步:第一步更新所有agent的邻居信息,第二步对每个agent求解ORCA线性规划,得到新的实际速度,第三步用实际速度去积分位置。这三个步骤对应论文里的“感知-规划-执行”,是完整的闭环。
这里的setAgentPrefVelocity设置的prefVelocity不是agent最终的速度,而是一个“想要的速度”。ORCA算法会在这个期望速度基础上做修正:如果期望速度在可行半平面交集内,就直接用;如果不在,就求解离期望速度最近的可行速度。
这个设计非常聪明,它把“导航”和“避碰”解耦了。上层的人(比如全局路径规划器)只需要告诉ORCA“我建议你往这个方向走”,ORCA负责确保“不管你怎么建议,我实际走的方向不会撞到人”。上层不用考虑动态障碍,下层不用考虑目标点在哪。
我自己做导航集成时,把全局规划器的输出速度直接作为prefVelocity输入,效果出乎意料地好,不需要额外做速度平滑或者障碍物规避层,ORCA天然就把这部分工作消化了。
3.3 静态障碍物的处理方式
RVO2不止能处理agent之间的动态避碰,还能处理静态障碍物,比如墙壁和柱子。添加方式如下:
sim.addObstacle([(5, 0), (5, 5), (6, 5), (6, 0)]) sim.processObstacles()注意添加障碍物后必须调用processObstacles,这个函数会做障碍物的预处理,生成障碍物的边和顶点的速度障碍数据。不调用的话,障碍物是不会参与避碰计算的。
这里有一个容易踩的坑:静态障碍物的避碰和动态agent之间的避碰不是同一套逻辑。对于静态障碍物,ORCA默认把责任完全放在agent身上——毕竟墙壁不会让路。所以静态障碍物的约束是无条件强制性的,agent只能在凹角附近“贴墙走”,没有“各让一半”的说法。这就导致agent在静态障碍物附近的轨迹比动态避碰更保守,转弯半径更大。
如果你在静态障碍物密集的走廊场景里发现agent转弯非常生硬,先检查一下障碍物的边是否完整闭合,以及障碍物轮廓是否过于粗糙。RVO2对障碍物是直接用多边形线段做碰撞检测的,线段越短、采样点越密,轨迹越平滑,代价是processObstacles时间变长。
3.4 一个完整的“交错”仿真与输出
纸上谈兵不如动手跑一次。下面这个完整例子里,两个agent在坐标系里“X”型交叉相遇,分别从(0,0)和(10,10)出发,目标是对角线的另一头。我把每步的坐标打印出来,你就能直观看到ORCA是怎么让它们互相让行的。
import rvo2 import math sim = rvo2.PyRVOSimulator(1/60, 15.0, 10, 5.0, 5.0, 0.5, 2.0, (0, 0)) a1 = sim.addAgent((0, 0), 15.0, 10, 5.0, 5.0, 0.5, 2.0, (0, 0)) a2 = sim.addAgent((10, 10), 15.0, 10, 5.0, 5.0, 0.5, 2.0, (0, 0)) target1 = (10, 10) target2 = (0, 0) for i in range(200): v1 = sim.getAgentVelocity(a1) v2 = sim.getAgentVelocity(a2) # 简单目标导引 to_target1 = (target1[0] - sim.getAgentPosition(a1)[0], target1[1] - sim.getAgentPosition(a1)[1]) dist1 = math.hypot(to_target1[0], to_target1[1]) if dist1 > 0.1: pref1 = (to_target1[0] / dist1 * 2.0, to_target1[1] / dist1 * 2.0) else: pref1 = (0, 0) to_target2 = (target2[0] - sim.getAgentPosition(a2)[0], target2[1] - sim.getAgentPosition(a2)[1]) dist2 = math.hypot(to_target2[0], to_target2[1]) if dist2 > 0.1: pref2 = (to_target2[0] / dist2 * 2.0, to_target2[1] / dist2 * 2.0) else: pref2 = (0, 0) sim.setAgentPrefVelocity(a1, pref1) sim.setAgentPrefVelocity(a2, pref2) sim.doStep() if i % 10 == 0: print(f"step {i:3d} A=({sim.getAgentPosition(a1)[0]:.2f}, " f"{sim.getAgentPosition(a1)[1]:.2f}) " f"B=({sim.getAgentPosition(a2)[0]:.2f}, " f"{sim.getAgentPosition(a2)[1]:.2f})")跑完后你可以观察到:两个agent在靠近交汇点之前就开始侧向偏移,A往左上让,B往右下让,然后在交错后回到原路径,整个过程没有振荡。
这个就是ORCA“互惠避让”的直观体现:没有任何通信,没有提前协商,双方靠同一个约定俗成的“各让一半”规则,完成了平滑交互。
4. 参数调优、典型故障与项目实战经验
4.1 最容易翻车的参数组合
参数调优是ORCA项目里耗时最多的环节,也是踩坑率最高的地方。把最常见的几个“翻车组合”列一下。
第一个翻车组合是timeHorizon过大 + maxSpeed过大。比如timeHorizon=10,maxSpeed=3,agent会在完全没有碰撞威胁时就开始大幅绕路,因为算法“预见”了10秒后的潜在碰撞。看起来像agent在“怕鬼”,路线弯弯绕绕。解决方案是让timeHorizon与maxSpeed匹配:最大速度和期望速度的比值决定了你在τ秒内能走多远,τ要大于这个距离除以相对速度。
第二个翻车组合是neighborDist过小。常见于密集人群仿真中,为了优化性能把neighborDist从15缩到3,结果agent对距离较远但速度极快的agent完全无感,等发现要碰撞时已经来不及刹车。经验法则是neighborDist至少覆盖maxSpeed * τ的距离,也就是让agent“看得够远,确保刹车来得及”。
第三个翻车组合是radius远大于实际尺寸。有些项目里安全起见把agent的radius设成真实物理尺寸的1.5倍甚至2倍,结果在狭窄通道场景里出现agent互相“礼让到僵住”的局面。因为两个大圆在窄通道里的可行半平面交集是空的,ORCA会退化成“找惩罚最小的速度”,实际上就是贴着墙滑,看起来非常别扭。如果一定要加大安全距离,优先考虑改静态障碍物轮廓,而不是把radius调大。
4.2 典型故障一:agent抖动、来回摇摆
抖动是ORCA项目里最常见的故障,特征就是agent在一条直线上来回摆动,像在跳“祭祀舞”,整体无法前进。
抖动的原因通常是控制频率和时间步长不匹配。ORCA是离散时间规划,如果timeStep过大,一个周期内agent从决策到执行经历了太长的物理距离,导致每次决策都在重新“矫枉过正”;如果timeStep过小,但navigation层给的prefVelocity更新频率过低,agent会在两次更新之间反复执行同一个避让动作,等到prefVelocity终于变化时,修正量又太大了。
另一个隐藏原因是τ过小。τ=0.1时,agent只关心未来0.1秒的碰撞,而一个timeStep本身可能就已经接近这个时长,相当于算法总是在“刚刚看见碰撞就立刻躲避”,缺乏预测缓冲,自然就抖。
我的处理顺序是:先调小timeStep到0.1秒以下,确认不是控制频率问题;再把τ提到至少1.0秒,给预测留出余量;最后检查prefVelocity的更新是否平滑,避免上层速度突变影响下层决策。
抖动的“根治”方法是加一个程度轻的低通滤波或速度平滑器。但注意不要加太多滤波,否则会引入滞后,反而降低避碰能力。我一般用一阶低通,系数0.3到0.5之间,具体要靠现场调。
4.3 典型故障二:agent“冻住”或穿越
“冻住”是指agent停在原地不动,即使前方根本没有障碍物。“穿越”是指agent明明应该避让,却直接从其他agent身上碾过去了。
“冻住”在ORCA里通常意味着线性规划无解——所有半平面约束加上最大速度约束的交集为空。这种情况在密集场景里经常发生:三方agent在一个狭窄区域互相堵死,任何一个方向都被别人的避碰半平面封死。ORCA论文里提到这个情况,做法是返回一个“惩罚最小”的速度,但在实现里这很容易退化成“原地不动”。
解决思路有两个。第一,调大maxSpeed,给线性规划更大的搜索空间。我在仿真里发现,把maxSpeed从1.5调到2.0,原本会冻住的场景大幅减少,代价是速度偶尔超出预期。第二,引入“避碰优先级”。给不同agent设置不同的τ或radius,让低优先级agent先让路,打破对称性。这也解释了为什么在实际系统里不应该所有agent都用完全相同的参数——对称反而容易导致对称死锁。
“穿越”则通常是因为邻居搜索半径不足或者maxNeighbors太小,导致agent根本没有把另一个agent纳入计算,自然就穿模了。排查方式很简单:调大neighborDist和maxNeighbors,如果穿越消失,就是这两个参数的问题;如果仍然穿越,那是逻辑Bug,需要检查是否重复添加了agent导致索引错乱。
4.4 RVO2在真实项目里的性能表现与优化空间
RVO2最让人惊喜的一点是性能。在一台普通笔记本上,500个agent、每个agent最多考虑10个邻居、timeStep=0.1秒,单步仿真时间能控制在20毫秒以内,这包含了KD树重建、邻居搜索和线性规划求解的全流程。也就是说,500个agent完全可以跑在100Hz以上的实时频率。
如果对性能有更高要求,有几个成熟的优化方向。
第一个是用GPU并行。ORCA的每个agent的线性规划求解是相互独立的,天然适合SIMT架构。源码里有CUDA版本,实测10000个agent也能跑到200Hz以上。不过GPU版本的前期投入很高,如果只是几千个agent,CPU版已经够用。
第二个是邻居搜索优化。RVO2自带KD树,但在高度动态场景里,KD树每帧都要重建,重建成本不低。可以考虑用空间哈希或者网格索引替代,在均匀分布场景下性能反而更好。
第三个是求解器优化。RVO2的线性规划求解器是基于二维几何的定制版本,理论上已经很快了。但在高密度场景里可以考虑加入“提前终止”策略:当一个agent的期望速度已经通过所有半平面约束时,直接输出,不用再做完整的最小化。这个优化实现在很多场景里能省掉三分之一的求解时间。
4.5 我在项目里总结出来的调参顺序
调ORCA参数是个系统工程,我摸索出来一个还比较稳定的顺序,分享给你参考。
第一步,固定基础参数。radius按物理尺寸设,maxSpeed按任务需求设,timeStep按控制频率设,这几个先定死,不参与后续调整。
第二步,用单agent单障碍场景,调τ。找一个agent直线接近一个静态障碍物的测试场景,观察agent开始转向的距离。τ越大,转向越早。调到“转向点距离≈maxSpeed * τ”这个理论值就差不多。
第三步,加第二个agent,做交错测试。观察两个agent是否对称让行、是否抖动、是否在相交点附近有明显减速。这个阶段主要调neighborDist到maxSpeed * τ的2倍以上,保证交错时能看清对方。
第四步,加密集场景,调maxNeighbors。如果高密度下出现穿模或抖动,先加大maxNeighbors,不行再缩小agent半径或者调τ。maxNeighbors越大,计算越慢,所以这个值够用就好,不是越大越好。
最后一步,是在整个场景里跑长时仿真,观察是否出现“冻住”或绕路。如果有,回到第三步微调参数。这个流程走下来,大部分场景在半天内能拿到一个效果不错的参数组合。
5. 你需要知道的:ORCA的边界与适用场景
ORCA不是万能的,它有明确的适用范围。在我评估一个项目是否适合用ORCA时,通常会先问几个问题。
第一个问题是:智能体是不是“全向移动”的?ORCA的数学推导建立在“速度和方向可以独立改变”这个假设之上。如果你的机器人是差速轮式,转弯半径非零,或者有最小转弯半径限制,ORCA的输出速度不能直接用,需要额外的运动学约束层做后处理。我自己在阿克曼底盘上试过直接接ORCA输出,效果非常差——算法让车“平移”到一个新位置,但车根本做不到。
第二个问题是:障碍物的形状是不是可以用圆来近似?ORCA把agent都建模成圆形,优点是计算快,缺点是在狭长场景里保守过头。拿长条形AGV或者无人机吊舱做避碰,直接套ORCA会浪费大量空间,不如改用椭圆建模或者其他形状的避碰算法。
第三个问题是:系统里有没有主从关系或者优先级?ORCA默认所有agent平等,各让一半。但如果场景要求“自动驾驶汽车必须让行行人”,需要把主从优先级外挂进来,比如对优先agent的半平面约束加权、对非优先agent的半平面约束加大。RVO2源码里没有直接暴露权重接口,需要自己改求解器。
第四个问题是:你需不需要全局最优?ORCA是一个局部避碰算法,它只保证下一步不撞,不保证整体路径最优。如果你需要的是“几十个agent从A区域到B区域的时间最短”,ORCA单独做不了,得配全局规划器。ORCA是局部执行层,不是全局调度器。
反过来说,如果你的场景满足全向移动、类圆形agent、agent之间平等、局部避碰,那ORCA几乎就是最优解:实现成熟、参数少、性能好、社区大。游戏里的群体寻路、无人机集群巡航、仓储机器人的动态避碰,都是它的经典应用范围。
还有一点值得说:ORCA的环境感知完全基于当前位置和速度的观测,不需要历史轨迹预测模块。这让它在感知噪声大的场景里反而更稳定——你不需要预测对方未来五秒怎么走,只看它现在怎么走就够了。但也正因如此,面对突然加速或者急转弯的“恶意”agent,ORCA的表现会变差,因为互惠假设建立在对方行为连续平滑的预期上。
读这篇论文最让我受用的不是公式,而是它的“分工”思想——与其和不确定性较劲,不如建立一个能让各方都承担部分责任的机制。这个思想在分布式系统设计里比算法本身值钱得多。如果你看完这篇笔记去翻了原始论文或者跑通了RVO2的demo,会发现那些看起来很复杂的数学,本质上就是在讲清楚“每个人让一小步,比所有人让一大步更高效”这个朴素道理。