|
本帖最后由 littleblackLB 于 7-23-2025 10:17 编辑
这篇帖子,我将复盘一下在前日719RT年度派对上用势场法所实现的AI.
很荣幸能成为四组中存活时间最长的AI,同时算法高效,是O(N)的线性时间复杂度(N为障碍物).
(绿色方块为AI)
为什么选势场法?
这个方法更加自然,同时有一定的直观性,因为你可以将障碍物类比成一个个小山坡(或者是电场中的正电荷,高度为场强大小)。同时易于拓展,比如希望 AI 更倾向于前往稀疏区域,同时尽可能不靠近边界。对于这类需求,我们就可以简单的叠加一个场来实现目标。
怎么建模?
思路很简单,将所有障碍物的斥力作用于AI的合成为一个力,然后再分解为x, y两个分量即可
令 \(|d_i|\) 为 AI 到 i 编号障碍物的欧氏距离,\(d_{0i}\)为到 i 编号障碍物的单位方向矢量, \(f_i\)为 AI 受到到 i 编号障碍物的斥力,下标为x或y的变量均为标量,k 为基础斥力系数
\(|f_i| = \frac{k}{|d_i|}\)
\(tan \theta = \frac{d_y}{d_x}\)
\(f = \sum_{i=1}^{n}|f_i| \cdot d_{0i}\)
\(f_x = \sum_{i=0}^{n}|f_i| \cdot cos\theta = \sum_{i=0}^{n}|f_i| \cdot \frac{d_x}{\sqrt{d_x^2+d_y^2}}\)
\(f_y = \sum_{i=0}^{n}|f_i| \cdot sin\theta = \sum_{i=0}^{n}|f_i| \cdot \frac{d_y}{\sqrt{d_x^2+d_y^2}}\)
- for obs in boxList:
- ox1, oy1 = obs.rect.left, obs.rect.top
- ox2, oy2 = obs.rect.right, obs.rect.bottom
- cx = min(max(bx, ox1), ox2)
- cy = min(max(by, oy1), oy2)
- dx, dy = bx - cx , by - cy
- dist_sq = max(dx*dx + dy*dy, 1.0)
- strength = k_obs / dist_sq
- inv_d = 1.0 / math.sqrt(dist_sq)
- fx += strength * (dx * inv_d)
- fy += strength * (dy * inv_d)
复制代码 于是问题出现了,当 AI 跑到四边后便会卡在那里不出去,我们便可以给四边设置一个"屏障场",让 AI 尽可能不靠近边界
- fx += k_border / max(dist_left*dist_left, 1.0)
- fx += -k_border / max(dist_right*dist_right, 1.0)
- fy += k_border / max(dist_top*dist_top, 1.0)
- fy += -k_border / max(dist_bottom*dist_bottom,1.0)
复制代码 ---
这里的 max 函数是为了防止无穷大无效化了其他斥力.
同时我给场强做了可视化(用了 Numpy 后性能好多了), 蓝 -> 红, 越红强度越大(没有渲染边界的场强),可以发现 AI 会倾向于移动至蓝色区域.
动态调参
我们注意到已经有两个可以调节的参数了,分别是 k_obs 和 k_border。由于其是超参数,所以并不简单的存在全局最优值,因此我们便可以在 AI 运行的过程中动态调节:
- def compute_dynamic_k(base_k, current_dist, d0=100.0, min_k=100.0, max_k=5000.0):
- """
- 根据飞船与最近障碍物的距离 current_dist 动态缩放斥力系数。
- - base_k: 基础斥力系数
- - current_dist: 与最近障碍物的实际距离
- - d0: 参考距离(当 current_dist == d0 时,k == base_k)
- - min_k, max_k: 系数上下界,防止过小/过大
- """
- # 比例因子:当越靠近(dist < d0),因子 >1;越远时因子 <1
- factor = d0 / max(current_dist, 1.0)
- k = base_k * factor
- # 限制在 [min_k, max_k] 范围内
- return max(min_k, min(max_k, k))
复制代码
向稀疏的地方进发! 我们可以利用高斯密度分布核来生成"密度场" - sigma = 100.0 # 核宽度
- k_sparse = 100.0 # 稀疏吸引强度系数
- for obs in boxList:
- area = obs.rect.width * obs.rect.height
- # 障碍物中心
- ox = obs.rect.centerx
- oy = obs.rect.centery
- dx = bx - ox
- dy = by - oy
- dist_sq = dx*dx + dy*dy
- # area_factor = obs.rect.width * obs.rect.height / total_area * 10 + 1
- area_factor = 1
- # 高斯核密度: exp(-dist_sq / (2*sigma^2))
- gauss = area_factor * math.exp(-dist_sq / (2 * sigma * sigma))
- # 密度梯度 = ∇(gauss) = (-dx / sigma^2 * gauss, -dy / sigma^2 * gauss)
- # 稀疏吸引 = -密度梯度
- fx += k_sparse * ( dx * gauss / (sigma*sigma) )
- fy += k_sparse * ( dy * gauss / (sigma*sigma) )
复制代码sigma:控制感知“密度”的范围,相当于你在多远距离上能“感知”障碍物。 k_sparse:决定飞船有多强的意愿走向稀疏区域。
蓝 -> 绿,越绿表面该区域越密集
感想
正因为有了 AI 的协助,我们才能在项目中实现“边用边学”的循环提升。从最初的构想到逐步实现,虽然大量代码是由 AI 生成的,但每一段代码背后所蕴含的算法思想与工程逻辑,都在实践中被不断理解和内化。
这种“即学即用、即用即精”的方式,不仅提高了效率,也让我们的思维方式发生了转变:不再畏惧复杂的技术细节,而是勇于提出问题、尝试解决,并在反馈中不断优化。
在未来,我们或许会更依赖 AI,但更重要的,是我们学会了如何与 AI 一起思考、一起成长。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
评分
-
查看全部评分
|