【python】基于pygame实现动态粒子爱心

发布于:2025-07-15 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

功能概括

安装依赖

版本1 动态粒子爱心

执行代码

 运行效果

版本2:优化粒子点缀

执行代码

运行效果 

 版本3:增加例子密度

执行代码

运行效果

版本4:改成粉色粒子

 执行代码

运行效果


功能概括

使用 pygame 实现了一个动态的 心形粒子特效动画。主要功能包括:

  • 在屏幕中心通过心形参数方程生成粒子轨迹;

  • 每个粒子具有独立的大小、速度、角度、寿命和随机扰动;

  • 粒子逐渐缩小并淡出,营造浪漫柔和的视觉效果;

  • 采用 asyncio 实现异步主循环,保持帧率稳定在 60 FPS;

  • 自动循环更新粒子,实现持续动态效果;

  • 可兼容 Web 环境(通过对 platform.system() 判断 Emscripten 进行适配)。

【运行效果】

动态粒子爱心-CSDN直播


安装依赖

pip install pygame

版本1 动态粒子爱心

执行代码

import asyncio
import platform
import pygame
import random
import math

# 初始化Pygame
pygame.init()

# 屏幕设置
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Enhanced Heart Particle Effect")

# 颜色定义
WHITE = (255, 255, 255)
RED = (255, 0, 0)
PINK = (255, 192, 203)
DARK_PINK = (255, 105, 180)

# 粒子类
class Particle:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.size = random.uniform(3, 6)
        self.color = random.choice([PINK, DARK_PINK, RED])
        self.speed = random.uniform(0.3, 1.5)
        self.angle = random.uniform(0, 2 * math.pi)
        self.lifetime = random.randint(80, 160)
        self.age = 0
        self.offset_x = random.uniform(-5, 5)
        self.offset_y = random.uniform(-5, 5)

    def update(self):
        # 心形路径的参数方程
        t = self.angle
        scale = 18  # 放大心形
        base_x = scale * 16 * (math.sin(t) ** 3)
        base_y = -(scale * (13 * math.cos(t) - 5 * math.cos(2 * t) - 2 * math.cos(3 * t) - math.cos(4 * t)))
        
        # 平滑移动到目标位置
        self.x += (WIDTH / 2 + base_x + self.offset_x - self.x) * 0.1
        self.y += (HEIGHT / 2 + base_y + self.offset_y - self.y) * 0.1
        
        self.age += 1
        self.size = max(1, self.size * 0.985)  # 更慢的缩小速度
        self.angle += self.speed * 0.02  # 缓慢旋转

    def draw(self, surface):
        if self.age < self.lifetime:
            alpha = max(0, 255 * (1 - self.age / self.lifetime))
            color = (*self.color[:3], int(alpha))
            pygame.draw.circle(surface, color, (int(self.x), int(self.y)), int(self.size))

# 粒子列表
particles = []

def setup():
    global particles
    particles = [Particle(WIDTH / 2, HEIGHT / 2) for _ in range(400)]  # 增加粒子数量

def update_loop():
    global particles
    screen.fill((10, 10, 20))  # 深色背景增强对比
    
    # 更新和绘制粒子
    for particle in particles[:]:
        particle.update()
        particle.draw(screen)
        if particle.age >= particle.lifetime:
            particles.remove(particle)
            particles.append(Particle(WIDTH / 2, HEIGHT / 2))
    
    pygame.display.flip()

async def main():
    setup()
    clock = pygame.time.Clock()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
        update_loop()
        clock.tick(60)
        await asyncio.sleep(1.0 / 60)

if platform.system() == "Emscripten":
    asyncio.ensure_future(main())
else:
    if __name__ == "__main__":
        asyncio.run(main())

 运行效果

动态效果:动态粒子爱心-CSDN直播


版本2:优化粒子点缀

执行代码

import asyncio
import platform
import pygame
import random
import math

# 初始化Pygame
pygame.init()

