如何在Pygame中高效存储与渲染行星轨道点
高效存储与渲染Pygame太阳系模拟的轨道点
问题分析
当前代码中,每个行星的orbit列表会无限添加原始天文坐标点,运行时间越长列表越庞大,既占用大量内存,又导致每次渲染时都要遍历所有点重新计算屏幕坐标,双重消耗导致FPS下降最终崩溃。
优化方案
1. 限制轨道点的最大数量
设定合理的最大轨道点数量,只保留最近的N个点,避免内存无限增长。比如根据行星公转周期和视觉效果,设置MAX_ORBIT_POINTS = 1000,超过数量就删除最旧的点。
2. 提前存储屏幕坐标而非原始天文坐标
原来的draw方法中每次都要遍历所有轨道点进行坐标缩放计算,这是重复的性能消耗。改为在update_position时直接计算并存储缩放后的屏幕坐标,渲染时直接使用。
3. 简化渲染逻辑
避免不必要的循环和计算,比如轨道点不足2个时直接跳过渲染,减少无效操作。
修改后的完整代码
import pygame import math pygame.init() WIDTH, HEIGHT = 1200, 750 WIN = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Planet Simulation") WHITE = (255, 255, 255) YELLOW = (255, 255, 0) BLUE = (100, 149, 237) RED = (188, 39, 50) DARK_GREY = (80, 78, 81) FONT = pygame.font.SysFont("comicsans", 16) class Planet: AU = 149.6e6 * 1000 G = 6.67428e-11 SCALE = 250 / AU # 1AU = 100 pixels TIMESTEP = 3600*24 # 1 day MAX_ORBIT_POINTS = 1000 # 限制轨道点最大数量 def __init__(self, x, y, radius, color, mass): self.x = x self.y = y self.radius = radius self.color = color self.mass = mass self.orbit = [] # 现在存储的是缩放后的屏幕坐标 self.sun = False self.distance_to_sun = 0 self.x_vel = 0 self.y_vel = 0 def draw(self, win): x = self.x * self.SCALE + WIDTH / 2 y = self.y * self.SCALE + HEIGHT / 2 if len(self.orbit) > 2: # 直接使用预计算的屏幕坐标渲染 pygame.draw.lines(win, self.color, False, self.orbit, 2) pygame.draw.circle(win, self.color, (x, y), self.radius) if not self.sun: distance_text = FONT.render(f"{round(self.distance_to_sun/1000, 1)}km", 1, WHITE) win.blit(distance_text, (x - distance_text.get_width()/2, y - distance_text.get_height()/2)) def attraction(self, other): other_x, other_y = other.x, other.y distance_x = other_x - self.x distance_y = other_y - self.y distance = math.sqrt(distance_x ** 2 + distance_y ** 2) if other.sun: self.distance_to_sun = distance force = self.G * self.mass * other.mass / distance**2 theta = math.atan2(distance_y, distance_x) force_x = math.cos(theta) * force force_y = math.sin(theta) * force return force_x, force_y def update_position(self, planets): total_fx = total_fy = 0 for planet in planets: if self == planet: continue fx, fy = self.attraction(planet) total_fx += fx total_fy += fy self.x_vel += total_fx / self.mass * self.TIMESTEP self.y_vel += total_fy / self.mass * self.TIMESTEP self.x += self.x_vel * self.TIMESTEP self.y += self.y_vel * self.TIMESTEP # 计算并存储缩放后的屏幕坐标,而非原始天文坐标 screen_x = self.x * self.SCALE + WIDTH / 2 screen_y = self.y * self.SCALE + HEIGHT / 2 self.orbit.append((screen_x, screen_y)) # 超过最大数量时删除最旧的点 if len(self.orbit) > self.MAX_ORBIT_POINTS: self.orbit.pop(0) def main(): run = True clock = pygame.time.Clock() sun = Planet(0, 0, 30, YELLOW, 1.98892 * 10**30) sun.sun = True earth = Planet(-1 * Planet.AU, 0, 16, BLUE, 5.9742 * 10**24) earth.y_vel = 29.783 * 1000 mars = Planet(-1.524 * Planet.AU, 0, 12, RED, 6.39 * 10**23) mars.y_vel = 24.077 * 1000 mercury = Planet(0.387 * Planet.AU, 0, 8, DARK_GREY, 3.30 * 10**23) mercury.y_vel = -47.4 * 1000 venus = Planet(0.723 * Planet.AU, 0, 14, WHITE, 4.8685 * 10**24) venus.y_vel = -35.02 * 1000 planets = [sun, earth, mars, mercury, venus] while run: clock.tick(60) WIN.fill((0, 0, 0)) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False for planet in planets: planet.update_position(planets) planet.draw(WIN) pygame.display.update() pygame.quit() main()
额外优化建议
- 对于公转周期较长的行星,可以降低轨道点的添加频率,比如每N个TIMESTEP才添加一个点,既不影响视觉效果,又能进一步减少点的数量。
- 如果需要保留完整轨道,可以将轨道渲染为静态表面(Surface),只在轨道点更新到一定程度时重新绘制Surface,而非每次都调用
pygame.draw.lines。
内容的提问来源于stack exchange,提问作者RandomCoder59
相关产品推荐
相关产品推荐

