【Python】用Pygame打造绚丽烟花效果!实现过程+源代码

372 阅读12分钟

在这个数字化时代,视觉效果在游戏开发中扮演着至关重要的角色。无论是简单的2D游戏还是复杂的3D世界,视觉效果都能为用户带来沉浸式体验。今天,我们将通过Python的Pygame库,带你一步步实现一个绚丽的烟花效果。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的指导和灵感。 在这里插入图片描述

为什么选择Pygame?

Pygame是一个简单易用的Python库,专为游戏开发设计。它提供了丰富的功能,包括图像处理、声音播放和事件管理等,非常适合用来制作2D游戏和动画效果。通过Pygame,你可以轻松地将创意转化为现实。

项目概述

在本教程中,我们将创建一个烟花效果的模拟器。烟花将从屏幕底部升起,达到一定高度后爆炸,形成色彩斑斓的粒子效果。我们将使用面向对象的编程思想来组织代码,使其更易于维护和扩展。

代码实现

环境准备

在开始之前,请确保你的开发环境中已经安装了Python和Pygame库。如果没有安装,可以通过以下命令进行安装:

pip install pygame

代码结构

我们将代码分为几个主要部分:Firework类、Particle类、Trail类以及主程序逻辑。每个部分都有其特定的功能和职责。

1. Firework类

Firework类负责管理烟花的整体行为,包括升空、爆炸和粒子生成。

class Firework:
    def __init__(self):
        # 随机生成烟花的颜色,范围为RGB (0-255)
        self.colour = tuple(randint(0, 255) for _ in range(3))
        # 随机生成烟花爆炸后产生的颜色,生成3种不同的颜色
        self.colours = [tuple(randint(0, 255) for _ in range(3)) for _ in range(3)]
        # 创建一个粒子对象,表示烟花的初始状态,位置在屏幕底部
        self.firework = Particle(randint(0, DISPLAY_WIDTH), DISPLAY_HEIGHT, True, self.colour)
        # 标记烟花是否已经爆炸
        self.exploded = False
        # 存储爆炸后产生的粒子
        self.particles = []
        # 定义烟花爆炸时产生的粒子数量范围
        self.min_max_particles = vector(200, 300)  # 增加粒子数量

    def update(self, win):
        # 更新烟花状态
        if not self.exploded:
            # 应用重力对烟花的影响
            self.firework.apply_force(gravity)
            # 移动烟花
            self.firework.move()
            # 显示烟花的尾迹
            for tf in self.firework.trails:
                tf.show(win)

            # 显示当前烟花
            self.show(win)

            # 检查烟花是否达到爆炸条件
            if self.firework.vel.y >= 0:
                self.exploded = True  # 标记为已爆炸
                self.explode()  # 执行爆炸
        else:
            # 如果烟花已经爆炸,更新每个粒子的状态
            for particle in self.particles:
                # 应用重力和随机扰动对粒子的影响
                particle.apply_force(
                    vector(gravity.x + uniform(-0.1, 0.1), gravity.y / 2 + uniform(0.01, 0.08)))
                # 移动粒子
                particle.move()
                # 显示粒子的尾迹
                for t in particle.trails:
                    t.show(win)
                # 显示当前粒子
                particle.show(win)

    def explode(self):
        # 随机生成爆炸产生的粒子数量
        amount = randint(int(self.min_max_particles.x), int(self.min_max_particles.y))
        # 创建粒子并添加到粒子列表中
        self.particles.extend(
            Particle(self.firework.pos.x, self.firework.pos.y, False, self.colours) for _ in range(amount)
        )

    def show(self, win):
        # 在窗口中绘制当前烟花
        pygame.draw.circle(win, self.colour, (int(self.firework.pos.x), int(self.firework.pos.y)), self.firework.size)

    def remove(self):
        # 移除已标记为删除的粒子
        self.particles = [p for p in self.particles if not p.remove]
        # 返回是否已爆炸且没有剩余粒子
        return self.exploded and not self.particles

2. Particle类

Particle类负责管理每个粒子的行为,包括运动、衰减和显示。

