基于Pygame的粒子引力模拟异常:粒子集体左移问题排查
修正Pygame粒子引力模拟的异常移动问题
你的粒子模拟程序出现集体左移而非中心聚集的问题,根源在于速度初始化、角度计算、位置更新逻辑以及时间步长处理这几处错误,以下是具体修正方案:
核心错误点修正
- 速度初始化错误:速度是二维向量(x、y方向分量),原代码用标量
0初始化,导致后续速度计算完全失效,需改为[0, 0]。 - 角度参数顺序错误:
math.atan2的正确参数是(y差值, x差值),原代码写反了,导致角度计算完全错误。 - 位置更新逻辑错误:更新粒子y坐标时误用了x坐标的初始值,且角度引用混乱,需统一对应粒子的角度。
- 未引入时间步长:引力模拟需要基于时间步长(
dt)更新速度和位置,否则运动状态会脱离实际物理规律。 - 列表拷贝错误:原代码对粒子位置和速度的浅拷贝会导致计算过程中原始数据被篡改,需使用深拷贝。
- 睡眠逻辑错误:原代码的睡眠时长计算会出现负数,需修正时间差的计算顺序。
修正后的完整代码
import pygame import math import time particleMass = 1 res = 10 fps = 60 G = 6.674 * 10 ** -11 multi = 10 ** 7.5 size = (700, 700) screen = pygame.display.set_mode(size) particles = [] velocities = [] red = (255, 0, 0) black = (0, 0, 0) x = 0 y = 0 # 初始化粒子位置和二维速度向量 for _ in range(res ** 2): particles.append([-size[0] / 2 + x * (size[0] / res), -size[1] / 2 + y * (size[1] / res)]) velocities.append([0, 0]) # 改为二维速度向量 x += 1 if x == res: x = 0 y += 1 def norm(pos): return [pos[0] + size[0] / 2, pos[1] + size[1] / 2] def update_forces(pos1, pos2, vel1, vel2, dt): dx = pos1[0] - pos2[0] dy = pos1[1] - pos2[1] dist_sq = dx ** 2 + dy ** 2 dist = math.sqrt(dist_sq) # 避免除以0 if dist < 1: return pos1, pos2, vel1, vel2 # 计算引力大小 force = (G * particleMass ** 2) / dist_sq # 计算加速度分量(F=ma,a=F/m) acc_x = (-force * dx / dist) * multi / particleMass acc_y = (-force * dy / dist) * multi / particleMass # 更新速度 new_vel1_x = vel1[0] + acc_x * dt new_vel1_y = vel1[1] + acc_y * dt new_vel2_x = vel2[0] - acc_x * dt # 作用力与反作用力,加速度方向相反 new_vel2_y = vel2[1] - acc_y * dt # 更新位置 new_pos1_x = pos1[0] + new_vel1_x * dt new_pos1_y = pos1[1] + new_vel1_y * dt new_pos2_x = pos2[0] + new_vel2_x * dt new_pos2_y = pos2[1] + new_vel2_y * dt return [new_pos1_x, new_pos1_y], [new_pos2_x, new_pos2_y], [new_vel1_x, new_vel1_y], [new_vel2_x, new_vel2_y] def calculatePoses(poses, velo, dt): # 深拷贝位置和速度,避免计算中修改原始数据 resPoses = [[p[0], p[1]] for p in poses] resVelo = [[v[0], v[1]] for v in velo] for i in range(len(resPoses)): for j in range(i): resPoses[i], resPoses[j], resVelo[i], resVelo[j] = update_forces(resPoses[i], resPoses[j], resVelo[i], resVelo[j], dt) return resPoses, resVelo def clear(color): pygame.draw.rect(screen, color, pygame.Rect(0, 0, size[0], size[1])) def draw(lst): for pos in lst: pygame.draw.rect(screen, red, pygame.Rect(norm(pos)[0], norm(pos)[1], size[0]/res/5, size[1]/res/5)) while True: start = time.time() clear(black) # 计算时间步长 dt = min(1/fps, time.time() - start) particles, velocities = calculatePoses(particles, velocities, dt) draw(particles) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() # 控制帧率 elapsed = time.time() - start if elapsed < 1/fps: time.sleep(1/fps - elapsed)
修正说明
- 改用二维速度向量存储每个粒子的x、y方向速度,符合物理运动的实际情况。
- 通过直接计算x、y方向的力分量替代角度计算,简化逻辑同时避免角度参数错误。
- 引入时间步长
dt,让速度和位置的更新符合真实的物理时间流逝,模拟更稳定。 - 使用深拷贝避免计算过程中原始数据被篡改,保证每一轮计算的独立性。
- 修正帧率控制的睡眠逻辑,避免出现负数睡眠时长的问题。
内容的提问来源于stack exchange,提问作者piro2
相关产品推荐
相关产品推荐

