简介:这是一份多智能体协同围捕算法的Python实现源码及项目说明,面向计算机、数学、电子信息等专业学生,可用于课程设计、期末大作业或毕业设计参考。资源基于不同环境设计了多种围捕策略,涉及Voronoi分割、MADDPG强化学习、单出口/多出口等典型场景,能帮助读者理解多智能体协同控制与路径规划的关键技术。压缩包共14个文件,其中13个Python脚本负责算法核心与仿真测试,1个Markdown文档为项目说明,整体大小仅38KB,结构简洁清晰。目前已有345人学习,适合具备一定Python基础、希望通过源码研读快速上手多智能体仿真的读者。下载后可直接运行或二次开发,借助源码与说明文档可以轻松复现仿真结果,并针对自身场景调整参数深入探索。
1. 多智能体协同围捕,难的不是追得够快
多智能体协同围捕(Cooperative Pursuit)在仿真、无人机集群、仓储机器人和自动泊车场景里都是绕不开的算法题。很多人第一反应是“给每个追捕者配一个追踪逻辑,追上去就行”,但这个思路在单逃逸者、无障碍环境下勉强能跑,一旦地图里出现静态障碍、动态障碍或者多个逃逸者,追捕者会互相堵路、在障碍前绕圈、甚至把包围圈撕开口子。真正难的不是单机追踪,而是“多机如何商量好谁走哪个方向、谁守哪个缺口”。
这个标题给出的落地方案很有意思:一套 Python 源码配一份项目说明,覆盖“各种环境”下的围捕任务。这意味着代码里通常要包含环境建模、目标分配、路径规划、速度控制和可视化仿真五个层次。本文按这套结构展开,从栅格地图建模讲到 RVO 避碰,给出可以直接跑的最小实现,把参数和坑说清楚。适合刚接触多智能体系统的人跟着复现,也适合已经写过单机路径规划、想往协同方向扩展的工程师参考。核心思路是一句话:围捕算法是“分配 + 规划 + 博弈”三件事的组合,而不是单纯的追踪算法。
2. 环境建模:把“各种环境”统一成栅格地图
2.1 为什么用栅格地图做多智能体围捕的公共环境接口
不同环境之间的差异很大:室外空旷场地、室内走廊、带有动态行人的仓库、迷宫类障碍布局,物理建模方式各不相同。如果每种环境单独写一套接口,围捕算法就要跟环境耦合,后期换地图等于重写逻辑。常见做法是引入**栅格地图(Occupancy Grid)**作为统一中间层,把连续空间离散化成网格,每个格子标记为可通行、静态障碍、动态障碍或未知区域。
栅格地图的优势在于它对上层算法暴露的信息是统一的:追捕者不关心障碍物是墙还是货架,只关心“这个格子能不能走、那个格子能不能看到目标”。同时,A*、Dijkstra 这类图搜索算法天然在栅格上运行,RVO 避碰需要邻居位置,也能从栅格对应的坐标空间里计算。代价是分辨率带来的内存和计算量,但对围捕仿真这种中等规模场景(地图 300x300 以内、智能体数量 10 个左右),完全够用。
2.2 用 Python 生成带静态障碍的栅格地图
import numpy as np import matplotlib.pyplot as plt class OccupancyGrid: def __init__(self, width, height, resolution=1.0): self.width = width self.height = height self.resolution = resolution # 每个栅格代表的物理尺寸,单位:米 self.grid = np.zeros((height, width), dtype=np.int8) def add_rect_obstacle(self, x_min, y_min, x_max, y_max): """在栅格地图上画一个矩形障碍物""" self.grid[y_min:y_max, x_min:x_max] = 1 def is_free(self, x, y): """查询某个栅格是否可通行,越界视为不可通行""" if x < 0 or x >= self.width or y < 0 or y >= self.height: return False return self.grid[y, x] == 0 # 生成 100x100 地图,加两个矩形障碍 env = OccupancyGrid(100, 100) env.add_rect_obstacle(30, 20, 45, 35) env.add_rect_obstacle(60, 50, 75, 65) plt.imshow(env.grid, cmap='gray_r', origin='lower') plt.title('Occupancy Grid for Multi-Agent Pursuit') plt.show()这段代码做的事很简单:建立一个二维数组,0 表示空地,1 表示障碍,并且把真实坐标与栅格索引通过resolution参数对应起来。is_free()是所有上层算法都要用的公共查询接口,越界直接返回False是为了避免智能体跑到地图边界外部——这个细节容易被忽略,但围捕过程中追捕者经常因为目标在边界附近而把自己逼出地图,提前拦截越界查询能省很多调试时间。
参数说明:resolution决定了空间精度,值越小栅格数越多,路径越精细但计算越慢。矩形障碍接口只是最小示例,实际可以从图片、JSON 文件或激光雷达数据生成栅格,核心是维护同样的grid数组和is_free()接口。
2.3 动态障碍与“各种环境”的工程实现
静态障碍只解决了环境建模的一半问题。“各种环境”还包括移动的障碍物,比如仓库里的其他车辆。处理方式有两种:一是把动态障碍物位置实时写入栅格,让路径规划每次查询时都能看到最新状态;二是动态障碍物单独维护位置列表,只在避碰层处理,不进入全局栅格。
第一种做法实现简单但会导致全局路径频繁重规划,追捕者容易出现抖动;第二种做法更接近工程实践——全局规划只考虑静态建筑物,局部控制层负责躲移动物体。常见方案是两层结构:全局层(A)只认静态栅格,局部层(DWA 或 RVO)用邻居位置做实时避碰*。下面的表格给出了三种典型环境的配置差异:
| 环境类型 | 全局路径规划 | 局部避碰策略 | 典型参数调整 |
|---|---|---|---|
| 无障碍开阔地 | A*(直接走直线) | 不需要或仅最小间距 | 速度权重调高 |
| 静态障碍环境 | A* 避开障碍 | RVO 防追捕者互相碰撞 | 邻居数量设大 |
| 动态障碍环境 | A*(静态地图) | RVO 同时避动态物 | 避碰时间窗设短 |
对应到源码组织上,环境类要对外提供三个能力:可通行查询、动态障碍当前位置、地图可视化接口。这样上层的多智能体围捕算法不需要知道环境内部是仿真还是真实感知模块提供的栅格,替换起来非常方便。
3. 协同围捕的分配策略:从贪心到匈牙利算法
3.1 围捕分配的本质是组合优化
当多个追捕者对多个逃逸者时,“谁追谁”直接决定围捕能否成功。一个错误的分配可能导致两个追捕者追同一个目标,另一个目标完全没人管。分配问题可以写成代价矩阵的形式:矩阵的行是追捕者,列是逃逸者,每个元素代表该追捕者去围捕该逃逸者的代价(通常用欧氏距离,也可以加入路径代价)。问题变成:为每个追捕者选一个逃逸者,使总代价最小,并且每个逃逸者至少被一个追捕者分配。
贪心算法(每个追捕者找最近的目标)在这个场景下会失效,因为两个追捕者可能同时选中同一个逃逸者,导致另一个目标被漏掉。解决这个问题有两条路:一是用匈牙利算法求全局最优匹配;二是用市场机制(拍卖算法)让追捕者之间通过“出价”协商分配。匈牙利算法实现简洁、结果可复现,适合做教学和多数仿真场景,下面重点讲它。
3.2 用 Python 实现匈牙利算法的围捕分配
import numpy as np from scipy.optimize import linear_sum_assignment # 追捕者位置(x, y) pursuers = np.array([[0, 0], [10, 2], [3, 8], [12, 10]]) # 逃逸者位置(x, y) evaders = np.array([[5, 5], [9, 7], [2, 9]]) # 计算代价矩阵:追捕者 i 到逃逸者 j 的欧氏距离 cost_matrix = np.linalg.norm(pursuers[:, None, :] - evaders[None, :, :], axis=2) # 匈牙利算法求解最小代价匹配 row_ind, col_ind = linear_sum_assignment(cost_matrix) # 输出结果:每个追捕者被分配的目标编号 for p_idx, e_idx in zip(row_ind, col_ind): print(f"Pursuer {p_idx} -> Evader {e_idx}, dist = {cost_matrix[p_idx, e_idx]:.2f}")代码里的linear_sum_assignment直接返回最优匹配的行索引和列索引。注意追捕者数量多于逃逸者时,row_ind中多余的追捕者会被分配到一个虚拟逃逸者,需要单独处理——常见做法是让剩余追捕者根据实时位置动态补位,去支援距离最近的围捕圈。
参数说明:代价矩阵不一定是欧氏距离,工程中可以把“路径规划后的实际距离”作为代价,这样分配结果更能反映真实追捕代价,但计算量大一个数量级。另一种做法是给欧氏距离加上一个障碍惩罚项,比如两智能体之间直线穿过的障碍数。这个折中在同人仿真里很常用。
3.3 多逃逸者场景:先分组再分配
如果逃逸者数量多于 2 个且之间距离较远,比较好的做法是在分配之前先做一次空间聚类,把距离接近的逃逸者划为一组,每组分配一组追捕者。这样组织逻辑比较清晰:外层是簇与追捕者的分配,内层是簇内追捕者对具体逃逸者的目标点分配。可以用最基础的 K-Means 处理逃逸者分组,再用匈牙利算法处理追捕者到簇的分配。
from sklearn.cluster import KMeans # 假设有 6 个追捕者、3 个逃逸者 kmeans = KMeans(n_clusters=3, random_state=0).fit(evaders) evader_groups = kmeans.labels_ # 每个逃逸者属于哪一组分组合并与之前的全局分配各有优劣:全局分配在逃逸者分散时更优,分组策略在逃逸者聚集且数量多时计算更快。实实在在的工程做法是两者结合——先算全局分配,如果发现多个逃逸者距离小于某个阈值(比如两倍围捕半径),就合并成一组统一围捕。这也解释了为什么标题里叫“协同围捕”:单一目标围捕只需要路径规划,多目标是分配问题,共同构成了协同的核心。
4. 路径规划与协同避碰:A*、DWA 和 RVO
4.1 全局路径规划:A* 在栅格地图上的实现
全局规划的任务是给每个追捕者规划一条从当前位置到围捕点的路径。栅格地图上最常用的是 A*,因为它能结合启发式函数快速收敛。下面是精简实现:
import heapq def a_star(grid, start, goal): """grid: OccupancyGrid 对象, start/goal: (x, y)""" open_set = [] heapq.heappush(open_set, (0, start)) came_from = {} g_score = {start: 0.0} while open_set: _, current = heapq.heappop(open_set) if current == goal: # 回溯路径 path = [] while current in came_from: path.append(current) current = came_from[current] path.append(start) return path[::-1] for dx, dy in [(1,0),(-1,0),(0,1),(0,-1),(1,1),(-1,-1),(1,-1),(-1,1)]: neighbor = (current[0] + dx, current[1] + dy) if not grid.is_free(neighbor[0], neighbor[1]): continue tentative_g = g_score[current] + (1.414 if dx != 0 and dy != 0 else 1.0) if tentative_g < g_score.get(neighbor, float('inf')): came_from[neighbor] = current g_score[neighbor] = tentative_g f_score = tentative_g + heuristic(neighbor, goal) heapq.heappush(open_set, (f_score, neighbor)) return None # 无可行路径 def heuristic(a, b): """欧氏距离启发函数,比曼哈顿距离在网格中更平滑""" return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** 0.5逻辑说明:open_set 是一个按 f 值排序的最小堆,每次取出 f 最小的节点扩展。对角线移动的代价设为 √2(约 1.414),直线为 1,这样路径不会出现刻意绕对角线的现象。启发函数使用欧氏距离,比曼哈顿距离在允许对角移动的地图上信息更充足,搜索节点更少。
参数说明:是否允许对角移动是一个关键开关。允许对角线时路径更短、更自然,但追捕者实际运动需要额外的避碰判断;不允许对角线时路径呈曼哈顿风格,平滑性差但更安全。常见做法是全局规划允许对角,局部避碰层再处理轨迹偏离问题。
4.2 局部速度控制:DWA 轨迹评价
A* 给出了一条全局路径,但追捕者是连续运动的,不可能逐栅格跟踪。DWA(Dynamic Window Approach)的思路是:在当前速度空间中采样一组速度组合(线速度 v、角速度 ω),对每个采样速度模拟未来一小段时间的运动轨迹,然后用评价函数选出最优速度。
def dwa_control(state, goal, obstacles, max_speed=1.0, max_yaw_rate=0.5, dt=0.1, predict_time=1.0): """ state: [x, y, yaw, v, omega] goal: 目标点 (x, y) obstacles: 动态障碍和追捕者邻居位置列表 """ best_v, best_w = 0, 0 best_score = -float('inf') for v in np.arange(0, max_speed, 0.1): for w in np.arange(-max_yaw_rate, max_yaw_rate, 0.1): # 模拟预测轨迹终点 x, y, yaw = state[0], state[1], state[2] traj = [] for _ in range(int(predict_time / dt)): x += v * np.cos(yaw) * dt y += v * np.sin(yaw) * dt yaw += w * dt traj.append((x, y)) # 评价:越接近目标越好、速度越快越好、越远离障碍越好 dist_to_goal = np.linalg.norm( np.array([x, y]) - np.array(goal)) clearance = min( np.linalg.norm(np.array([x, y]) - np.array(ob)) for ob in obstacles) score = 0.5 * (1.0 / (dist_to_goal + 0.1)) + \ 0.3 * v + 0.2 * min(clearance, 1.0) if score > best_score: best_score, best_v, best_w = score, v, w return best_v, best_wDWA 的三个评价权重需要根据场景反复调整:目标权重(0.5)决定追踪强度,速度权重(0.3)防止智能体停在原地,避障权重(0.2)保证安全。如果围捕过程中出现追捕者貼着障碍物抖动,通常是避障权重过高导致速度频繁切换;如果追捕者经常撞到障碍,则是避障权重过低。
注意这段代码做了两点简化:一是没有限制加速度变化范围(真实系统需要),二是评价函数用了简单的加权和而不是归一化分数。作为教学实现够用,工程落地时需要用动态窗口内的可达速度做归一化再加权。
4.3 多智能体协同避碰:RVO 双向避让
DWA 只能帮单个智能体躲避静态障碍物,多个追捕者同时往围捕点附近运动时,它们之间也会互相碰撞。VO(Velocity Obstacle)的思路是:把其他智能体在未来一段时间内可能占用的空间映射到速度空间,凡是被映射区域覆盖的速度都会导致碰撞,直接排除。RVO(Reciprocal Velocity Obstacle)在 VO 基础上做了改进:每个智能体只承担一半避让责任,两个相遇的智能体各自偏移一半,避免同时朝同一个方向避让导致的抖动和死锁。
def rvo_velocity(pursuer_pos, pursuer_vel, neighbor_pos, neighbor_vel, max_speed=1.0, neighbor_radius=0.5, tau=2.0): """ pursuer_pos/neighbor_pos: 追捕者和邻居位置 pursuer_vel/neighbor_vel: 速度向量 tau: 避碰时间窗,越大避让越提前 """ # 相对位置和相对速度 rel_pos = neighbor_pos - pursuer_pos rel_vel = pursuer_vel - neighbor_vel # 计算碰撞锥 dist = np.linalg.norm(rel_pos) if dist > 2 * neighbor_radius: # 距离较远,直接返回原始速度 return pursuer_vel # 在相对速度空间中找到避免碰撞的速度偏移 # 简化实现:只做垂直方向偏移 angle = np.arctan2(rel_pos[1], rel_pos[0]) avoidance_dir = np.array([-np.sin(angle), np.cos(angle)]) # 根据距离调整避让强度,越近避让越多 weight = max(0.0, 1.0 - dist / (2 * neighbor_radius)) new_vel = pursuer_vel + avoidance_dir * weight * max_speed # 限速 speed = np.linalg.norm(new_vel) if speed > max_speed: new_vel *= max_speed / speed return new_vel这段代码是一个非常简化的 RVO 实现,逻辑是:当追捕者与邻居距离小于两倍半径时,计算一个垂直于相对位置方向的避让速度,距离越近避让力度越大。完整的 RVO 还要处理相对速度是否落在碰撞锥内、多邻居时取加权平均避让速度等问题。
参数tau=2.0表示只考虑未来 2 秒内的碰撞风险,这个值设太大(>5)会导致智能体从很远就开始避让,队形松散;设太小(<0.5)则避让不及时,容易撞上。
4.4 围捕圈的形成:包围点的动态计算
围捕的核心不只是追到目标旁,而是形成包围结构。如果所有追捕者都往逃逸者当前坐标冲,结果是大家挤在一堆,没有一个方向被封锁。围捕点(Encircling Point)的计算思路是:围绕逃逸者当前朝向的 360 度范围,均匀分布 N 个点,N 是参与围捕的追捕者数量,每个追捕者选择距离自己最近的包围点作为目标。
def calculate_encircling_points(evader_pos, evader_heading, num_pursuers, radius): """ evader_pos: 逃逸者位置 (x, y) evader_heading: 逃逸者朝向角(弧度) num_pursuers: 参与围捕的追捕者数量 radius: 围捕圈半径 返回:num_pursuers 个围捕点位 """ points = [] for i in range(num_pursuers): # 从逃逸者朝向反方向开始均匀分布 angle = evader_heading + np.pi + 2 * np.pi * i / num_pursuers x = evader_pos[0] + radius * np.cos(angle) y = evader_pos[1] + radius * np.sin(angle) points.append((x, y)) return points这里的细节是角度偏移np.pi:逃逸者通常朝自己的前进方向逃跑,追捕者重点应该布置在逃逸者的前方和侧方,而不是后方。均匀分布加上一个 π 的偏移,保证前方有更多包围点。radius是围捕圈半径,需要根据追捕者数量和逃逸者速度动态调整——逃逸者速度快时围捕圈要放大,否则追捕者来不及合围。
5. 仿真主循环与工程落地:从算法到可运行项目
5.1 仿真主循环的设计
把分配、路径规划、DWA 和 RVO 组合起来的核心是一个固定频率的仿真循环。每一次 tick 做四件事:更新逃逸者位置(这里用固定的逃跑策略)、调用分配层给每个追捕者指定目标逃逸者、计算每个追捕者的围捕点和全局路径、用 DWA+RVO 计算实际速度并更新位置。下面是主循环的简化结构:
class PursuitSimulation: def __init__(self, env, pursuers, evaders): self.env = env self.pursuers = pursuers # 追捕者状态列表 self.evaders = evaders # 逃逸者状态列表 self.dt = 0.1 # 控制周期,单位秒 self.time = 0.0 def step(self): # 1. 更新逃逸者:简单的直线逃跑策略 for evader in self.evaders: evader.update(self.dt) # 2. 分配逃逸者给追捕者(匈牙利算法) assignments = self.assign_evaders() # 3. 每个追捕者计算围捕点,并规划全局路径 for p_idx, pursuer in enumerate(self.pursuers): evader = self.evaders[assignments[p_idx]] target_pos = self.calc_encircling_point(pursuer, evader) path = a_star(self.env, pursuer.pos, target_pos) # 4. 局部控制:DWA + RVO for p_idx, pursuer in enumerate(self.pursuers): neighbors = [p.pos for p in self.pursuers if p is not pursuer] v, w = dwa_control(pursuer.state, path_next, neighbors) rvo_vel = rvo_velocity(pursuer.pos, v, neighbors[0], ...) pursuer.update(rvo_vel, self.dt) self.time += self.dt def run(self, max_steps=1000): for _ in range(max_steps): self.step() self.visualize() if self.is_capture(): print(f"Capture at {self.time}s") break主循环的关键参数是dt。多智能体仿真的dt一般设在 0.05~0.2 秒之间:太大容易穿透障碍物,太小计算量过大。max_steps是防止围捕永远不成功时程序无限跑下去的死循环保护。
5.2 可视化:让围捕过程可观察
可视化不只是“好看”,它是调试多智能体算法最重要的工具。很多 bug 在输出数据里很难发现,但看一眼动画就明白了——比如两个追捕者互相避让导致来回摆动,或者追捕者卡在障碍物拐角处不断重规划。用 Matplotlib 做围捕动画的最小方案如下:
import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation def animate_pursuit(sim, interval=100): fig, ax = plt.subplots(figsize=(8, 8)) ax.imshow(sim.env.grid, cmap='gray_r', origin='lower') pursuer_plot, = ax.plot([], [], 'bo', markersize=8, label='Pursuers') evader_plot, = ax.plot([], [], 'rx', markersize=10, label='Evaders') def update(frame): sim.step() px = [p.pos[0] for p in sim.pursuers] py = [p.pos[1] for p in sim.pursuers] ex = [e.pos[0] for e in sim.evaders] ey = [e.pos[1] for e in sim.evaders] pursuer_plot.set_data(px, py) evader_plot.set_data(ex, ey) return pursuer_plot, evader_plot anim = FuncAnimation(fig, update, frames=200, interval=interval, blit=True) plt.legend() return anim注意update函数里直接调用sim.step(),这意味着动画的每一帧对应仿真的一步。interval=100 表示每帧 100ms,真实时间比约 1:1,方便观察速度太快的问题;调大 interval 可以减少计算量,但会让避碰行为的细节视觉上变模糊。
5.3 项目目录结构:源码 + 说明怎么组织
一套完整的“各种环境下多智能体协同围捕算法”源码包,通常按功能拆成以下目录:
pursuit_project/ ├── configs/ # 环境与算法参数 JSON 配置 │ ├── empty_room.json │ ├── static_obstacles.json │ └── dynamic_obstacles.json ├── environment/ # 环境相关 │ ├── grid_map.py │ └── dynamic_obstacle.py ├── algorithms/ # 核心算法 │ ├── assignment.py # 匈牙利算法分配 │ ├── a_star.py │ ├── dwa.py │ └── rvo.py ├── simulation/ # 仿真主循环 │ ├── pursuit_sim.py │ └── visualizer.py ├── main.py # 入口 └── README.md # 项目说明项目说明文档要写清楚的内容包括:Python 版本要求、依赖安装命令、每种配置文件的参数含义、如何切换到不同环境、如何调整追捕者数量。这看起来像是“说明书”工作,但在源码项目里,说明文档里对参数的描述直接决定了使用者能不能跑通。
6. 调参顺序与三个高频陷阱
6.1 调参顺序:先环境,再分配,最后速度控制
面对一套多智能体围捕代码,最容易犯的错是一开始就猛调 DWA 的权重。参数调优的合理顺序是:第一步把栅格分辨率调到能跑通 A*;第二步确认匈牙利分配结果合理(打印分配矩阵看有没有两个追捕者去同一个目标);第三步固定逃逸者不动,只调 DWA 让追捕者无碰撞到达围捕点;最后才是让逃逸者运动并调 RVO 的参数。
6.2 陷阱一:围捕目标点抖动导致路径频繁重规划
当追捕者接近围捕点时,如果围捕点根据逃逸者位置实时刷新,逃逸者一移动,围捕点就变化,追捕者每帧都在重新规划路径,表现为来回抖动但走不动。解决方法是对围捕点做缓存:只有当新的围捕点与当前围捕点距离超过某个阈值(比如 1 个栅格宽度)时才重新规划路径,否则沿用旧路径。
另外要给每个追捕者设定一个“到位半径”,到达围捕点附近后就切换为原地等待,不再反复追踪移动的围捕点。
6.3 陷阱二:RVO 与 DWA 步长不匹配导致避碰失效
DWA 的预测轨迹是基于dt积分的结果,而 RVO 的避让速度是一个瞬时值,两者频率不一致时会出问题。比如 DWA 每 0.1 秒计算一次,RVO 每 0.5 秒才更新一次,那么 0.5 秒内追捕者可能已经撞上邻居。
解决方案是让两者在同一个控制周期内同步更新。如果性能不允许,至少保证 RVO 的更新频率不低于 DWA,否则避碰行为始终滞后。一个更稳的做法是把 RVO 计算放在 DWA 的轨迹评价里,即对每个采样速度先检查是否与邻居产生碰撞冲突,有冲突的直接排除,这样从源头规避了频率不匹配。
6.4 陷阱三:多逃逸者时全局分配不及时
当逃逸者移动速度很快,或者场景中逃逸者数量大于追捕者数量的一半时,如果在主循环里每帧都做匈牙利分配,会产生目标频繁切换,追捕者像无头苍蝇一样朝不同方向跑,围捕效率反而下降。常见做法是:每 20~50 帧做一次分配,中间帧沿用上一次分配结果;同时增加一个判断,只有当目标逃逸者实际位置与初始分配时位置的距离差超过阈值时才触发重分配。
参数表可以参考:
| 参数 | 推荐值范围 | 说明 |
|---|---|---|
| 分配重计算频率 | 20~50 帧 | 太低追不上目标变化,太高目标抖动 |
| 围捕点更新阈值 | 1 个栅格 | 小于阈值不重算路径 |
| RVO 时间窗 tau | 1.0~3.0 | 越大避让越温和,越小越激进 |
| DWA 避障权重 | 0.2~0.4 | 太高导致路径绕行过多,太低撞墙 |
| 围捕圈半径 | 3~8 倍邻居半径 | 过小追捕者重叠,过大封不住出口 |
最后一组值得单独说的是围捕圈半径。它不是一个静态值,在有明确逃逸方向的场景里,追捕者应该根据逃逸者速度实时调整:逃逸者朝东跑,东侧围捕点半径缩小,西侧半径放大,形成不对称包围圈。实现上只需要在calculate_encircling_points里引入一个按角度伸缩的系数即可:radius_i = base_radius * (1.0 - k * dot(direction_i, evader_velocity_normalized)),k 在 0.2~0.5 之间取值。逃逸者速度越大,前方围捕点越收缩,追捕者越早完成拦截。
本文还有配套的精品资源,点击获取