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
相关产品推荐
相关产品推荐

