目录
效果演示
2026新年快乐烟花演示
代码示例
bash
# V1.0 初始版本,实现放烟花
# V1.1 优化代码结构,增加粒子类,优化粒子效果
# V1.2 优化上一版本中卡顿问题
# V1.3 增加音效
import pygame
import random
import math
import sys
import colorsys
from typing import List, Tuple
import time
import os
# 初始化pygame和音频
pygame.init()
pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=512)
# 设置窗口尺寸
WIDTH, HEIGHT = 1200, 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("新年快乐!2026绚烂烟花秀 - 带音效版")
# 颜色定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
# 预生成颜色表,避免重复计算
COLOR_CACHE = {}
def get_color(hue: float, saturation: float = 0.9, value: float = 0.9) -> Tuple[int, int, int]:
"""缓存的颜色生成函数"""
key = (int(hue * 1000), int(saturation * 100), int(value * 100))
if key not in COLOR_CACHE:
rgb = colorsys.hsv_to_rgb(hue, saturation, value)
COLOR_CACHE[key] = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
return COLOR_CACHE[key]
# 预生成颜色列表
COLORS = [get_color(i / 20) for i in range(20)] # 减少到20种颜色
# 特殊效果颜色(预定义,避免运行时生成)
SPECIAL_COLORS = [
(255, 215, 0), # 金色
(192, 192, 192), # 银色
(255, 20, 147), # 深粉色
(0, 255, 255), # 青色
(255, 105, 180), # 热粉色
]
# 数学函数预计算和缓存
SIN_CACHE = {}
COS_CACHE = {}
def fast_sin(angle: float) -> float:
"""缓存的sin函数"""
key = int(angle * 1000)
if key not in SIN_CACHE:
SIN_CACHE[key] = math.sin(angle)
return SIN_CACHE[key]
def fast_cos(angle: float) -> float:
"""缓存的cos函数"""
key = int(angle * 1000)
if key not in COS_CACHE:
COS_CACHE[key] = math.cos(angle)
return COS_CACHE[key]
# 预计算的PI值
PI2 = math.pi * 2
# 声音管理器类
class SoundManager:
def __init__(self):
self.sounds = {}
self.volume = 0.7
# 尝试加载声音文件
try:
# 这里我们可以使用pygame内置生成声音,或者加载文件
self.generate_sounds()
except Exception as e:
print(f"声音初始化失败: {e}")
def generate_sounds(self):
"""生成烟花声音效果"""
# 1. 发射声音(上升音调)
self.sounds['launch'] = []
for i in range(3):
sound = self.create_launch_sound(pitch_factor=0.8 + i * 0.1)
self.sounds['launch'].append(sound)
# 2. 爆炸声音(不同大小)
self.sounds['explosion_small'] = []
self.sounds['explosion_medium'] = []
self.sounds['explosion_large'] = []
for i in range(4):
# 小爆炸
sound = self.create_explosion_sound(duration=0.5, pitch=0.7 + i * 0.1)
self.sounds['explosion_small'].append(sound)
# 中爆炸
sound = self.create_explosion_sound(duration=0.8, pitch=0.5 + i * 0.1)
self.sounds['explosion_medium'].append(sound)
# 大爆炸
sound = self.create_explosion_sound(duration=1.2, pitch=0.3 + i * 0.1)
self.sounds['explosion_large'].append(sound)
# 3. 火花声音(细小爆裂声)
self.sounds['sparkle'] = []
for i in range(5):
sound = self.create_sparkle_sound()
self.sounds['sparkle'].append(sound)
# 4. 特殊效果声音
self.sounds['special'] = []
for i in range(3):
sound = self.create_special_sound()
self.sounds['special'].append(sound)
def create_launch_sound(self, pitch_factor=1.0):
"""创建发射声音"""
duration = 0.3 # 秒
sample_rate = 22050
samples = int(duration * sample_rate)
sound_array = []
for i in range(samples):
# 上升音调的正弦波
t = i / sample_rate
freq = 200 + 400 * t * pitch_factor # 频率从200Hz上升到600Hz
value = math.sin(2 * math.pi * freq * t) * (1 - t/duration) # 音量衰减
sound_array.append([int(value * 32767 * 0.3), int(value * 32767 * 0.3)])
sound = pygame.sndarray.make_sound(pygame.sndarray.array(sound_array))
sound.set_volume(self.volume * 0.5)
return sound
def create_explosion_sound(self, duration=1.0, pitch=1.0):
"""创建爆炸声音"""
sample_rate = 22050
samples = int(duration * sample_rate)
sound_array = []
for i in range(samples):
t = i / sample_rate
# 基础频率
base_freq = 80 * pitch
# 多个频率叠加,创造丰富的爆炸声
value = 0
for freq_mul in [1.0, 1.5, 2.0, 3.0, 4.5]:
freq = base_freq * freq_mul
value += (math.sin(2 * math.pi * freq * t) / freq_mul) * math.exp(-t * 3)
# 添加一些噪声
noise = (random.random() - 0.5) * 0.2
# 音量包络:快速上升,缓慢衰减
envelope = min(t * 10, 1) * math.exp(-t * 2)
sample_value = int((value * 0.7 + noise * 0.3) * envelope * 32767)
sound_array.append([sample_value, sample_value])
sound = pygame.sndarray.make_sound(pygame.sndarray.array(sound_array))
sound.set_volume(self.volume)
return sound
def create_sparkle_sound(self):
"""创建火花声音"""
duration = 0.1
sample_rate = 22050
samples = int(duration * sample_rate)
sound_array = []
for i in range(samples):
t = i / sample_rate
freq = 2000 + random.random() * 2000
value = math.sin(2 * math.pi * freq * t) * math.exp(-t * 20)
sample_value = int(value * 32767 * 0.2)
sound_array.append([sample_value, sample_value])
sound = pygame.sndarray.make_sound(pygame.sndarray.array(sound_array))
sound.set_volume(self.volume * 0.3)
return sound
def create_special_sound(self):
"""创建特殊效果声音"""
duration = 1.5
sample_rate = 22050
samples = int(duration * sample_rate)
sound_array = []
for i in range(samples):
t = i / sample_rate
# 创造"嘶嘶"上升效果
freq = 100 + 500 * t
value1 = math.sin(2 * math.pi * freq * t) * 0.5
# 添加高频成分
value2 = math.sin(2 * math.pi * freq * 3 * t) * 0.3 * math.exp(-t * 2)
# 最终混合
envelope = math.exp(-t * 1.5)
sample_value = int((value1 + value2) * envelope * 32767 * 0.4)
sound_array.append([sample_value, sample_value])
sound = pygame.sndarray.make_sound(pygame.sndarray.array(sound_array))
sound.set_volume(self.volume * 0.4)
return sound
def play_launch(self):
"""播放发射声音"""
if self.sounds.get('launch'):
sound = random.choice(self.sounds['launch'])
sound.play()
def play_explosion(self, size_type="medium"):
"""播放爆炸声音"""
if self.sounds.get(f'explosion_{size_type}'):
sound = random.choice(self.sounds[f'explosion_{size_type}'])
sound.play()
def play_sparkle(self):
"""播放火花声音"""
if self.sounds.get('sparkle'):
if random.random() < 0.3: # 30%概率播放,避免太频繁
sound = random.choice(self.sounds['sparkle'])
sound.play()
def play_special(self):
"""播放特殊效果声音"""
if self.sounds.get('special'):
sound = random.choice(self.sounds['special'])
sound.play()
def set_volume(self, volume):
"""设置音量"""
self.volume = max(0.0, min(1.0, volume))
# 创建声音管理器实例
sound_manager = SoundManager()
# 优化版粒子类(添加声音支持)
class Particle:
__slots__ = ('x', 'y', 'color', 'particle_type', 'size', 'speed', 'life',
'decay', 'gravity', 'angle', 'vx', 'vy', 'alpha', 'rotation',
'rotation_speed', 'pulse_speed', 'pulse_phase', 'original_size',
'color_change', 'target_color', 'current_color', 'drag_factor')
def __init__(self, x: float, y: float, color=None, particle_type="normal"):
self.x = x
self.y = y
self.particle_type = particle_type
# 颜色选择优化
if color is None:
if particle_type == "special":
self.color = random.choice(SPECIAL_COLORS)
elif particle_type == "sparkle":
self.color = WHITE
else:
self.color = random.choice(COLORS)
else:
self.color = color
# 根据粒子类型设置参数(减少随机范围)
if particle_type == "normal":
self.size = 1.5 + random.random() * 2.0
self.speed = 2 + random.random() * 6
self.life = 40 + random.randint(0, 60)
self.decay = 1 + random.random() * 2
self.gravity = 0.1
elif particle_type == "fast":
self.size = 1.0 + random.random() * 1.5
self.speed = 6 + random.random() * 6
self.life = 20 + random.randint(0, 30)
self.decay = 3 + random.random() * 2
self.gravity = 0.05
elif particle_type == "sparkle":
self.size = 0.5 + random.random() * 1.0
self.speed = 4 + random.random() * 4
self.life = 30 + random.randint(0, 30)
self.decay = 4 + random.random() * 2
self.gravity = 0.02
else: # special
self.size = 3.0 + random.random() * 2.0
self.speed = 3 + random.random() * 4
self.life = 60 + random.randint(0, 60)
self.decay = 1 + random.random() * 1
self.gravity = 0.08
# 使用预计算的sin/cos
self.angle = random.random() * PI2
cos_a = fast_cos(self.angle)
sin_a = fast_sin(self.angle)
self.vx = cos_a * self.speed
self.vy = sin_a * self.speed
self.alpha = 255
self.rotation = random.random() * 360
self.rotation_speed = -5 + random.random() * 10
# 减少复杂效果以提升性能
self.pulse_speed = 0.1 # 固定值减少计算
self.pulse_phase = random.random() * PI2
self.original_size = self.size
# 减少颜色变化效果
self.color_change = False # 禁用颜色变化提升性能
# 预计算空气阻力因子
self.drag_factor = 0.97 + (random.random() * 0.04 - 0.02)
def update(self) -> bool:
"""更新粒子,返回是否存活"""
self.x += self.vx
self.y += self.vy
self.vy += self.gravity
# 空气阻力 - 使用drag_factor
self.vx *= self.drag_factor
self.vy *= self.drag_factor
# 生命周期
self.life -= 1
if self.life <= 0:
return False
# 透明度衰减
self.alpha -= self.decay
if self.alpha <= 0:
return False
# 简化效果:只保留旋转
self.rotation += self.rotation_speed
# 随机播放火花声音
if self.particle_type == "sparkle" and random.random() < 0.01:
sound_manager.play_sparkle()
return True
def draw(self, surface: pygame.Surface):
"""绘制粒子到surface"""
if self.alpha <= 0:
return
alpha = int(self.alpha)
if alpha <= 0:
return
# 简化绘制:只绘制圆形,去掉复杂效果
radius = max(1, int(self.size))
if radius <= 0:
return
# 直接绘制,避免创建临时surface
color = self.color
if alpha < 255:
# 如果需要透明度,创建小surface
temp_surf = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
pygame.draw.circle(temp_surf, (*color, alpha), (radius, radius), radius)
surface.blit(temp_surf, (int(self.x - radius), int(self.y - radius)))
else:
pygame.draw.circle(surface, color, (int(self.x), int(self.y)), radius)
# 优化版烟花发射器(添加声音支持)
class Firework:
__slots__ = ('x', 'y', 'color', 'speed', 'target_y', 'exploded',
'particles', 'size', 'trail', 'sparks', 'explosion_count',
'max_explosions', 'firework_type', 'trail_counter', 'has_played_launch_sound')
# 预计算爆炸参数
EXPLOSION_PARAMS = {
"normal": {"count": (100, 150), "types": ["normal", "fast"], "sound": "medium"},
"big": {"count": (150, 200), "types": ["normal", "special"], "sound": "large"},
"multi": {"count": (80, 120), "types": ["normal", "sparkle"], "sound": "small"}
}
def __init__(self, firework_type="normal"):
self.firework_type = firework_type
self.reset()
self.trail_counter = 0
self.has_played_launch_sound = False
def reset(self):
self.x = random.randint(100, WIDTH - 100)
self.y = HEIGHT
self.color = random.choice(COLORS)
self.speed = 8 + random.random() * 7
self.target_y = 50 + random.randint(0, HEIGHT // 4) # 调整爆炸高度
self.exploded = False
self.particles = [] # 使用列表但会定期清理
self.size = 3
self.trail = [] # 简化轨迹存储
self.sparks = [] # 简化火花效果
self.explosion_count = 0
self.has_played_launch_sound = False
# 减少爆炸次数提升性能
if self.firework_type == "multi":
self.max_explosions = 2
else:
self.max_explosions = 1
def update(self):
"""更新烟花状态"""
if not self.exploded:
# 播放发射声音(只播放一次)
if not self.has_played_launch_sound and self.y < HEIGHT - 100:
sound_manager.play_launch()
self.has_played_launch_sound = True
# 简化轨迹记录:每2帧记录一次
self.trail_counter += 1
if self.trail_counter % 2 == 0:
self.trail.append((self.x, self.y))
if len(self.trail) > 8: # 减少轨迹长度
self.trail.pop(0)
# 上升移动
self.y -= self.speed
self.x += (random.random() - 0.5) * 1.6 # 简化随机移动
# 简化火花效果
if random.random() < 0.2 and len(self.sparks) < 5: # 限制火花数量
spark = Particle(self.x, self.y, self.color, "sparkle")
spark.speed = 1 + random.random() * 2
spark.vx = (random.random() - 0.5) * 2
spark.vy = -random.random() * 2
spark.life = 10 + random.randint(0, 10)
self.sparks.append(spark)
# 更新火花
alive_sparks = []
for spark in self.sparks:
if spark.update():
alive_sparks.append(spark)
self.sparks = alive_sparks
# 检查爆炸
if self.y <= self.target_y:
self.explode()
else:
# 更新粒子
alive_particles = []
for particle in self.particles:
if particle.update():
alive_particles.append(particle)
self.particles = alive_particles
def explode(self):
"""执行爆炸"""
self.exploded = True
self.explosion_count += 1
params = self.EXPLOSION_PARAMS.get(self.firework_type, self.EXPLOSION_PARAMS["normal"])
min_count, max_count = params["count"]
particle_types = params["types"]
sound_type = params["sound"]
# 播放爆炸声音
sound_manager.play_explosion(sound_type)
# 如果是特殊烟花,播放额外声音
if self.firework_type == "special":
sound_manager.play_special()
# 减少粒子数量
particle_count = min_count + random.randint(0, max_count - min_count)
# 预计算角度和速度
for i in range(particle_count):
angle = (i / particle_count) * PI2
speed = 2 + random.random() * 6
# 使用查表法选择粒子类型
if len(particle_types) > 1:
particle_type = particle_types[i % 2] # 交替使用两种类型
else:
particle_type = particle_types[0]
particle = Particle(self.x, self.y, self.color, particle_type)
particle.angle = angle
cos_a = fast_cos(angle)
sin_a = fast_sin(angle)
particle.vx = cos_a * speed
particle.vy = sin_a * speed
self.particles.append(particle)
def draw(self, surface: pygame.Surface):
"""绘制烟花"""
if not self.exploded:
# 绘制简化轨迹
for i, (trail_x, trail_y) in enumerate(self.trail):
alpha = 100 + int(155 * (i / len(self.trail)) if self.trail else 0)
size = max(1, int(self.size * (i / len(self.trail))))
pygame.draw.circle(surface, (*self.color, alpha),
(int(trail_x), int(trail_y)), size)
# 绘制火花
for spark in self.sparks:
spark.draw(surface)
# 绘制烟花主体
pygame.draw.circle(surface, WHITE, (int(self.x), int(self.y)), self.size)
pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), self.size // 2)
else:
# 批量绘制粒子
for particle in self.particles:
particle.draw(surface)
def is_done(self) -> bool:
"""检查烟花是否结束"""
if not self.exploded:
return False
return len(self.particles) == 0 and self.explosion_count >= self.max_explosions
# 简化版星星背景
class Star:
__slots__ = ('x', 'y', 'size', 'brightness', 'twinkle_speed', 'phase')
def __init__(self):
self.x = random.randint(0, WIDTH)
self.y = random.randint(0, HEIGHT // 2)
self.size = random.random() * 1.5 # 减小最大尺寸
self.brightness = 100 + random.randint(0, 155)
self.twinkle_speed = 0.01 + random.random() * 0.04
self.phase = random.random() * PI2
def update(self):
self.phase += self.twinkle_speed
self.brightness = 100 + int(105 * (0.5 + 0.5 * fast_sin(self.phase)))
def draw(self, surface: pygame.Surface):
brightness = min(255, max(0, self.brightness))
color = (brightness, brightness, brightness)
pygame.draw.circle(surface, color, (int(self.x), int(self.y)), max(0.5, self.size))
# 优化绘制函数
def draw_background_gradient(surface: pygame.Surface):
"""优化背景绘制"""
# 使用填充代替逐行绘制
top_color = (10, 10, 40)
bottom_color = (30, 30, 60)
# 创建渐变surface(缓存)
if not hasattr(draw_background_gradient, 'gradient_surf'):
gradient = pygame.Surface((1, HEIGHT))
for y in range(HEIGHT):
ratio = y / HEIGHT
r = int(top_color[0] * (1 - ratio) + bottom_color[0] * ratio)
g = int(top_color[1] * (1 - ratio) + bottom_color[1] * ratio)
b = int(top_color[2] * (1 - ratio) + bottom_color[2] * ratio)
gradient.set_at((0, y), (r, g, b))
draw_background_gradient.gradient_surf = gradient
# 拉伸填充
pygame.transform.scale(draw_background_gradient.gradient_surf, (WIDTH, HEIGHT), surface)
# 主程序
def main():
# 创建表面
screen = pygame.display.set_mode((WIDTH, HEIGHT))
particle_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
# 创建对象
fireworks = [Firework("normal") for _ in range(2)] # 减少初始烟花数量
stars = [Star() for _ in range(150)] # 减少星星数量
# 字体
try:
font = pygame.font.SysFont("simhei", 48) # 使用较小字体
small_font = pygame.font.SysFont("simhei", 24)
except:
font = pygame.font.SysFont(None, 48)
small_font = pygame.font.SysFont(None, 24)
# 预渲染文本(减少每帧渲染)
title_text = font.render("新年快乐!2026", True, (255, 215, 0))
clock = pygame.time.Clock()
running = True
frame = 0
last_firework_time = 0
# 游戏循环
while running:
# 事件处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
# 限制添加烟花数量
if len(fireworks) < 16: # 最大8个烟花
firework_type = random.choice(["normal", "normal", "big"])
fireworks.append(Firework(firework_type))
# 清空表面
screen.fill(BLACK)
draw_background_gradient(screen)
particle_surface.fill((0, 0, 0, 0))
# 更新和绘制星星
for star in stars:
star.update()
star.draw(screen)
# 自动添加烟花(限制频率)
current_time = pygame.time.get_ticks()
if (current_time - last_firework_time > 2000 and # 每2秒
len(fireworks) < 6 and # 不超过6个
random.random() < 0.1): # 10%概率
firework_type = random.choice(["normal", "normal", "big", "multi"])
fireworks.append(Firework(firework_type))
last_firework_time = current_time
# 更新和绘制烟花
alive_fireworks = []
total_particles = 0
for firework in fireworks:
firework.update()
# 统计粒子数量
total_particles += len(firework.particles)
# 绘制到粒子表面
firework.draw(particle_surface)
# 移除完成的烟花
if not firework.is_done():
alive_fireworks.append(firework)
elif random.random() < 0.3 and len(alive_fireworks) < 4: # 30%概率立即添加新烟花
firework_type = random.choice(["normal", "big"])
alive_fireworks.append(Firework(firework_type))
fireworks = alive_fireworks
# 绘制粒子表面
screen.blit(particle_surface, (0, 0))
# 绘制UI
frame += 1
# 计算彩虹颜色
hue = (frame * 0.01) % 1.0 # 色相在0-1之间循环
rgb = colorsys.hsv_to_rgb(hue, 0.9, 1.0) # 高饱和度,高亮度
color = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
# 重新渲染文本(使用新颜色)
title_text = font.render("新年快乐!2026", True, color)
# 添加文字阴影增强效果
shadow_color = (int(rgb[0] * 150), int(rgb[1] * 150), int(rgb[2] * 150))
shadow_text = font.render("新年快乐!2026", True, shadow_color)
# 绘制阴影(偏移2像素)
screen.blit(shadow_text, (WIDTH//2 - shadow_text.get_width()//2 + 2, 52))
# 绘制主文字
screen.blit(title_text, (WIDTH//2 - title_text.get_width()//2, 50))
# 更新显示
pygame.display.flip()
# 动态调整帧率目标
target_fps = 60
if total_particles > 2000:
target_fps = 45
elif total_particles > 1000:
target_fps = 50
clock.tick(target_fps)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()