在浩瀚宇宙中,太阳系以其独特的魅力吸引着我们不断探索。今天,笔者在 CSDN 向大家介绍一款我制作的 Python Pygame 库的太阳系动画程序。
先看视频效果:
炫来袭!Python代码敲出动态太阳系!
运行此程序,眼前瞬间展现出一个精妙绝伦的太阳系微缩模型。以太阳为中心,八大行星沿着各自的轨道有条不紊地运行,仿佛将浩瀚宇宙浓缩于眼前。各行星色彩逼真,大小比例协调,让人仿佛置身于浩瀚星空之中。通过精准的物理模拟,行星的公转速度与实际宇宙规律相契合,直观地展示出行星间的相对运动关系,让观众能清晰地观察到太阳系的运转机制。
软件主界面展示:
Gif动图展示(视频转后不是很清晰,可以观看视频;)
界面控制功能展示:包括五大功能!!
1.速度控制:星云斗转!竟在一指之间!!
2.运行暂停:点击即可暂停运行;
3.界面缩放:鼠标滚轮即可实现;
4.界面拖动:适合近距离观看,鼠标左键即可;
5.行星聚焦:点击一颗行星,即可聚焦星轨!
界面卫星信息展示:鼠标移动至对应行星,会显示物理信息!!!
这款程序不仅具有极高的观赏性,还蕴含着丰富的科普价值。无论是天文爱好者,还是对宇宙奥秘充满好奇的初学者,都能从中获得知识与乐趣。它将复杂的天体运动以一种生动形象的方式呈现,使抽象的天文知识变得触手可及。
地球界面信息展示:
木星界面信息展示:
对于开发者而言,这款程序更是学习 Pygame 库的绝佳范例。它展示了如何利用 Pygame 进行图形绘制、动画制作以及物理模拟,涵盖了从界面搭建到复杂交互的各方面技术,为你的编程之旅提供了一个富有创意的实践案例。
土星界面微观星云展示:
快来体验这款充满魅力的太阳系动画程序吧。
全代码展示:复制即可在python中运行!!
import pygame
import sys
import math
import random
import numpy as np
# 初始化pygame
pygame.init()
# 设置中文字体
pygame.font.init()
font_options = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC", "Arial Unicode MS"]
system_fonts = pygame.font.get_fonts()
font_name = None
for font in font_options:
if font.lower() in system_fonts:
font_name = font
break
if font_name is None:
font_name = pygame.font.get_default_font()
# 常量定义
WIDTH, HEIGHT = 1200, 800
BACKGROUND_COLOR = (0, 0, 0)
FPS = 60
# 行星数据 (名称, 颜色, 基础半径, 轨道半径, 公转周期(地球日), 自转周期(小时), 直径(km))
planets_data = [
("太阳", (255, 255, 0), 20, 0, 0, 25.4, 1392700),
("水星", (169, 169, 169), 3.2, 60, 88, 58.6, 4879),
("金星", (255, 182, 193), 7.9, 100, 225, -243, 12104),
("地球", (0, 0, 255), 8.0, 150, 365, 24, 12756),
("火星", (255, 0, 0), 4.3, 200, 687, 24.6, 6792),
("木星", (255, 140, 0), 16.0, 250, 4333, 9.9, 142984),
("土星", (240, 230, 140), 12.0, 330, 10759, 10.7, 120536),
("天王星", (173, 216, 230), 10.0, 400, 30687, -17.2, 51118),
("海王星", (70, 130, 180), 9.0, 470, 60190, 16.1, 49528)
]
class CelestialObject:
"""天体基类"""
def __init__(self, name, color, radius, orbit_radius, orbit_period, rotation_period, diameter):
self.name = name
self.base_color = color
self.radius = radius
self.orbit_radius = orbit_radius
self.orbit_period = orbit_period
self.rotation_period = rotation_period
self.diameter = diameter
self.angle = 0
self.x = 0
self.y = 0
self.planets = [] # 卫星或行星
self.focused = False
self.info_display = False
self.surface = None # 天体表面
self.rotation_angle = 0 # 自转角度
self._create_surface()
def _create_surface(self):
"""创建天体表面"""
if self.name == "太阳":
self._create_sun_surface()
elif self.name == "地球":
self._create_earth_surface()
elif self.name == "木星":
self._create_jupiter_surface()
elif self.name == "土星":
self._create_saturn_surface()
else:
self._create_default_surface()
def _create_default_surface(self):
"""创建默认天体表面"""
size = int(self.radius * 2 * 2.5) # 留出发光效果的空间
self.surface = pygame.Surface((size, size), pygame.SRCALPHA)
# 创建基础球体
pygame.draw.circle(
self.surface,
self.base_color,
(size // 2, size // 2),
int(self.radius * 2)
)
# 添加一些细节斑点
for _ in range(15):
spot_size = random.randint(2, 5)
spot_color = tuple(max(0, c - random.randint(0, 50)) for c in self.base_color)
x = random.randint(int(self.radius * 2 * 0.3), int(self.radius * 2 * 0.7))
y = random.randint(int(self.radius * 2 * 0.3), int(self.radius * 2 * 0.7))
pygame.draw.circle(self.surface, spot_color, (x, y), spot_size)
def _create_sun_surface(self):
"""创建太阳表面"""
size = int(self.radius * 2 * 3) # 留出发光效果的空间
self.surface = pygame.Surface((size, size), pygame.SRCALPHA)
# 创建太阳核心
core_radius = int(self.radius * 2 * 0.8)
pygame.draw.circle(
self.surface,
(255, 255, 150), # 淡黄色
(size // 2, size // 2),
core_radius
)
# 创建太阳外层
for i in range(10):
opacity = 255 - i * 25
radius = core_radius + i * 3
pygame.draw.circle(
self.surface,
(*self.base_color, opacity),
(size // 2, size // 2),
radius
)
# 添加太阳黑子
for _ in range(8):
spot_size = random.randint(5, 15)
spot_color = (200, 200, 100) # 暗黄色
x = random.randint(int(self.radius * 2 * 0.2), int(self.radius * 2 * 0.8))
y = random.randint(int(self.radius * 2 * 0.2), int(self.radius * 2 * 0.8))
pygame.draw.circle(self.surface, spot_color, (x, y), spot_size)
def _create_earth_surface(self):
"""创建地球表面"""
size = int(self.radius * 2 * 2.5)
self.surface = pygame.Surface((size, size), pygame.SRCALPHA)
# 创建地球球体
pygame.draw.circle(
self.surface,
(0, 100, 255), # 深蓝色
(size // 2, size // 2),
int(self.radius * 2)
)
# 添加大陆(修复透明度负值问题)
for _ in range(8):
continent_size = random.randint(8, 15)
x = random.randint(int(self.radius * 2 * 0.3), int(self.radius * 2 * 0.7))
y = random.randint(int(self.radius * 2 * 0.3), int(self.radius * 2 * 0.7))
# 创建渐变大陆(添加透明度最小值限制)
for i in range(continent_size, 0, -1):
opacity = max(0, 255 - (continent_size - i) * 20) # 确保透明度非负
pygame.draw.circle(
self.surface,
(100 + random.randint(0, 50), 200 + random.randint(0, 50), 100 + random.randint(0, 50), opacity),
(x, y),
i
)
# 添加云层
for _ in range(5):
cloud_size = random.randint(7, 12)
x = random.randint(int(self.radius * 2 * 0.3), int(self.radius * 2 * 0.7))
y = random.randint(int(self.radius * 2 * 0.3), int(self.radius * 2 * 0.7))
pygame.draw.circle(
self.surface,
(240, 240, 255, 150),
(x, y),
cloud_size
)
def _create_jupiter_surface(self):
"""创建木星表面"""
size = int(self.radius * 2 * 2.5)
self.surface = pygame.Surface((size, size), pygame.SRCALPHA)
# 创建木星基础
pygame.draw.circle(
self.surface,
(255, 180, 100), # 橙色
(size // 2, size // 2),
int(self.radius * 2)
)
# 添加木星条纹
for i in range(8):
height = random.randint(5, 10)
y_offset = random.randint(-int(self.radius * 2 * 0.4), int(self.radius * 2 * 0.4))
color = (255, 160 - i * 10, 80 - i * 5)
pygame.draw.ellipse(
self.surface,
color,
(int(self.radius * 2 * 0.2), int(self.radius * 2 + y_offset - height / 2),
int(self.radius * 2 * 1.6), height)
)
# 添加大红斑
pygame.draw.ellipse(
self.surface,
(200, 50, 50),
(int(self.radius * 2 * 0.5), int(self.radius * 2 * 0.7),
int(self.radius * 2 * 0.4), int(self.radius * 2 * 0.2))
)
def _create_saturn_surface(self):
"""创建土星表面及光环"""
size = int(self.radius * 2 * 3) # 为光环留出空间
self.surface = pygame.Surface((size, size), pygame.SRCALPHA)
# 创建土星球体
pygame.draw.circle(
self.surface,
self.base_color,
(size // 2, size // 2),
int(self.radius * 2)
)
# 添加土星条纹
for i in range(5):
height = random.randint(4, 8)
y_offset = random.randint(-int(self.radius * 2 * 0.3), int(self.radius * 2 * 0.3))
color = tuple(max(0, c - random.randint(0, 30)) for c in self.base_color)
pygame.draw.ellipse(
self.surface,
color,
(int(self.radius * 2 * 0.3), int(self.radius * 2 + y_offset - height / 2),
int(self.radius * 2 * 1.4), height)
)
# 创建土星光环(关键修复:确保循环语法正确)
for i in range(3): # 检查此处缩进和语法
width = 8 - i * 2
radius = int(self.radius * 2 * (1.3 + i * 0.3))
# 使用临时表面绘制半透明光环
ring_surface = pygame.Surface((size, size), pygame.SRCALPHA)
pygame.draw.ellipse(
ring_surface,
(200, 180, 150, 200 - i * 50),
(size // 2 - radius, size // 2 - radius // 3,
radius * 2, radius * 2 // 3),
width
)
self.surface.blit(ring_surface, (0, 0))
def add_planet(self, planet):
"""添加卫星或行星"""
self.planets.append(planet)
def update(self, dt, scale, offset_x, offset_y, center_x, center_y):
"""更新天体位置"""
if self.orbit_period != 0: # 不是太阳
self.angle += (2 * math.pi / self.orbit_period) * dt * 10 # 调整速度
self.x = center_x + self.orbit_radius * math.cos(self.angle) * scale + offset_x
self.y = center_y + self.orbit_radius * math.sin(self.angle) * scale + offset_y
else: # 太阳位于中心
self.x = center_x + offset_x
self.y = center_y + offset_y
# 更新自转角度
if self.rotation_period != 0:
self.rotation_angle += (360 / (self.rotation_period * 3600)) * dt * 1000 # 转为度/秒
# 更新卫星或行星
for planet in self.planets:
planet.update(dt, scale, offset_x, offset_y, self.x, self.y)
def draw(self, surface, scale, font):
"""绘制天体及其轨道"""
# 绘制轨道
if self.orbit_period != 0: # 不是太阳
pygame.draw.circle(surface, (255, 255, 255, 30),
(int(self.x - self.orbit_radius * math.cos(self.angle) * scale),
int(self.y - self.orbit_radius * math.sin(self.angle) * scale)),
int(self.orbit_radius * scale), 1)
# 绘制天体
if self.surface:
# 根据缩放调整显示大小
display_size = max(1, int(self.radius * scale * 2.5))
scaled_surface = pygame.transform.scale(self.surface, (display_size, display_size))
# 应用自转
if self.rotation_period != 0:
rotated_surface = pygame.transform.rotate(scaled_surface, self.rotation_angle)
rotated_rect = rotated_surface.get_rect(center=(int(self.x), int(self.y)))
surface.blit(rotated_surface, rotated_rect.topleft)
else:
surface.blit(scaled_surface, (int(self.x - display_size // 2), int(self.y - display_size // 2)))
else:
# 备用绘制方法
pygame.draw.circle(surface, self.base_color, (int(self.x), int(self.y)), int(self.radius * scale))
# 添加发光效果
if self.name == "太阳":
glow_size = int(self.radius * scale * 2)
glow_surface = pygame.Surface((glow_size, glow_size), pygame.SRCALPHA)
pygame.draw.circle(glow_surface, (*self.base_color, 100),
(glow_size // 2, glow_size // 2),
glow_size // 2)
surface.blit(glow_surface, (int(self.x - glow_size // 2),
int(self.y - glow_size // 2)),
special_flags=pygame.BLEND_ADD)
# 绘制卫星或行星
for planet in self.planets:
planet.draw(surface, scale, font)
# 如果被聚焦,绘制高亮
if self.focused:
pygame.draw.circle(surface, (255, 255, 0), (int(self.x), int(self.y)),
max(5, int(self.radius * scale + 5)), 2)
# 如果鼠标悬停,显示信息
if self.info_display:
self._draw_info(surface, font, scale)
def _draw_info(self, surface, font, scale):
"""绘制天体信息"""
info_texts = [
f"{self.name}",
f"直径: {self.diameter:,} km",
f"与太阳距离: {self.orbit_radius * 149.6:,} 百万 km" if self.orbit_period != 0 else "",
f"公转周期: {self.orbit_period:,} 地球日" if self.orbit_period != 0 else "",
f"自转周期: {abs(self.rotation_period):.1f} 小时" if self.rotation_period != 0 else ""
]
# 过滤空字符串
info_texts = [text for text in info_texts if text]
# 创建信息框
box_width = max(font.size(text)[0] for text in info_texts) + 20
box_height = len(info_texts) * 25 + 10
box_x = self.x + max(self.radius * scale, 30) + 10
box_y = self.y - box_height // 2
# 确保信息框不超出屏幕
if box_x + box_width > WIDTH:
box_x = self.x - max(self.radius * scale, 30) - box_width - 10
if box_y < 0:
box_y = 0
elif box_y + box_height > HEIGHT:
box_y = HEIGHT - box_height
# 绘制信息框背景
info_surface = pygame.Surface((box_width, box_height), pygame.SRCALPHA)
info_surface.fill((0, 0, 0, 180))
pygame.draw.rect(info_surface, (255, 255, 255), (0, 0, box_width, box_height), 1)
surface.blit(info_surface, (box_x, box_y))
# 绘制信息文本
for i, text in enumerate(info_texts):
text_surface = font.render(text, True, (255, 255, 255))
surface.blit(text_surface, (box_x + 10, box_y + 10 + i * 25))
def check_hover(self, mouse_pos, scale):
"""检查鼠标是否悬停在天体上"""
distance = math.sqrt((mouse_pos[0] - self.x) ** 2 + (mouse_pos[1] - self.y) ** 2)
self.info_display = distance <= max(10, self.radius * scale)
# 检查卫星或行星
for planet in self.planets:
planet.check_hover(mouse_pos, scale)
def check_click(self, mouse_pos, scale):
"""检查天体是否被点击"""
distance = math.sqrt((mouse_pos[0] - self.x) ** 2 + (mouse_pos[1] - self.y) ** 2)
if distance <= max(10, self.radius * scale):
return self
else:
# 检查卫星或行星
for planet in self.planets:
result = planet.check_click(mouse_pos, scale)
if result:
return result
return None
class SolarSystem:
"""太阳系类"""
def __init__(self):
self.sun = None
self.planets = []
self.stars = []
self.scale = 1.0
self.offset_x = 0
self.offset_y = 0
self.center_x = WIDTH // 2
self.center_y = HEIGHT // 2
self.paused = False
self.speed_factor = 1.0
self.focused_planet = None
self.dragging = False
self.drag_start = (0, 0)
self.slider_dragging = False
self._create_celestial_objects()
self._create_stars()
def _create_celestial_objects(self):
"""创建太阳系天体"""
# 创建太阳
sun_data = planets_data[0]
self.sun = CelestialObject(*sun_data)
# 创建行星
for planet_data in planets_data[1:]:
planet = CelestialObject(*planet_data)
self.sun.add_planet(planet)
self.planets.append(planet)
def _create_stars(self):
"""创建星空背景"""
for _ in range(300):
x = random.randint(0, WIDTH)
y = random.randint(0, HEIGHT)
size = random.randint(1, 2)
brightness = random.randint(100, 255)
# 添加轻微闪烁效果
flicker_speed = random.uniform(0.1, 0.5)
self.stars.append((x, y, size, brightness, flicker_speed, random.uniform(0, 2 * math.pi)))
def update(self, dt):
"""更新太阳系状态"""
if not self.paused:
actual_dt = dt * self.speed_factor
if self.focused_planet:
self.sun.update(actual_dt, self.scale, self.offset_x, self.offset_y,
self.center_x - self.focused_planet.x,
self.center_y - self.focused_planet.y)
else:
self.sun.update(actual_dt, self.scale, self.offset_x, self.offset_y,
self.center_x, self.center_y)
# 更新星星闪烁
for i in range(len(self.stars)):
x, y, size, brightness, speed, phase = self.stars[i]
new_brightness = max(50, min(255, brightness + int(30 * math.sin(pygame.time.get_ticks() * speed + phase))))
self.stars[i] = (x, y, size, new_brightness, speed, phase)
def draw(self, surface, font):
"""绘制太阳系"""
# 绘制星空背景
for star in self.stars:
x, y, size, brightness, _, _ = star
pygame.draw.circle(surface, (brightness, brightness, brightness), (x, y), size)
# 绘制天体
self.sun.draw(surface, self.scale, font)
# 绘制控制信息
self._draw_control_info(surface, font)
def _draw_control_info(self, surface, font):
"""绘制控制信息"""
# 绘制暂停/继续按钮
pause_text = "⏸️ 暂停" if not self.paused else "▶️ 继续"
pause_surface = font.render(pause_text, True, (255, 255, 255))
pause_rect = pygame.Rect(20, 20, pause_surface.get_width() + 10, pause_surface.get_height() + 10)
# 添加按钮悬停效果
if pause_rect.collidepoint(pygame.mouse.get_pos()):
pygame.draw.rect(surface, (70, 70, 70), pause_rect)
else:
pygame.draw.rect(surface, (50, 50, 50), pause_rect)
pygame.draw.rect(surface, (200, 200, 200), pause_rect, 1)
surface.blit(pause_surface, (pause_rect.x + 5, pause_rect.y + 5))
# 绘制速度控制滑块
speed_text = font.render("速度控制:", True, (255, 255, 255))
surface.blit(speed_text, (20, 60))
# 滑块背景
slider_bg = pygame.Rect(140, 65, 200, 10)
pygame.draw.rect(surface, (50, 50, 50), slider_bg)
# 滑块当前位置
slider_pos = 140 + ((self.speed_factor - 0.1) / 4.9) * 200 # 速度范围0.1-5
slider_circle = (int(slider_pos), 70)
# 滑块交互效果
if slider_bg.collidepoint(pygame.mouse.get_pos()) or self.slider_dragging:
pygame.draw.circle(surface, (255, 255, 255), slider_circle, 8)
pygame.draw.circle(surface, (200, 200, 200), slider_circle, 9, 2)
else:
pygame.draw.circle(surface, (255, 255, 255), slider_circle, 7)
pygame.draw.circle(surface, (200, 200, 200), slider_circle, 8, 2)
# 速度值
speed_value_text = font.render(f"{self.speed_factor:.1f}x", True, (255, 255, 255))
surface.blit(speed_value_text, (360, 60))
# 缩放提示
zoom_text = font.render("缩放: 鼠标滚轮", True, (255, 255, 255))
surface.blit(zoom_text, (20, 100))
# 拖动提示
drag_text = font.render("拖动: 按住鼠标左键", True, (255, 255, 255))
surface.blit(drag_text, (20, 130))
# 聚焦提示
focus_text = font.render("聚焦行星: 点击行星", True, (255, 255, 255))
surface.blit(focus_text, (20, 160))
# 当前聚焦行星信息
if self.focused_planet:
focus_info = f"当前聚焦: {self.focused_planet.name}"
focus_info_surface = font.render(focus_info, True, (255, 255, 0))
focus_info_rect = pygame.Rect(WIDTH - focus_info_surface.get_width() - 30, 20,
focus_info_surface.get_width() + 10, focus_info_surface.get_height() + 10)
pygame.draw.rect(surface, (50, 50, 50), focus_info_rect)
pygame.draw.rect(surface, (200, 200, 200), focus_info_rect, 1)
surface.blit(focus_info_surface, (focus_info_rect.x + 5, focus_info_rect.y + 5))
def handle_event(self, event):
"""处理用户事件"""
# 鼠标滚轮缩放
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 4: # 滚轮上滚
self.scale *= 1.1
elif event.button == 5: # 滚轮下滚
self.scale /= 1.1
self.scale = max(0.1, self.scale) # 最小缩放比例
elif event.button == 1: # 左键点击
# 检查是否点击了暂停按钮
pause_rect = pygame.Rect(20, 20, 80, 30)
if pause_rect.collidepoint(event.pos):
self.paused = not self.paused
else:
# 检查是否点击了速度滑块
slider_rect = pygame.Rect(140, 65, 200, 10)
if slider_rect.collidepoint(event.pos):
self.slider_dragging = True
# 直接更新速度
new_speed = 0.1 + ((event.pos[0] - 140) / 200) * 4.9
self.speed_factor = max(0.1, min(5.0, new_speed))
else:
# 检查是否点击了行星
clicked_planet = self.sun.check_click(event.pos, self.scale)
if clicked_planet:
# 切换聚焦状态
if self.focused_planet == clicked_planet:
self.focused_planet = None
else:
self.focused_planet = clicked_planet
else:
# 开始拖动
self.dragging = True
self.drag_start = event.pos
# 鼠标移动
elif event.type == pygame.MOUSEMOTION:
if self.dragging:
# 计算拖动偏移
dx = event.pos[0] - self.drag_start[0]
dy = event.pos[1] - self.drag_start[1]
self.offset_x += dx
self.offset_y += dy
self.drag_start = event.pos
elif self.slider_dragging:
# 更新速度
slider_rect = pygame.Rect(140, 65, 200, 10)
if slider_rect.collidepoint(event.pos):
new_speed = 0.1 + ((event.pos[0] - 140) / 200) * 4.9
self.speed_factor = max(0.1, min(5.0, new_speed))
# 检查鼠标悬停
self.sun.check_hover(event.pos, self.scale)
# 鼠标释放
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1: # 左键释放
self.dragging = False
self.slider_dragging = False
# 键盘事件
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.paused = not self.paused
elif event.key == pygame.K_ESCAPE:
self.focused_planet = None
self.scale = 1.0
self.offset_x = 0
self.offset_y = 0
def main():
"""主函数"""
# 创建窗口
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("太阳系模拟器")
# 设置字体
font = pygame.font.SysFont(font_name, 16)
# 创建太阳系
solar_system = SolarSystem()
# 时钟
clock = pygame.time.Clock()
# 游戏主循环
running = True
while running:
# 计算每帧时间间隔
dt = clock.tick(FPS) / 1000.0 # 转换为秒
# 处理事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
solar_system.handle_event(event)
# 更新
solar_system.update(dt)
# 绘制
screen.fill(BACKGROUND_COLOR)
solar_system.draw(screen, font)
# 更新显示
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()