class Particle:
    def __init__(self, x, y, firework, colour):
        # 初始化粒子的位置、类型(烟花或普通粒子)、颜色等属性
        self.firework = firework  # 布尔值,指示是否为烟花粒子
        self.pos = vector(x, y)  # 当前粒子的位置
        self.origin = vector(x, y)  # 粒子的起始位置
        self.radius = 20  # 粒子的半径
        self.remove = False  # 标记粒子是否需要被移除
        self.explosion_radius = randint(10, 25)  # 随机生成爆炸半径
        self.life = 0  # 粒子的生命周期
        self.acc = vector(0, 0)  # 粒子的加速度
        # 创建粒子的尾迹,动态或静态,取决于粒子类型
        self.trails = [Trail(i, 5 if firework else randint(2, 4), firework) for i in range(5)]
        self.prev_posx = [-10] * 10  # 存储前10帧的x坐标
        self.prev_posy = [-10] * 10  # 存储前10帧的y坐标

        # 根据粒子类型设置速度、大小和颜色
        if self.firework:
            self.vel = vector(0, -randint(17, 20))  # 烟花粒子向上发射
            self.size = 5  # 烟花粒子大小
            self.colour = colour  # 烟花颜色
        else:
            # 普通粒子随机速度和大小
            self.vel = vector(uniform(-2, 2), uniform(-2, 2))  # 随机初始速度
            self.vel *= randint(10, self.explosion_radius + 5)  # 根据爆炸半径调整速度
            self.size = randint(3, 5)  # 随机粒子大小
            self.colour = choice(colour)  # 从颜色列表中随机选择颜色

    def apply_force(self, force):
        # 应用外力到粒子的加速度
        self.acc += force

    def move(self):
        # 更新粒子的位置和状态
        if not self.firework:
            self.vel *= 0.9  # 普通粒子速度衰减

        self.vel += self.acc  # 更新速度
        self.pos += self.vel  # 更新位置
        self.acc *= 0  # 重置加速度

        # 检查普通粒子是否超出爆炸半径
        if self.life == 0 and not self.firework:
            distance = self.pos.distance_to(self.origin)
            if distance > self.explosion_radius:
                self.remove = True  # 超出范围则标记为移除

        self.decay()  # 处理粒子的衰减
        self.trail_update()  # 更新尾迹
        self.life += 1  # 增加生命周期

    def show(self, win):
        # 在窗口中绘制粒子
        pygame.draw.circle(win, self.colour, (int(self.pos.x), int(self.pos.y)), self.size)

    def decay(self):
        # 根据粒子的生命周期决定是否移除
        if 70 > self.life > 20:  # 在20到70之间的粒子
            if randint(0, 30) == 0:  # 有小概率移除
                self.remove = True
        elif self.life > 70:  # 超过70的粒子
            if randint(0, 5) == 0:  # 有更高概率移除
                self.remove = True

    def trail_update(self):
        # 更新粒子的尾迹位置
        self.prev_posx.pop()  # 移除最旧的x坐标
        self.prev_posx.insert(0, int(self.pos.x))  # 插入当前x坐标
        self.prev_posy.pop()  # 移除最旧的y坐标
        self.prev_posy.insert(0, int(self.pos.y))  # 插入当前y坐标

        # 更新每个尾迹的位置
        for n, t in enumerate(self.trails):
            if t.dynamic:
                t.get_pos(self.prev_posx[n + dynamic_offset], self.prev_posy[n + dynamic_offset])
            else:
                t.get_pos(self.prev_posx[n + static_offset], self.prev_posy[n + static_offset])

3. Trail类

Trail类负责管理粒子的尾迹效果,使烟花看起来更加真实。

