Python类中.append()失效引发IndexError,Pygame流体模拟求助
Pygame流体模拟:修复IndexError与碰撞检测逻辑问题
错误根源分析
check_collisions函数核心逻辑错误
- 原跳过条件
self.x < self.x + distance_to_ignore * (...)永远为真,导致所有粒子都被跳过,dx/dy列表始终为空,后续move_if_colliding访问self.dx[i]直接触发IndexError。 - 循环内第一次判断就直接
return,只会检查第一个粒子,无法遍历所有粒子,也无法收集完整的dx/dy列表。 - 未排除粒子自身(自己与自己碰撞的无效情况)。
- 原跳过条件
move_if_colliding函数逻辑缺陷
- 点积计算错误:正确的向量点积应为
(dx1 * dx2) + (dy1 * dy2),而非原代码的(x差)*(y差)。 - 未处理
overlap=0的无碰撞场景,会触发除以0错误。 - 角度计算冗余,且未直接使用弧度值(
math.cos/math.sin需要弧度输入,无需转角度)。
- 点积计算错误:正确的向量点积应为
其他潜在问题
start函数中gravity_temp未初始化,第一次运行会触发AttributeError。draw函数中坐标计算错误,self.x - (self.size/WIDTH)会导致粒子位置无意义偏移。
修复方案与完整代码
以下是修复后的完整代码,附带关键修改注释:
import pygame, random, math # Pygame基础配置 WIDTH = 750 HEIGHT = 750 FPS = 60 # 初始化Pygame pygame.init() SCREEN = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("2D Fluid Simulation") # 颜色定义 BLACK = (0, 0, 0) WHITE = (255, 255, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) # 模拟参数 GRAVITY = 0.918 amt_white_particles = 100 amt_red_particles = 50 amt_green_particles = 10 collision_check_depth = 5 distance_to_ignore = 3 class Particle: def __init__(self, start_x, start_y, size, color): self.start_x = start_x self.start_y = start_y self.x, self.y = self.start_x, self.start_y self.dx, self.dy = [], [] # 存储与其他粒子的x/y距离向量 self.x_speed, self.y_speed = 0, 0 self.size = size self.color = color self.mass = size ** 2 # 质量与粒子面积成正比,符合物理逻辑 self.gravity_temp = GRAVITY # 初始化重力临时变量 def check_collisions(self, particles_list): self.dx.clear() self.dy.clear() max_overlap = 0 collisions = [] for particle in particles_list: # 跳过粒子自身 if particle is self: continue dx = self.x - particle.x dy = self.y - particle.y distance = math.hypot(dx, dy) # 高效计算两点距离 min_distance = (self.size + particle.size) / 2 # 跳过距离过远的粒子,优化性能 if distance > min_distance + distance_to_ignore: continue self.dx.append(dx) self.dy.append(dy) # 计算重叠量并记录碰撞信息 if distance < min_distance: overlap = min_distance - distance if overlap > max_overlap: max_overlap = overlap collisions.append((particle, overlap, dx, dy)) return max_overlap, collisions def move_if_colliding(self, overlap, collisions): if overlap <= 0: return for particle, overlap, dx, dy in collisions: # 计算碰撞法线方向的单位向量 distance = math.hypot(dx, dy) if distance == 0: continue # 避免除以0错误 nx = dx / distance ny = dy / distance # 计算相对速度 rel_vx = particle.x_speed - self.x_speed rel_vy = particle.y_speed - self.y_speed # 计算相对速度在法线方向的分量 dot_product = rel_vx * nx + rel_vy * ny # 粒子正在分离时无需处理 if dot_product > 0: continue # 完全弹性碰撞公式计算冲量 impulse = (2 * dot_product) / (self.mass + particle.mass) self.x_speed += impulse * particle.mass * nx self.y_speed += impulse * particle.mass * ny particle.x_speed -= impulse * self.mass * nx particle.y_speed -= impulse * self.mass * ny # 分离重叠的粒子,避免卡死 separation = overlap / 2 self.x += separation * nx self.y += separation * ny particle.x -= separation * nx particle.y -= separation * ny def start(self, particles): # 地面碰撞处理,加入能量损失模拟真实弹跳 if self.y + self.size > HEIGHT: self.y_speed = -self.y_speed * 0.8 self.y = HEIGHT - self.size else: self.y_speed += self.gravity_temp # 更新粒子位置 self.x += self.x_speed self.y += self.y_speed # 多轮碰撞检测,确保粒子完全分离 for _ in range(collision_check_depth): max_overlap, collisions = self.check_collisions(particles) self.move_if_colliding(max_overlap, collisions) def draw(self): # 修复坐标计算错误,直接使用粒子中心坐标绘制 pygame.draw.circle(SCREEN, self.color, (int(self.x), int(self.y)), self.size) def main(): running = True clock = pygame.time.Clock() particles = [] # 创建白色粒子 for _ in range(amt_white_particles): particles.append(Particle( random.randint(50, WIDTH-50), random.randint(50, HEIGHT//2), 5, WHITE )) # 创建红色粒子 for _ in range(amt_red_particles): particles.append(Particle( random.randint(50, WIDTH-50), random.randint(50, HEIGHT//2), 7, RED )) # 创建绿色粒子 for _ in range(amt_green_particles): particles.append(Particle( random.randint(50, WIDTH-50), random.randint(50, HEIGHT//2), 10, GREEN )) while running: clock.tick(FPS) # 事件处理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 绘制背景 SCREEN.fill(BLACK) # 更新并绘制所有粒子 for particle in particles: particle.start(particles) particle.draw() # 更新屏幕显示 pygame.display.flip() pygame.quit() if __name__ == "__main__": main()
关键修改说明
碰撞检测逻辑重构
- 每次检测前清空
dx/dy列表,确保数据最新无累积。 - 排除粒子自身,避免无效计算。
- 使用
math.hypot高效计算距离,避免重复平方运算。 - 返回所有碰撞的详细信息,支持多粒子碰撞处理。
- 每次检测前清空
碰撞响应优化
- 采用物理弹性碰撞公式,实现更真实的粒子交互效果。
- 加入粒子分离逻辑,避免重叠卡死。
- 地面碰撞加入能量损失,模拟真实弹跳衰减。
其他细节修复
- 初始化
gravity_temp,避免未定义错误。 - 修复
draw函数坐标计算,确保粒子位置准确。 - 调整粒子初始位置,避免一开始就出界。
- 初始化
内容的提问来源于stack exchange,提问作者Enrico Bottura
相关产品推荐
相关产品推荐

