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

Python PyGame粒子模拟碰撞时重叠异常问题求助

问题根因
  • 你仅对粒子和边界碰撞做了位置修正,粒子间碰撞只修改了速度,没有把重叠的粒子推开,离散帧步长下重叠状态会持续触发碰撞,导致速度计算逻辑反复执行出现异常
  • 现有碰撞检测逻辑会重复计算同一对粒子的碰撞:遍历每个粒子时都和其他所有粒子配对,一对粒子会被处理两次,速度被错误修改两次
  • 类方法调用逻辑冗余:collision作为类方法不需要使用实例调用,也不需要重复传入实例本身作为参数
修复步骤

1. 新增粒子间碰撞的位置修正逻辑

在collision方法中计算两个粒子的重叠深度,沿碰撞法向量方向按质量反比分配位移,把粒子推开到刚好不重叠的位置,逻辑和你处理边界碰撞的位置修正一致。

2. 优化碰撞检测循环

使用双层索引遍历粒子对,仅处理i<j的配对,避免同一对粒子重复计算碰撞

3. 修正类方法调用方式

统一使用Particle.collision(p1, p2)调用碰撞处理方法,避免参数冗余

修改后的完整代码
import pygame
import random
import math
import numpy as np

width, height = 700, 450
screen = pygame.display.set_mode((width, height))
particles = []
no_particles = 10
tick_speed = 60 # 可按需调整,过高的tick会增加性能消耗

class Particle:
    def __init__(self, x, y, r):
        self.r = r
        self.pos = np.array([x, y], dtype=np.float64)
        # 速度对应调低tick后可适当放大,比如到(-2,2)
        self.vel = np.array([random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5)], dtype=np.float64)
        self.acc = np.array([0, 0], dtype=np.float64)

    def display(self):
        pygame.draw.circle(screen, (227, 53, 15), (int(self.pos[0]), int(self.pos[1])), self.r, 1)

    def move(self):
        self.vel = self.vel + self.acc 
        self.pos = self.pos + self.vel
            
    def bounce(self):
        if self.pos[1] > height - self.r:
            self.pos[1] = 2*(height - self.r) - self.pos[1]
            self.vel[1] = -self.vel[1]

        elif self.pos[1] < self.r:
            self.pos[1] = 2*self.r - self.pos[1]
            self.vel[1] = -self.vel[1]

        if self.pos[0] + self.r > width:
            self.pos[0] = 2*(width - self.r) - self.pos[0]            
            self.vel[0] = -self.vel[0]
            
        elif self.pos[0] < self.r:
            self.pos[0] = 2*self.r - self.pos[0]
            self.vel[0] = -self.vel[0]
            
    @classmethod
    def collision(cls, p1, p2):
        dc = math.hypot((p1.pos[0]-p2.pos[0]), (p1.pos[1]-p2.pos[1]))
        if dc <= p1.r + p2.r:
            x1, y1 = p1.pos[0], p1.pos[1]
            x2, y2 = p2.pos[0], p2.pos[1]
            m1, m2 = p1.r**2, p2.r**2
            
            n = np.array([x2-x1, y2-y1])
            # 避免两个粒子完全重合时除以0的问题
            if np.linalg.norm(n) == 0:
                n = np.array([random.uniform(-1,1), random.uniform(-1,1)])
            un = n / np.linalg.norm(n)
            ut = np.array([-un[1], un[0]])
            
            # 新增:位置修正,推开重叠的粒子
            overlap = p1.r + p2.r - dc
            total_mass = m1 + m2
            # 按质量反比分配位移,质量大的粒子位移更小
            p1.pos -= un * overlap * (m2 / total_mass)
            p2.pos += un * overlap * (m1 / total_mass)
            
            v1 = p1.vel
            v2 = p2.vel

            v1n = np.dot(un, v1)
            v1t = np.dot(ut, v1)

            v2n = np.dot(un, v2)
            v2t = np.dot(ut, v2)

            v1n_prime_s = (v1n * (m1 - m2) + 2*m2*v2n) / (m1 + m2)
            v2n_prime_s = (v2n * (m2 - m1) + 2*m1*v1n) / (m1 + m2)
             
            v1n_prime = v1n_prime_s * un
            v1t_prime = v1t * ut

            v2n_prime = v2n_prime_s * un
            v2t_prime = v2t * ut
            
            u1 = v1n_prime + v1t_prime 
            u2 = v2n_prime + v2t_prime
            p1.vel = u1
            p2.vel = u2

            
while len(particles) < no_particles:
    r = random.randint(10, 20)
    x = random.randint(r, width-r)
    y = random.randint(r, height-r)
    collide = False
    for particle in particles:
        d = (particle.pos[0] - x)**2 + (particle.pos[1] - y)**2
        if d < (r + particle.r)**2:
            collide = True
            break
        
    if not collide:
        particles.append(Particle(x, y, r))
        
          
running = True
clock = pygame.time.Clock()
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            
    screen.fill((255, 255, 255))
    
    # 先统一处理所有粒子的移动和边界碰撞
    for particle in particles:
        particle.move()
        particle.bounce()
    
    # 统一处理粒子间碰撞,避免重复计算
    for i in range(len(particles)):
        p1 = particles[i]
        for j in range(i+1, len(particles)):
            p2 = particles[j]
            Particle.collision(p1, p2)
    
    # 统一渲染
    for particle in particles:
        particle.display()

    pygame.display.flip()
    clock.tick(tick_speed)


pygame.quit()
quit()
额外优化点
  • 新增了粒子完全重合时的法向量随机生成逻辑,避免除以0报错
  • 修复了初始化粒子时半径传入错误的问题(原来初始化时随机生成了两次半径,第一次的r没用上)
  • 给numpy数组指定了float64类型,避免精度误差导致的异常

内容的提问来源于stack exchange,提问作者Nick_623

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 22:42:00