class Trail:
    def __init__(self, n, size, dynamic):
        # 初始化尾迹的属性
        self.pos_in_line = n  # 尾迹在粒子尾迹中的位置索引
        self.pos = vector(-10, -10)  # 尾迹的初始位置,设置为无效值
        self.dynamic = dynamic  # 布尔值,指示尾迹是否为动态
        # 根据尾迹的动态性设置颜色
        self.colour = trail_colours[n] if dynamic else (255, 255, 200)  # 动态尾迹使用预定义颜色,静态尾迹为淡黄色
        # 根据尾迹的动态性和位置设置大小
        self.size = max(size - n // 2, 0) if dynamic else max(size - 2, 0)  # 动态尾迹大小随位置变化,静态尾迹大小固定

    def get_pos(self, x, y):
        # 更新尾迹的位置
        self.pos = vector(x, y)  # 将尾迹位置设置为传入的坐标

    def show(self, win):
        # 在窗口中绘制尾迹
        pygame.draw.circle(win, self.colour, (int(self.pos.x), int(self.pos.y)), self.size)  # 绘制尾迹的圆形

4. 主程序逻辑

主程序负责初始化Pygame窗口,处理事件循环,并更新和显示烟花效果。

def update(win, fireworks):
    # 更新所有烟花的状态并在窗口中绘制
    for fw in fireworks:
        fw.update(win)  # 调用每个烟花的更新方法
        if fw.remove():  # 检查烟花是否需要移除
            fireworks.remove(fw)  # 从列表中移除烟花

    pygame.display.update()  # 更新窗口显示

def main():
    pygame.init()  # 初始化 Pygame
    pygame.display.set_caption("Fireworks in Pygame")  # 设置窗口标题
    win = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))  # 创建窗口
    clock = pygame.time.Clock()  # 创建时钟对象以控制帧率

    fireworks = [Firework() for _ in range(2)]  # 初始化两个烟花对象
    running = True  # 运行标志

    while running:
        clock.tick(60)  # 控制游戏循环的帧率为60帧每秒
        for event in pygame.event.get():  # 处理事件
            if event.type == pygame.QUIT:  # 检查是否关闭窗口
                running = False  # 设置运行标志为 False,退出循环

            if event.type == pygame.KEYDOWN:  # 检查键盘按下事件
                if event.key == pygame.K_1:  # 按下数字1键
                    fireworks.append(Firework())  # 添加一个新的烟花
                if event.key == pygame.K_2:  # 按下数字2键
                    fireworks.extend(Firework() for _ in range(10))  # 添加10个新的烟花

        win.fill((20, 20, 30))  # 清空窗口,填充背景色

        if randint(0, 20) == 1:  # 随机生成新的烟花
            fireworks.append(Firework())  # 添加一个新的烟花

        update(win, fireworks)  # 更新窗口和烟花状态

    pygame.quit()  # 退出 Pygame

main()  # 调用主函数开始程序

想体验OpenAI-o1模型的可以点击这里:pc.aihao123.cn/index.html#…

在这里插入图片描述

源代码

import pygame
from random import randint, uniform, choice
import math

# 定义向量和重力
vector = pygame.math.Vector2
gravity = vector(0, 0.3)  # 模拟重力效果
DISPLAY_WIDTH = DISPLAY_HEIGHT = 800  # 窗口大小

# 定义尾迹颜色
trail_colours = [(45, 45, 45), (60, 60, 60), (75, 75, 75),
                 (125, 125, 125), (150, 150, 150)]
dynamic_offset = 1  # 动态尾迹偏移
static_offset = 5  # 静态尾迹偏移