# 屏幕设置
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Enhanced Heart Particle Effect")

# 颜色定义
WHITE = (255, 255, 255)
RED = (255, 0, 0)
PINK = (255, 192, 203)
DARK_PINK = (255, 105, 180)
SOFT_PINK = (255, 182, 193)

# 粒子类
class Particle:
    def __init__(self, x, y, is_decorative=False):
        self.x = x
        self.y = y
        self.is_decorative = is_decorative
        self.size = random.uniform(2, 5) if is_decorative else random.uniform(3, 7)
        self.color = random.choice([PINK, DARK_PINK, RED, SOFT_PINK])
        self.speed = random.uniform(0.2, 1.2) if not is_decorative else random.uniform(0.1, 0.5)
        self.angle = random.uniform(0, 2 * math.pi)
        self.lifetime = random.randint(100, 200) if not is_decorative else random.randint(50, 150)
        self.age = 0
        self.offset_x = random.uniform(-8, 8) if not is_decorative else random.uniform(-50, 50)
        self.offset_y = random.uniform(-8, 8) if not is_decorative else random.uniform(-50, 50)

    def update(self):
        if not self.is_decorative:
            # 心形路径的参数方程
            t = self.angle
            scale = 20  # 放大心形
            base_x = scale * 16 * (math.sin(t) ** 3)
            base_y = -(scale * (13 * math.cos(t) - 5 * math.cos(2 * t) - 2 * math.cos(3 * t) - math.cos(4 * t)))
            # 平滑移动到目标位置
            self.x += (WIDTH / 2 + base_x + self.offset_x - self.x) * 0.15
            self.y += (HEIGHT / 2 + base_y + self.offset_y - self.y) * 0.15
        else:
            # 点缀粒子随机漂浮
            self.x += random.uniform(-1, 1)
            self.y += random.uniform(-1, 1)
            self.x = max(0, min(WIDTH, self.x))
            self.y = max(0, min(HEIGHT, self.y))

        self.age += 1
        self.size = max(1, self.size * 0.98)  # 更慢的缩小速度
        self.angle += self.speed * 0.015  # 更慢的旋转

    def draw(self, surface):
        if self.age < self.lifetime:
            alpha = max(0, 255 * (1 - self.age / self.lifetime))
            color = (*self.color[:3], int(alpha))
            pygame.draw.circle(surface, color, (int(self.x), int(self.y)), int(self.size))

# 粒子列表
particles = []

def setup():
    global particles
    # 主心形粒子
    particles = [Particle(WIDTH / 2, HEIGHT / 2) for _ in range(600)]  # 增加粒子数量
    # 点缀粒子
    particles.extend([Particle(random.randint(0, WIDTH), random.randint(0, HEIGHT), is_decorative=True) for _ in range(200)])

def update_loop():
    global particles
    screen.fill((10, 10, 20))  # 深色背景增强对比
    
    # 更新和绘制粒子
    for particle in particles[:]:
        particle.update()
        particle.draw(screen)
        if particle.age >= particle.lifetime:
            particles.remove(particle)
            if particle.is_decorative:
                particles.append(Particle(random.randint(0, WIDTH), random.randint(0, HEIGHT), is_decorative=True))
            else:
                particles.append(Particle(WIDTH / 2, HEIGHT / 2))

    pygame.display.flip()

async def main():
    setup()
    clock = pygame.time.Clock()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
        update_loop()
        clock.tick(60)
        await asyncio.sleep(1.0 / 60)

if platform.system() == "Emscripten":
    asyncio.ensure_future(main())
else:
    if __name__ == "__main__":
        asyncio.run(main())

运行效果 


 版本3:增加例子密度

执行代码

import asyncio
import platform
import pygame
import random
import math

# 初始化Pygame
pygame.init()

# 屏幕设置
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Dense 3D Heart Particle Effect")

# 颜色定义
WHITE = (255, 255, 255)
RED = (255, 0, 0)
PINK = (255, 192, 203)
DARK_PINK = (255, 105, 180)
SOFT_PINK = (255, 182, 193)

