You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pygame模拟两球弹性碰撞出现振动粘连穿模问题求解

Pygame双球弹性碰撞异常修复

核心错误点

  • 碰撞检测逻辑失效:原判断要求两球中心距离严格等于两球半径之和,但小球按固定时间步长离散移动,绝大多数碰撞发生时两球已经互相重叠,甚至直接跨过接触位置,导致漏判穿透;如果刚好在重叠区间反复触发判断,又会导致速度反复反转出现振动粘连。
  • 碰撞公式应用错误:直接对x、y轴速度分量分别套用一维弹性碰撞公式是错误的。二维碰撞的速度交换仅发生在两球中心连线(碰撞法线)方向,切线方向速度不受碰撞影响,直接拆分轴计算会得到完全错误的速度结果。
  • 缺少碰撞位置修正:检测到碰撞时如果仅修改速度、不把已经重叠的两球推到刚好接触的位置,下一帧两球仍处于重叠状态,会重复触发碰撞判断,持续反转速度引发粘连。
  • 重复处理同一碰撞:原遍历逻辑中A球检测到和B球碰撞处理一次后,B球遍历到A球时会再次处理同一次碰撞,一帧内两次修改速度导致结果完全异常。

修正逻辑

  1. 碰撞检测改为判断两球中心距离平方小于等于半径和的平方,覆盖重叠状态的碰撞场景,避免漏判。
  2. 实现标准二维弹性碰撞速度计算:先将两球速度分解到碰撞法线、切线方向,仅对法线方向速度套用一维弹性碰撞公式,切线速度保持不变,再将分量重新组合为x/y轴速度。
  3. 新增重叠位置修正:计算两球重叠深度,沿法线方向按质量比将两球推开到刚好接触的位置,避免重复触发碰撞。
  4. 优化遍历逻辑:按索引遍历球对,每对球每帧仅做一次碰撞检测处理,避免重复计算。
  5. 补全边界碰撞的位置修正,避免小球卡入墙体。

修正后可运行代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 18:06:19