class Firework:
    def __init__(self):
        # 初始化烟花的颜色和粒子
        self.colour = tuple(randint(0, 255) for _ in range(3))  # 随机颜色
        self.colours = [tuple(randint(0, 255) for _ in range(3)) for _ in range(3)]  # 粒子颜色
        self.firework = Particle(randint(0, DISPLAY_WIDTH), DISPLAY_HEIGHT, True, self.colour)  # 创建烟花粒子
        self.exploded = False  # 标记烟花是否已爆炸
        self.particles = []  # 存储爆炸后的粒子
        self.min_max_particles = vector(200, 300)  # 粒子数量范围

    def update(self, win):
        # 更新烟花状态
        if not self.exploded:
            self.firework.apply_force(gravity)  # 应用重力
            self.firework.move()  # 移动烟花粒子
            for tf in self.firework.trails:
                tf.show(win)  # 显示尾迹

            self.show(win)  # 显示烟花

            if self.firework.vel.y >= 0:  # 检查烟花是否达到最高点
                self.exploded = True  # 标记为已爆炸
                self.explode()  # 执行爆炸

        else:
            # 更新爆炸后的粒子
            for particle in self.particles:
                particle.apply_force(vector(gravity.x + uniform(-0.1, 0.1), gravity.y / 2 + uniform(0.01, 0.08)))
                particle.move()  # 移动粒子
                for t in particle.trails:
                    t.show(win)  # 显示粒子的尾迹
                particle.show(win)  # 显示粒子

    def explode(self):
        # 生成爆炸后的粒子
        amount = randint(int(self.min_max_particles.x), int(self.min_max_particles.y))
        self.particles.extend(
            Particle(self.firework.pos.x, self.firework.pos.y, False, self.colours) for _ in range(amount)
        )

    def show(self, win):
        # 在窗口中绘制烟花
        pygame.draw.circle(win, self.colour, (int(self.firework.pos.x), int(self.firework.pos.y)), self.firework.size)

    def remove(self):
        # 移除已标记的粒子
        self.particles = [p for p in self.particles if not p.remove]
        return self.exploded and not self.particles  # 返回是否可以移除烟花

class Particle:
    def __init__(self, x, y, firework, colour):
        # 初始化粒子的属性
        self.firework = firework
        self.pos = vector(x, y)  # 当前粒子位置
        self.origin = vector(x, y)  # 粒子起始位置
        self.radius = 20  # 粒子半径
        self.remove = False  # 标记粒子是否需要移除
        self.explosion_radius = randint(10, 25)  # 随机爆炸半径
        self.life = 0  # 粒子生命周期
        self.acc = vector(0, 0)  # 粒子加速度
        # 创建尾迹
        self.trails = [Trail(i, 5 if firework else randint(2, 4), firework) for i in range(5)]
        self.prev_posx = [-10] * 10  # 存储前10帧的x坐标
        self.prev_posy = [-10] * 10  # 存储前10帧的y坐标

        # 根据粒子类型设置速度、大小和颜色
        if self.firework:
            self.vel = vector(0, -randint(17, 20))  # 烟花粒子向上发射
            self.size = 5  # 烟花粒子大小
            self.colour = colour  # 烟花颜色
        else:
            # 普通粒子随机速度和大小
            self.vel = vector(uniform(-2, 2), uniform(-2, 2))  # 随机初始速度
            self.vel *= randint(10, self.explosion_radius + 5)  # 根据爆炸半径调整速度
            self.size = randint(3, 5)  # 随机粒子大小
            self.colour = choice(colour)  # 从颜色列表中随机选择颜色

    def apply_force(self, force):
        # 应用外力到粒子的加速度
        self.acc += force

    def move(self):
        # 更新粒子的位置和状态
        if not self.firework:
            self.vel *= 0.9  # 普通粒子速度衰减

        self.vel += self.acc  # 更新速度
        self.pos += self.vel  # 更新位置
        self.acc *= 0  # 重置加速度

        # 检查普通粒子是否超出爆炸半径
        if self.life == 0 and not self.firework:
            distance = self.pos.distance_to(self.origin)
            if distance > self.explosion_radius:
                self.remove = True  # 超出范围则标记为移除

        self.decay()  # 处理粒子的衰减
        self.trail_update()  # 更新尾迹
        self.life += 1  # 增加生命周期

    def show(self, win):
        # 在窗口中绘制粒子
        pygame.draw.circle(win, self.colour, (int(self.pos.x), int(self.pos.y)), self.size)

    def decay(self):
        # 根据粒子的生命周期决定是否移除
        if 70 > self.life > 20:  # 在20到70之间的粒子
            if randint(0, 30) == 0:  # 有小概率移除
                self.remove = True
        elif self.life > 70:  # 超过70的粒子
            if randint(0, 5) == 0:  # 有更高概率移除
                self.remove = True

    def trail_update(self):
        # 更新粒子的尾迹位置
        self.prev_posx.pop()  # 移除最旧的x坐标
        self.prev_posx.insert(0, int(self.pos.x))  # 插入当前x坐标
        self.prev_posy.pop()  # 移除最旧的y坐标
        self.prev_posy.insert(0, int(self.pos.y))  # 插入当前y坐标

        # 更新每个尾迹的位置
        for n, t in enumerate(self.trails):
            if t.dynamic:
                t.get_pos(self.prev_posx[n + dynamic_offset], self.prev_posy[n + dynamic_offset])
            else:
                t.get_pos(self.prev_posx[n + static_offset], self.prev_posy[n + static_offset])