# 粒子类
class Particle:
    def __init__(self, x, y, is_decorative=False):
        self.x = x
        self.y = y
        self.z = random.uniform(-50, 50) if not is_decorative else random.uniform(-100, 100)
        self.is_decorative = is_decorative
        self.size = random.uniform(2, 5) if is_decorative else random.uniform(2, 7)  # 调整主粒子大小
        self.color = random.choice([PINK, DARK_PINK, RED, SOFT_PINK])
        self.speed = random.uniform(0.2, 1.2) if not is_decorative else random.uniform(0.1, 0.5)
        self.angle = random.uniform(0, 2 * math.pi)
        self.lifetime = random.randint(100, 200) if not is_decorative else random.randint(50, 150)
        self.age = 0
        self.offset_x = random.uniform(-8, 8) if not is_decorative else random.uniform(-50, 50)
        self.offset_y = random.uniform(-8, 8) if not is_decorative else random.uniform(-50, 50)
        self.offset_z = random.uniform(-10, 10) if not is_decorative else random.uniform(-20, 20)

    def update(self):
        if not self.is_decorative:
            # 心形路径的参数方程
            t = self.angle
            scale = 20
            base_x = scale * 16 * (math.sin(t) ** 3)
            base_y = scale * (13 * math.cos(t) - 5 * math.cos(2 * t) - 2 * math.cos(3 * t) - math.cos(4 * t))
            # 透视投影
            fov = 200
            scale_factor = fov / (fov + self.z) if (fov + self.z) != 0 else 1
            proj_x = (base_x + self.offset_x) * scale_factor
            proj_y = (base_y + self.offset_y) * scale_factor
            # 平滑移动
            self.x += (WIDTH / 2 + proj_x - self.x) * 0.15
            self.y += (HEIGHT / 2 - proj_y - self.y) * 0.15
            self.z += (self.offset_z - self.z) * 0.1
        else:
            # 点缀粒子随机漂浮
            self.x += random.uniform(-1, 1)
            self.y += random.uniform(-1, 1)
            self.z += random.uniform(-0.5, 0.5)
            self.x = max(0, min(WIDTH, self.x))
            self.y = max(0, min(HEIGHT, self.y))
            self.z = max(-100, min(100, self.z))

        self.age += 1
        self.size = max(1, self.size * 0.98)
        self.angle += self.speed * 0.015

    def draw(self, surface):
        if self.age < self.lifetime:
            # 计算透视缩放
            fov = 200
            scale_factor = fov / (fov + self.z) if (fov + self.z) != 0 else 1
            adjusted_size = max(1, self.size * scale_factor)
            alpha = max(0, int(255 * (1 - self.age / self.lifetime) * scale_factor))
            # 调整亮度,确保整数并在[0, 255]范围内
            brightness = max(0.5, scale_factor)
            color = (
                min(255, max(0, int(self.color[0] * brightness))),
                min(255, max(0, int(self.color[1] * brightness))),
                min(255, max(0, int(self.color[2] * brightness)))
            )
            # 使用透明表面绘制
            temp_surface = pygame.Surface((int(adjusted_size * 2), int(adjusted_size * 2)), pygame.SRCALPHA)
            pygame.draw.circle(temp_surface, color, (int(adjusted_size), int(adjusted_size)), int(adjusted_size))
            temp_surface.set_alpha(alpha)
            surface.blit(temp_surface, (int(self.x - adjusted_size), int(self.y - adjusted_size)))

# 粒子列表
particles = []

def setup():
    global particles
    particles = [Particle(WIDTH / 2, HEIGHT / 2) for _ in range(1000)]  # 增加到1000个主粒子
    particles.extend([Particle(random.randint(0, WIDTH), random.randint(0, HEIGHT), is_decorative=True) for _ in range(200)])

