Pygame模拟两球弹性碰撞出现振动粘连穿模问题求解
Pygame双球弹性碰撞异常修复
核心错误点
- 碰撞检测逻辑失效:原判断要求两球中心距离严格等于两球半径之和,但小球按固定时间步长离散移动,绝大多数碰撞发生时两球已经互相重叠,甚至直接跨过接触位置,导致漏判穿透;如果刚好在重叠区间反复触发判断,又会导致速度反复反转出现振动粘连。
- 碰撞公式应用错误:直接对x、y轴速度分量分别套用一维弹性碰撞公式是错误的。二维碰撞的速度交换仅发生在两球中心连线(碰撞法线)方向,切线方向速度不受碰撞影响,直接拆分轴计算会得到完全错误的速度结果。
- 缺少碰撞位置修正:检测到碰撞时如果仅修改速度、不把已经重叠的两球推到刚好接触的位置,下一帧两球仍处于重叠状态,会重复触发碰撞判断,持续反转速度引发粘连。
- 重复处理同一碰撞:原遍历逻辑中A球检测到和B球碰撞处理一次后,B球遍历到A球时会再次处理同一次碰撞,一帧内两次修改速度导致结果完全异常。
修正逻辑
- 碰撞检测改为判断两球中心距离平方小于等于半径和的平方,覆盖重叠状态的碰撞场景,避免漏判。
- 实现标准二维弹性碰撞速度计算:先将两球速度分解到碰撞法线、切线方向,仅对法线方向速度套用一维弹性碰撞公式,切线速度保持不变,再将分量重新组合为x/y轴速度。
- 新增重叠位置修正:计算两球重叠深度,沿法线方向按质量比将两球推开到刚好接触的位置,避免重复触发碰撞。
- 优化遍历逻辑:按索引遍历球对,每对球每帧仅做一次碰撞检测处理,避免重复计算。
- 补全边界碰撞的位置修正,避免小球卡入墙体。
修正后可运行代码
import pygame import random import math class Ball: def __init__(self, x, y, vx, vy, r, m): self.x = x self.y = y self.vx = vx self.vy = vy self.r = r self.m = m def change_attribute(self, x=None, y=None, vx=None, vy=None, r=None, m=None): if x!=None: self.x = x if y!=None: self.y = y if vx!=None: self.vx = vx if vy!=None: self.vy = vy if r!=None: self.r = r if m!=None: self.m = m pygame.init() screen = pygame.display.set_mode((500, 500)) animationTimer = pygame.time.Clock() balls = [] num_balls = 2 # 初始化小球,避免初始位置重叠 for _ in range(num_balls): while True: new_r = random.randint(30, 50) new_x = random.randint(new_r, 500 - new_r) new_y = random.randint(new_r, 500 - new_r) new_vx = random.randint(-5, 5) new_vy = random.randint(-5, 5) new_m = random.randint(1, 4)*5 valid = True for ball in balls: dx = new_x - ball.x dy = new_y - ball.y if dx*dx + dy*dy < (new_r + ball.r)**2: valid = False break if valid: balls.append(Ball(new_x, new_y, new_vx, new_vy, new_r, new_m)) break running = True while running: for e in pygame.event.get(): if e.type == pygame.QUIT: running = False screen.fill((0, 0, 0)) # 处理边界碰撞+位置修正 for ball in balls: if ball.x - ball.r <= 0: ball.vx = abs(ball.vx) ball.x = ball.r if ball.x + ball.r >= 500: ball.vx = -abs(ball.vx) ball.x = 500 - ball.r if ball.y - ball.r <= 0: ball.vy = abs(ball.vy) ball.y = ball.r if ball.y + ball.r >= 500: ball.vy = -abs(ball.vy) ball.y = 500 - ball.r # 处理球与球碰撞,每对球仅处理一次 for i in range(len(balls)): ball1 = balls[i] for j in range(i+1, len(balls)): ball2 = balls[j] dx = ball2.x - ball1.x dy = ball2.y - ball1.y dist_sq = dx*dx + dy*dy r_sum = ball1.r + ball2.r if dist_sq <= r_sum * r_sum: dist = math.sqrt(dist_sq) # 处理两球完全重合的极端情况 if dist == 0: dist = 0.1 dx = 0.1 dy = 0 # 计算法线、切线方向 nx = dx / dist ny = dy / dist tx = -ny ty = nx # 速度分解到法向、切向 v1n = ball1.vx * nx + ball1.vy * ny v1t = ball1.vx * tx + ball1.vy * ty v2n = ball2.vx * nx + ball2.vy * ny v2t = ball2.vx * tx + ball2.vy * ty # 一维弹性碰撞计算新法向速度 m1 = ball1.m m2 = ball2.m v1n_new = (v1n*(m1 - m2) + 2*m2*v2n) / (m1 + m2) v2n_new = (v2n*(m2 - m1) + 2*m1*v1n) / (m1 + m2) # 转换回x/y轴速度 ball1.vx = v1n_new * nx + v1t * tx ball1.vy = v1n_new * ny + v1t * ty ball2.vx = v2n_new * nx + v2t * tx ball2.vy = v2n_new * ny + v2t * ty # 位置修正,推开重叠部分 overlap = r_sum - dist ball1.x -= overlap * nx * (m2/(m1+m2)) ball1.y -= overlap * ny * (m2/(m1+m2)) ball2.x += overlap * nx * (m1/(m1+m2)) ball2.y += overlap * ny * (m1/(m1+m2)) # 更新位置+绘制 for ball in balls: ball.change_attribute(x = ball.x+ball.vx, y = ball.y+ball.vy) pygame.draw.circle(screen, (200, 0, 0), (int(ball.x), int(ball.y)), ball.r) pygame.display.update() animationTimer.tick(100) pygame.quit()
修正后小球不会再出现穿透、粘连、振动问题,碰撞后的速度变化符合弹性碰撞物理规律。
内容的提问来源于stack exchange,提问作者okayatcp12
相关产品推荐
相关产品推荐

