简介这是一份面向计算机专业课程设计、期末大作业及项目实战的多智能体协同围捕算法Python实现适合需要完成高分毕设或想系统学习多智能体规划、强化学习与机器人仿真控制的学习者。项目以Voronoi剖分与MADDPG为核心方案覆盖单出口、多出口、凸多边形环境、多智能体协作围捕等典型实验场景同时给出单双智能体运动控制、碰撞检测、共线判定、围捕效果可视化等配套代码可直接运行调试并结合项目说明与README梳理完整算法流程。资源共14个文件包含13个Python源码与1个Markdown说明文档压缩包仅45KB但模块划分清晰涉及区域划分、多智能体训练、出口决策、避障判断与Gazebo仿真联动等便于按需复用和二次开发。目前已有174人学习下载适合正在完成课程设计、期末大作业或希望积累多智能体协同算法实战经验的中级学习者。1. 多智能体协同围捕算法为什么“围得住”比“追得快”更有工程价值提到多智能体协同围捕算法的 Python 实现很多人的第一反应是让机器人或者游戏 NPC 去“追”目标。实际做下来你会发现问题恰恰相反单个智能体追一个会转弯的目标基本追不上真正有效的策略是提前在目标四周形成包围圈用协同把目标的逃逸方向封死。这也是“各种环境下多智能体协同围捕算法的实现 python 源码项目说明高分项目”这类题目在课程设计和毕业设计里常被选中的原因——它有明确的问题定义、可见的仿真效果又能拆成环境建模、任务分配、运动控制三个独立模块来写项目说明。适合的人群包括正在做多机器人集群仿真、游戏 AI 行为树、无人机围堵任务或者单纯想拿一套完整源码快速跑通多智能体流程的 Python 开发者。标题里的“各种环境”落到代码层面其实是四类变化静态障碍、目标变速、局部观测、多目标。理解了这四类你就能把一套核心算法扩成多个实验场景。2. 围捕问题的数学模型围捕半径、出生点与围捕点分配2.1 把追逃问题转成包围问题三个核心参数在设计围绕围捕算法之前必须先明确“什么算围捕成功”。单智能体追击里成功条件很常见追捕者与目标距离小于捕获半径即成功。多智能体围捕则要多加一个约束——不只是距离近还要保证目标找不到足够大的逃逸间隙。一个可复现的度量方法是以目标为圆心计算每个智能体的极角相邻智能体之间形成角度空隙取最大空隙角作为“逃逸开口”。当目标到每个智能体的距离都小于捕获半径且最大空隙角小于给定阈值时判定为围捕成功。这样一来算法要调的核心参数就清楚了捕获半径 R_c、最大允许空隙角 θ_enc、虚拟围捕半径 R_enc。R_enc 是智能体围绕目标展开理想圆周的半径取值通常比 R_c 大 1.5 到 3 倍。如果 R_enc 太大包围圈松散目标很容易从空隙中溜走如果太小智能体彼此挤在一起内部排斥力会让队形扭曲。另一个容易被忽视的速度约束单智能体要求追捕者速度必须大于目标速度但围捕场景不严格需要只要整体速度不比目标慢太多即可因为围捕点分配会提前截住目标去路。参数默认值建议范围作用N53~8参与围捕的智能体数量v_p / v_e1.61.2~2.5追捕者与目标最大速度比值R_enc4.02.5~6.0虚拟围捕半径R_dead0.350.2~0.6排斥力死区半径dt0.030.01~0.05仿真步长θ_enc60°45°~90°允许的最大逃逸空隙角2.2 用 Python 搭建仿真环境障碍、边界与随机种子我习惯把仿真环境单独抽成一个类这样后面做“各种环境”的实验就不用反复改动主逻辑。环境负责三件事生成随机障碍、判定点是否在边界内、维护随机种子。随机种子特别重要后面调参时如果你不固定种子每次跑出的障碍布局都不一样完全没法判断是参数改善还是运气变好。这属于调试时最基础的血泪经验。环境代码这样写就够用import numpy as np class Env2D: def __init__(self, seed42, size20.0, obstacle_num8): self.rng np.random.default_rng(seed) self.size size self.obstacle_num obstacle_num self.obstacles [] for _ in range(obstacle_num): r self.rng.uniform(0.6, 1.6) x self.rng.uniform(-size/2 2.5, size/2 - 2.5) y self.rng.uniform(-size/2 2.5, size/2 - 2.5) self.obstacles.append(np.array([x, y, r])) def in_bounds(self, pos): margin 0.5 return (abs(pos[0]) self.size/2 - margin and abs(pos[1]) self.size/2 - margin)逻辑说明障碍生成范围刻意避开边界附近 2.5 个单位避免障碍与环境边界重叠导致智能体出生即撞墙。in_bounds里的 margin 是边界软约束缓冲带给后面的边界排斥力留出处理空间。随机种子通过numpy.random.default_rng(seed)创建比旧式np.random.seed()更适合多实验并行因为每个环境实例拥有独立的随机流。2.3 围捕点分配匈牙利算法与最小化总距离围捕点分配是“协同”二字的关键。常见做法是以目标当前位置为圆心在虚拟围捕半径 R_enc 上均匀生成 N 个候选点再用匈牙利算法把 N 个智能体匹配到 N 个点上目标是让总移动距离最小。这一步在 Python 里直接调用scipy.optimize.linear_sum_assignment即可不需要自己写增广路径算法。from scipy.optimize import linear_sum_assignment def assign_capture_points(agent_positions, target_pos, R_enc): n len(agent_positions) angles np.linspace(0.0, 2.0 * np.pi, n, endpointFalse) capture_points np.array([ [target_pos[0] R_enc * np.cos(a), target_pos[1] R_enc * np.sin(a)] for a in angles ]) costs np.array([ [np.linalg.norm(ag - pt) for pt in capture_points] for ag in agent_positions ]) row_idx, col_idx linear_sum_assignment(costs) return capture_points[col_idx]逻辑说明linear_sum_assignment返回的是行索引与列索引的最优配对capture_points[col_idx]按智能体顺序返回配好的围捕点。这里有一个坑均匀生成围捕点时第一个点朝向固定角度 0°如果目标旁边恰好有障碍会导致某个围捕点生成在障碍内部。我通常会在分配后加一个检查把所有距离障碍过近的围捕点整体旋转一个角度重新分配直到所有点可通行。3. 多智能体协同围捕算法的核心源码从控制力到调度循环3.1 控制力分解目标吸引力、群体排斥力与边界力围捕点分配完成后每个智能体其实都在朝自己的目标点移动。要让队形看起来自然最简单的控制方案是“势力场法”也是多智能体系统协同群集运动控制里最经典的做法——把控制量拆成三个力的叠加朝向围捕点的吸引力、智能体之间的排斥力、环境边界排斥力。这和 Reynolds 集群规则里的“分离、对齐、聚合”本质是同一套思想只是把聚合目标换成了动态围捕点。def compute_forces(agent, capture_point, agents, env, cfg): force np.zeros(2) # 吸引力指向当前围捕点 to_target capture_point - agent.pos dist_pt np.linalg.norm(to_target) if dist_pt 1e-6: force cfg[k_att] * to_target / dist_pt # 排斥力智能体之间保持安全距离 for other in agents: if other.id agent.id: continue diff agent.pos - other.pos dist np.linalg.norm(diff) if cfg[R_dead] dist cfg[R_safe]: force cfg[k_rep] * diff / (dist * dist 1e-6) # 边界软约束力 margin 1.0 limit env.size / 2 - margin for axis in range(2): if agent.pos[axis] limit: force[axis] - cfg[k_bound] * (agent.pos[axis] - limit) elif agent.pos[axis] -limit: force[axis] - cfg[k_bound] * (agent.pos[axis] limit) return force逻辑说明吸引力只给了方向没有给距离比例因为围捕点距离远时应该全速赶路接近时靠速度限制自然减速。排斥力的分母带了平方项模拟距离越近推力越强的物理直觉。这里最关键的是R_dead死区参数当两个智能体距离小于R_dead时不再施加排斥力。如果不设死区近距离高增益会让两个智能体在一个仿真步长内弹开很远下一帧又被吸引回来产生高频抖动这是典型的调参翻车现场。3.2 目标速度预测让围捕点“追着跑”而不守株待兔如果围捕点的圆心始终取目标当前位置目标一移动整个包围圈就要重新分配智能体疲于奔命。更高效的做法是用目标速度做简单一阶预测围捕圆心取目标当前位置加上目标速度乘以前瞻时间。这个前瞻时间要根据目标速度动态调整目标跑得快时加大前瞻目标减速时缩小。class TargetState: def __init__(self, pos, vel): self.pos np.array(pos, dtypefloat) self.vel np.array(vel, dtypefloat) def predict(self, lead_time): return self.pos self.vel * lead_time def compute_lead_time(target, agents): avg_dist np.mean([np.linalg.norm(a.pos - target.pos) for a in agents]) base_lead 0.4 speed_factor np.linalg.norm(target.vel) / 2.0 return base_lead speed_factor * 0.3 avg_dist * 0.01逻辑说明这里的前瞻时间不是固定值而是动态计算的。平均距离越大说明包围圈越松散需要更靠前的围捕圆心来提前截住目标。目标速度越快前瞻时间也越大。实际调试中发现基础前瞻 0.4 秒配合速度因子 0.3 秒在大多数二维仿真场景里都能得到比较稳定的包围效果。l时间窗口过长围捕点会冲到目标前方很远出现智能体集体跑过头的情况。3.3 帧循环里的时序感知→分配→控制→判定整个围捕源码的主循环可以整理成四个阶段。感知阶段更新每个智能体对目标位置的观测分配阶段按需重新计算围捕点控制阶段计算合力并更新位置判定阶段检查围捕是否成功或超时。分配不需要每帧执行因为围捕点频繁切换反而会导致队形不稳我一般每 10 帧或当目标移动距离超过半个围捕半径时才重新分配。def run_capture_simulation(agents, target, env, cfg, max_frames2000): frame 0 last_reassign -100 assign_interval int(cfg.get(assign_interval, 10)) history [] while frame max_frames: if frame - last_reassign assign_interval: lead_time compute_lead_time(target, agents) center target.predict(lead_time) capture_points assign_capture_points( [a.pos for a in agents], center, cfg[R_enc]) last_reassign frame forces [ compute_forces(ag, capture_points[i], agents, env, cfg) for i, ag in enumerate(agents) ] for i, ag in enumerate(agents): ag.update(forces[i], cfg[dt]) target.update(agents, cfg[dt]) if check_capture_success(agents, target, cfg): return frame, True history.append({ frame: frame, agents: [a.pos.copy() for a in agents], target: target.pos.copy(), }) frame 1 return frame, False逻辑说明target.update里面实现目标的逃逸策略通常会让目标朝当前最大角度空隙的中点移动模拟一个会找缺口逃跑的聪明目标。check_capture_success就是第 2 章提到的角度空隙判定。把history存下来是为了最后可视化重放有了这个列表你就能在 matplotlib 里逐帧绘制不用重新跑一遍仿真。4. 不同环境下算法适配障碍、变速目标与局部观测4.1 静态障碍下的绕行策略障碍膨胀与切向力“各种环境”里被问得最多的是静态障碍。纯势力场法有一个原生缺陷智能体靠近障碍时排斥力把智能体推开但目标在障碍正后方时围捕点穿过了障碍智能体被夹在障碍和围捕点之间来回振荡。处理办法有两个一是把障碍物半径膨胀一个安全量二是给势力场加一个沿障碍切线的分量。def obstacle_avoidance_force(agent, env, cfg): force np.zeros(2) for obs in env.obstacles: ox, oy, radius obs diff agent.pos - obs[:2] dist np.linalg.norm(diff) safe_r radius cfg[obstacle_margin] if dist safe_r: # 径向排斥方向是远离障碍中心 if dist 1e-6: force cfg[k_obs] * (safe_r - dist) * diff / dist # 切向力让智能体沿障碍边缘滑过去 tangent np.array([-diff[1], diff[0]]) force cfg[k_tangent] * tangent / (np.linalg.norm(tangent) 1e-6) return force逻辑说明径向排斥力的强度用(safe_r - dist)加权靠近障碍时推力线性增大避免瞬间跳飞。切向力的作用很微妙它不直接把智能体推离而是引导智能体绕障碍边缘滑动配合吸引力就能形成绕行路径。障碍膨胀量obstacle_margin我一般取 0.15 到 0.25太大等于把可通行区域大幅压缩太小则智能体仍然会蹭到障碍边缘。4.2 目标突然加速或急停放宽预测窗口与复位围捕半径目标的行为模型如果是随机速度突变基础预测策略会失效。比如目标原本以 1.0 速度匀速移动下一帧突然加速到 2.5那围捕出发点的位置可能落后目标一大截。检测速度突变不能用瞬时速度差因为目标速度本身就有波动。常见做法是保存目标最近 5 帧的速度序列计算标准差标准差超过设定阈值时认为发生突变。class AdaptiveTarget(TargetState): def __init__(self, pos, vel): super().__init__(pos, vel) self.vel_history [] def update(self, agents, dt, cfg): escape_dir compute_escape_direction(agents, self.pos) self.vel escape_dir * self.speed self.vel_history.append(self.vel.copy()) if len(self.vel_history) 5: self.vel_history.pop(0) speed_var np.std([v[0] for v in self.vel_history]) if speed_var cfg[vel_change_threshold]: self.speed np.clip(self.speed * 1.3, cfg[vmin], cfg[vmax]) else: self.speed np.clip(self.speed, cfg[vmin], cfg[vmax]) self.pos self.vel * dt逻辑说明这里做了一个简化速度突变时目标速度放大 1.3 倍模拟目标被逼急后突然冲刺。配套的围捕侧改法是提高分配频率检测到目标速度突变后强制下一帧重新分配围捕点并把围捕半径从 4.0 临时缩小到 3.0。收紧包围圈可以抵消目标冲刺带来的逃脱风险这个“检测异常 → 重置关键参数”的思路很适合写进项目说明它展示了算法适应不同环境的能力。4.3 局部观测条件用距离与方位角估计目标状态更接近真实落地的环境设定是局部观测智能体无法直接读取目标全局坐标只能拿到相对距离和方位角。这种情况下的常见做法是给每个智能体配一个简单估计器用自身位置加上距离和角度的观测反推目标位置。def estimate_target_pos(agent_pos, measured_dist, measured_angle): return np.array([ agent_pos[0] measured_dist * np.cos(measured_angle), agent_pos[1] measured_dist * np.sin(measured_angle) ])逻辑说明多个智能体对同一个目标分别估计会得到多个位置候选。实际流程中我会把这些候选做加权平均权重取观测质量的倒数简单理解就是离目标越近的智能体估计结果越可信。这里需要注意一个坑如果所有智能体都在目标同一侧估计结果会在目标切向方向产生系统性偏移包围圈整体偏向一边。缓解办法是让智能体在移动过程中分担部分观测更新而不是各自独立估计。局部观测模式下围捕点分配频率要比全局观测更高我一般每 5 帧重分配一次因为目标位置的估计噪声会不断累积。4.4 多目标场景按密度聚类后分组围捕多目标场景的难点不是让智能体去追多个目标而是如何把智能体分组。最省事的做法是 k-means 聚类把所有目标位置聚成 K 簇再按每个簇内目标数量分配智能体数量。簇离得越远、目标越分散需要的智能体越多。分配原则很简单每组智能体数量与目标数量成正比。from sklearn.cluster import KMeans def assign_agent_groups(agent_positions, target_positions, n_groups): kmeans KMeans(n_clustersn_groups, random_state42).fit(target_positions) labels kmeans.labels_ group_ids np.zeros(len(agent_positions), dtypeint) for g in range(n_groups): target_count np.sum(labels g) total len(agent_positions) ratio target_count / len(target_positions) group_ids[int(g * len(agent_positions) / n_groups):] g return group_ids, kmeans.cluster_centers_逻辑说明代码里的group_ids是给每个智能体打分组标签随后主循环里只对同组智能体执行围捕点分配。这个写法有一个问题如果某组智能体被数量不足填满围捕圈会开口。项目说明里我会建议在分组后检查每组智能体数量是否不少于 3不足则从其他组借调距离最近的智能体。多目标场景成功率的统计口径也和单目标不同通常要求所有目标都被捕才算成功这一个条件会直接影响调参方向。5. 避坑排查智能体抖动、卡障碍与模拟速度慢的 7 个经典坑5.1 智能体直接飞出地图边界现象仿真跑到几百帧后某个智能体的坐标突然超出边界随后整个队伍失控。原因控制力里只有吸引和排斥没有边界约束当目标逃到边界附近时围捕点生成在边界外智能体一路追出去。解决环境类的in_bounds只负责判定真正的约束要到compute_forces里加边界软力。我见过不少源码直接把边界处理放在位置更新后硬截断这样会破坏力的连续性造成位置跳变软边界力是更稳的解法。5.2 智能体在高增益下高频抖动现象智能体轨迹呈锯齿状每帧位置都在小幅反复横跳画面看起来像筛糠。原因控制力增益k_att太大或者仿真步长 dt 太大。吸引力是恒定方向力位置更新后目标点相对位置变化下一帧力方向反转就形成振荡。解决优先调小k_att从 2.0 降到 1.2再把 dt 从 0.05 降到 0.03。如果仍然抖动检查排斥力死区R_dead是否设置近距排斥无死区是最容易产生高频抖动的设计。5.3 智能体重叠与穿模现象两个智能体的位置距离几乎为 0甚至完全重合。原因斥力公式diff / dist^2在距离极小时会产生巨大推力但速度钳制限制了单帧位移如果速度过大仍然会一步跨过安全距离。解决除了斥力之外加一个显式碰撞修正位置更新后检查所有智能体对如果距离小于安全直径则强制拉开。def resolve_overlap(agents, min_dist0.6): for i in range(len(agents)): for j in range(i 1, len(agents)): diff agents[j].pos - agents[i].pos dist np.linalg.norm(diff) if dist min_dist and dist 1e-6: push (min_dist - dist) / 2 agents[i].pos - diff / dist * push agents[j].pos diff / dist * push逻辑说明这段修正要把“碰撞修复”和“斥力防止碰撞”区别开。斥力是预防性的碰撞修复是事后补救。两者配合才能保证智能体在快速移动时不重叠。min_dist取 0.6 是因为智能体本身的可视半径我设为 0.4留一点视觉余量。5.4 围捕点分配严重不平衡现象智能体整体偏在目标一侧另一侧空隙很大但系统不认为围捕失败因为距离条件满足。原因匈牙利算法优化的是“总距离最小”可能会为了节省总路程让两个智能体同时留在左侧右侧空出来。解决改成“瓶颈分配”即最小化最大分配距离或者干脆在生成围捕点时优先在最大角度空隙处放一个点然后再均匀排布其余点。def build_capture_points_with_priority(agents, target_pos, R_enc): angles [] n len(agents) gap_center compute_max_gap_center(agents, target_pos) angles.append(gap_center) for k in range(n - 1): angles.append(gap_center 2 * np.pi * (k 1) / n) return np.array([ [target_pos[0] R_enc * np.cos(a), target_pos[1] R_enc * np.sin(a)] for a in angles ])逻辑说明compute_max_gap_center算了当前目标周围最大的逃逸空隙的中心角度把第一个围捕点放在那里其他点按圆周方向均匀补齐。这样即使智能体数量少也能优先封住最危险的出口。这个修正看似简单但对围捕成功率的提升非常明显属于整个源码里性价比最高的一处改动。5.5 目标躲到障碍背后导致“集体围观”现象目标静止在障碍物正后方智能体因排斥力无法靠近全部停在障碍前一段距离外围成一个半圆但围捕判定一直不成功。原因势力场法没有路径规划能力排斥力把智能体阻在了障碍外围捕点又穿过障碍生成在目标侧。解决除了前面说的切向力还要在检测到围捕点与智能体之间连线段穿越障碍时临时生成一个绕行中转点。def create_waypoint_if_blocked(agent_pos, capture_point, env): for obs in env.obstacles: center obs[:2] radius obs[2] if segment_blocked(agent_pos, capture_point, center, radius): vec capture_point - center tang np.array([-vec[1], vec[0]]) return center tang * radius * 1.5 return capture_point逻辑说明segment_blocked用点到直线距离判断线段是否穿过圆形障碍。如果穿过就取障碍圆心到围捕点方向的切线方向生成一个中转点。智能体会先朝中转点移动绕过障碍后再转向真正的围捕点。这种方式比完整 A* 路径规划轻量又比纯势场可靠适合课程设计与源码演示。5.6 模拟帧率低智能体数量一多就卡现象智能体超过 10 个后每帧耗时明显上升超过 20 个几乎跑不动。原因势力场计算是 O(n²) 的每个智能体要遍历所有其他智能体算排斥力还有围捕点分配每帧都在跑匈牙利算法。解决两点优化。排斥力计算用网格哈希只查相邻网格单元里的智能体围捕点分配不要每帧跑设定距离变化阈值超过阈值才重新分配。def build_spatial_grid(agents, cell_size): grid {} for ag in agents: key (int(ag.pos[0] // cell_size), int(ag.pos[1] // cell_size)) grid.setdefault(key, []).append(ag) return grid逻辑说明cell_size取排斥力最大作用距离这样任意一个智能体只需要检查自己所在网格的 8 个邻居网格避开了全量遍历。这个优化在智能体数量超过 15 个时效果非常明显帧耗时能降到原来的五分之一以下。5.7 复现实验失败两次运行结果完全不一样现象同一套参数第一次成功率 80%第二次只剩 40%。原因环境障碍生成用的随机流没固定目标初始速度也是随机的导致两次实验的难度完全不同。解决环境、目标、智能体初始位置全部用同一个随机种子生成并且把初始状态保存下来。def save_experiment_state(filepath, agents, target, env): np.savez( filepath, agent_positionsnp.array([a.pos for a in agents]), agent_velocitiesnp.array([a.vel for a in agents]), target_positiontarget.pos, target_velocitytarget.vel, obstaclesnp.array(env.obstacles), seedenv.seed )逻辑说明保存初始状态后每次实验都是同一个开局参数调优才有可比性。项目说明里这一块通常写“实验可复现性设计”在评分时也是加分项。6. 验证与进阶统计围捕成功率、扫描参数与导出回放6.1 围捕成功率的统计口径围捕成功率不是跑一次看结果要做蒙特卡洛统计。我习惯固定障碍布局目标初始速度随机跑 30 到 50 次记录三个指标成功率、平均围捕时间、平均最大空隙角。统计代码很简单但统计口径必须提前定否则不同实验之间没法横向比较。def evaluate_policy(vmax, R_enc, trials30): success_count 0 time_list [] for trial in range(trials): env Env2D(seed100 trial) agents create_agents(env, n5, vmaxvmax) target create_target(env) end_frame, success run_capture_simulation( agents, target, env, {vmax: vmax, R_enc: R_enc}) if success: success_count 1 time_list.append(end_frame) return success_count / trials, np.mean(time_list)逻辑说明注意每个 trial 的种子不固定但可复现用100 trial来保证 30 次试验的初始环境互不相同但每次运行结果一致。成功率低于 60% 时不要急着调参数先检查围捕点的角度覆盖条件再检查速度配置。6.2 参数扫描用网格搜索找稳定区间调参过程中我用得最多的方法是网格扫描原理上和你写 python 量化交易策略代码时做参数回测一样把候选参数组合全部跑一遍用表格对比表现。常见的扫法是对R_enc和v_p做双层循环每组参数跑 20 次试验输出成功率和平均围捕时间。import itertools def grid_search(): candidates { R_enc: [2.5, 3.0, 4.0, 5.0], vmax: [1.2, 1.6, 2.0], } results [] for R_enc, vmax in itertools.product( candidates[R_enc], candidates[vmax]): rate, avg_time evaluate_policy(vmax, R_enc, trials20) results.append((R_enc, vmax, rate, avg_time)) print(fR_enc{R_enc}, vmax{vmax}, 成功率{rate:.2f}, 平均时间{avg_time:.1f}) return results逻辑说明网格扫描的代价是智能体数量越多越慢实时调整不要直接跑全量组合。可以先跑 5 次试验快速筛掉明显不行的区间再对稳定区间跑 20 次做最终确认。实际经验是R_enc在 3.0 到 4.0 区间、vmax在目标速度的 1.5 到 2.0 倍时成功率最高超出这个范围稳定性下降很快。6.3 可视化重放与状态导出仿真结束后把历史轨迹画出来是判断算法是否真的“协同”最直观的方式。主循环里保存的history列表包含每一帧智能体和目标的位置我用 matplotlib 逐帧绘制成动画也经常导出成npz文件给别人复现。导出格式和前面save_experiment_state保持一致只追加一维索引即可。一个我强烈建议养成的验证习惯在写项目说明时把智能体轨迹重叠图和角度空隙变化图画出来。图中能看到智能体是否真的形成了包围圈还是各自乱撞这比几十行文字说明更有说服力。我自己调试时会在每 50 帧打印一次当前最大角度空隙配合可视化一起看遇到成功率波动大时能快速定位到底是在哪个阶段出了缺口。希望这套源码思路和避坑经验能帮到你让你跑起多智能体协同围捕仿真时少走一些我走过的弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?