def update_loop():
    global particles
    screen.fill((10, 10, 20))
    
    # 按z轴排序
    particles.sort(key=lambda p: p.z, reverse=True)
    
    # 更新和绘制
    for particle in particles[:]:
        particle.update()
        particle.draw(screen)
        if particle.age >= particle.lifetime:
            particles.remove(particle)
            if particle.is_decorative:
                particles.append(Particle(random.randint(0, WIDTH), random.randint(0, HEIGHT), is_decorative=True))
            else:
                particles.append(Particle(WIDTH / 2, HEIGHT / 2))

    pygame.display.flip()

async def main():
    setup()
    clock = pygame.time.Clock()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
        update_loop()
        clock.tick(60)
        await asyncio.sleep(1.0 / 60)

if platform.system() == "Emscripten":
    asyncio.ensure_future(main())
else:
    if __name__ == "__main__":
        asyncio.run(main())

运行效果


版本4:改成粉色粒子

 执行代码

import asyncio
import platform
import pygame
import random
import math
import colorsys

# 初始化Pygame
pygame.init()

# 屏幕设置
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Bright Romantic 3D Heart Particle Effect")

# 颜色定义
WHITE = (255, 255, 255)
SOFT_PINK = (255, 182, 193)

# 创意参数
PULSE_INTERVAL = 5000  # 脉冲间隔(毫秒)
EXPLOSION_INTERVAL = 5000  # 爆炸间隔(毫秒)
last_pulse = 0
last_explosion = 0
explosion_particles = []

# 粒子类
class Particle:
    def __init__(self, x, y, is_decorative=False, is_explosion=False):
        self.x = x
        self.y = y
        self.z = random.uniform(-50, 50) if not is_decorative else random.uniform(-100, 100)
        self.is_decorative = is_decorative
        self.is_explosion = is_explosion
        self.size = random.uniform(2, 5) if is_decorative else random.uniform(2, 7) if not is_explosion else random.uniform(1, 3)
        self.color_hue = random.uniform(0.9, 0.1)  # 限制到粉红-红色-紫色区间
        self.speed = random.uniform(0.2, 1.2) if not (is_decorative or is_explosion) else random.uniform(0.1, 0.5)
        self.angle = random.uniform(0, 2 * math.pi)
        self.lifetime = random.randint(100, 200) if not is_decorative else random.randint(50, 150) if not is_explosion else random.randint(30, 60)
        self.age = 0
        self.offset_x = random.uniform(-8, 8) if not (is_decorative or is_explosion) else random.uniform(-50, 50) if is_decorative else random.uniform(-10, 10)
        self.offset_y = random.uniform(-8, 8) if not (is_decorative or is_explosion) else random.uniform(-50, 50) if is_decorative else random.uniform(-10, 10)
        self.offset_z = random.uniform(-10, 10) if not (is_decorative or is_explosion) else random.uniform(-20, 20) if is_decorative else random.uniform(-5, 5)

    def update(self):
        if self.is_explosion:
            # 爆炸粒子向外扩散
            self.x += self.offset_x * 0.1
            self.y += self.offset_y * 0.1
            self.z += self.offset_z * 0.05
            self.size *= 0.95  # 逐渐缩小
        elif not self.is_decorative:
            # 心形路径
            t = self.angle
            scale = 20
            base_x = scale * 16 * (math.sin(t) ** 3)
            base_y = scale * (13 * math.cos(t) - 5 * math.cos(2 * t) - 2 * math.cos(3 * t) - math.cos(4 * t))
            fov = 200
            scale_factor = fov / (fov + self.z) if (fov + self.z) != 0 else 1
            proj_x = (base_x + self.offset_x) * scale_factor
            proj_y = (base_y + self.offset_y) * scale_factor
            self.x += (WIDTH / 2 + proj_x - self.x) * 0.15
            self.y += (HEIGHT / 2 - proj_y - self.y) * 0.15
            self.z += (self.offset_z - self.z) * 0.1
            self.angle += self.speed * 0.015
        else:
            # 点缀粒子
            self.x += random.uniform(-1, 1)
            self.y += random.uniform(-1, 1)
            self.z += random.uniform(-0.5, 0.5)
            self.x = max(0, min(WIDTH, self.x))
            self.y = max(0, min(HEIGHT, self.y))
            self.z = max(-100, min(100, self.z))

        self.age += 1
        self.size = max(1, self.size * 0.98)

    def draw(self, surface):
        if self.age < self.lifetime:
            fov = 200
            scale_factor = fov / (fov + self.z) if (fov + self.z) != 0 else 1
            adjusted_size = max(1, self.size * scale_factor)
            alpha = max(0, int(255 * (1 - self.age / self.lifetime) * scale_factor))
            # 彩虹色渐变,限定爱心色调,增加亮度
            hue = (self.color_hue + pygame.time.get_ticks() / 10000) % 0.2 + 0.9  # 循环在0.9-1.1范围内
            color = colorsys.hsv_to_rgb(hue % 1, 0.4, 0.95)  # 亮度提升到0.95
            color = (int(color[0] * 255), int(color[1] * 255), int(color[2] * 255))
            temp_surface = pygame.Surface((int(adjusted_size * 2), int(adjusted_size * 2)), pygame.SRCALPHA)
            pygame.draw.circle(temp_surface, color, (int(adjusted_size), int(adjusted_size)), int(adjusted_size))
            temp_surface.set_alpha(alpha)
            surface.blit(temp_surface, (int(self.x - adjusted_size), int(self.y - adjusted_size)))