class Trail:
    def __init__(self, n, size, dynamic):
        # 初始化尾迹的属性
        self.pos_in_line = n  # 尾迹在粒子尾迹中的位置索引
        self.pos = vector(-10, -10)  # 尾迹的初始位置,设置为无效值
        self.dynamic = dynamic  # 布尔值,指示尾迹是否为动态
        # 根据尾迹的动态性设置颜色
        self.colour = trail_colours[n] if dynamic else (255, 255, 200)  # 动态尾迹使用预定义颜色,静态尾迹为淡黄色
        # 根据尾迹的动态性和位置设置大小
        self.size = max(size - n // 2, 0) if dynamic else max(size - 2, 0)  # 动态尾迹大小随位置变化,静态尾迹大小固定

    def get_pos(self, x, y):
        # 更新尾迹的位置
        self.pos = vector(x, y)  # 将尾迹位置设置为传入的坐标

    def show(self, win):
        # 在窗口中绘制尾迹
        pygame.draw.circle(win, self.colour, (int(self.pos.x), int(self.pos.y)), self.size)  # 绘制尾迹的圆形

def update(win, fireworks):
    # 更新所有烟花的状态并在窗口中绘制
    for fw in fireworks:
        fw.update(win)  # 调用每个烟花的更新方法
        if fw.remove():  # 检查烟花是否需要移除
            fireworks.remove(fw)  # 从列表中移除烟花

    pygame.display.update()  # 更新窗口显示

def main():
    pygame.init()  # 初始化 Pygame
    pygame.display.set_caption("Fireworks in Pygame")  # 设置窗口标题
    win = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))  # 创建窗口
    clock = pygame.time.Clock()  # 创建时钟对象以控制帧率

    fireworks = [Firework() for _ in range(2)]  # 初始化两个烟花对象
    running = True  # 运行标志

    while running:
        clock.tick(60)  # 控制游戏循环的帧率为60帧每秒
        for event in pygame.event.get():  # 处理事件
            if event.type == pygame.QUIT:  # 检查是否关闭窗口
                running = False  # 设置运行标志为 False,退出循环

            if event.type == pygame.KEYDOWN:  # 检查键盘按下事件
                if event.key == pygame.K_1:  # 按下数字1键
                    fireworks.append(Firework())  # 添加一个新的烟花
                if event.key == pygame.K_2:  # 按下数字2键
                    fireworks.extend(Firework() for _ in range(10))  # 添加10个新的烟花

        win.fill((20, 20, 30))  # 清空窗口,填充背景色

        if randint(0, 20) == 1:  # 随机生成新的烟花
            fireworks.append(Firework())  # 添加一个新的烟花

        update(win, fireworks)  # 更新窗口和烟花状态

    pygame.quit()  # 退出 Pygame

main()  # 调用主函数开始程序



更多提效文章

【IDER、PyCharm】免费AI编程工具完整教程:ChatGPT Free - Support Key call AI GPT-o1 Claude3.5

【OpenAI】获取OpenAI API KEY的两种方式,开发者必看全方面教程!

【Cursor】揭秘Cursor:如何免费无限使用这款AI编程神器?

结尾

通过本教程,你已经掌握了如何使用Pygame创建一个简单而又绚丽的烟花效果。这个项目不仅可以帮助你理解Pygame的基本用法,还能为你在游戏开发中实现复杂的视觉效果提供灵感。希望你能在此基础上,继续探索更多有趣的项目。🎇

如果你对Pygame或游戏开发有任何疑问,欢迎在评论区留言,我们将共同探讨。祝你在编程的旅程中不断进步!