# 粒子列表
particles = []

def setup():
    global particles
    particles = [Particle(WIDTH / 2, HEIGHT / 2) for _ in range(1000)]
    particles.extend([Particle(random.randint(0, WIDTH), random.randint(0, HEIGHT), is_decorative=True) for _ in range(200)])

def update_loop():
    global particles, last_pulse, last_explosion, explosion_particles
    current_time = pygame.time.get_ticks()

    # 绘制动态光晕
    if current_time - last_pulse > PULSE_INTERVAL:
        last_pulse = current_time
    pulse_radius = 50 * (1 + 0.5 * math.sin(current_time / 500))  # 心跳脉冲
    for r in range(int(pulse_radius), 0, -1):
        alpha = int(150 * (r / pulse_radius))  # 增加光晕亮度
        color = (255, 182, 193, alpha)  # 柔和粉色
        pygame.draw.circle(screen, color[:3], (WIDTH // 2, HEIGHT // 2), r, 1)

    # 触发粒子爆炸
    if current_time - last_explosion > EXPLOSION_INTERVAL:
        last_explosion = current_time
        for _ in range(100):
            explosion_particles.append(Particle(WIDTH // 2, HEIGHT // 2, is_explosion=True))

    screen.fill((10, 10, 20))
    
    # 更新和绘制爆炸粒子
    for p in explosion_particles[:]:
        p.update()
        p.draw(screen)
        if p.age >= p.lifetime:
            explosion_particles.remove(p)

    # 按z轴排序
    all_particles = particles + explosion_particles
    all_particles.sort(key=lambda p: p.z, reverse=True)
    
    # 更新和绘制
    for particle in all_particles[:]:
        particle.update()
        particle.draw(screen)
        if particle.age >= particle.lifetime and particle in particles:
            particles.remove(particle)
            if particle.is_decorative:
                particles.append(Particle(random.randint(0, WIDTH), random.randint(0, HEIGHT), is_decorative=True))
            else:
                particles.append(Particle(WIDTH / 2, HEIGHT // 2))

    pygame.display.flip()

async def main():
    setup()
    clock = pygame.time.Clock()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
        update_loop()
        clock.tick(60)
        await asyncio.sleep(1.0 / 60)

if platform.system() == "Emscripten":
    asyncio.ensure_future(main())
else:
    if __name__ == "__main__":
        asyncio.run(main())

运行效果


网站公告

今日签到

点亮在社区的每一天
去签到