"""
This file contains the classes Particle (a single point with
position/acceleration/velocity/lifetime, using numpy for the physics
integration) and ParticleSystem, spawning and managing bursts of them
— explosions, sparks, trails, and similar effects.
Author: Alejandro Mujica (aledrums@gmail.com)
"""
from typing import Callable, List, Optional
import numpy as np
import pygame
[docs]
class Particle:
def __init__(
self,
x: float,
y: float,
ax: float,
ay: float,
life_time: float,
color: pygame.Color,
) -> None:
"""
Set the initial value for a particle
:param x: X position.
:param y: Y position.
:param ax: X acceleration.
:param ay: Y acceleration.
:param life_time: duration in seconds of the particle.
:param color: render color.
"""
# Position
self.x: float = x
self.y: float = y
# Velocity
self.vx: float = 0
self.vy: float = 0
# Acceleration
self.ax: float = ax
self.ay: float = ay
self.life_time: float = life_time
self.color: pygame.Color = color
[docs]
def update(self, dt: float) -> None:
self.vx += self.ax * dt
self.vy += self.ay * dt
self.x += self.vx
self.y += self.vy
[docs]
def render(self, surface: pygame.Surface) -> None:
s = pygame.Surface((4, 4))
s.set_alpha(self.color[3])
pygame.draw.circle(s, self.color, (2, 2), 2)
surface.blit(s, (int(self.x), int(self.y)))
[docs]
class ParticleSystem:
def __init__(
self, x: float, y: float, n: int, on_finish: Optional[Callable[[], None]] = None
) -> None:
"""
Builds a particle system.
:param x: Center x of the system.
:param y: Center y of the system.
:param n: Number of particles.
"""
self.x_mean: float = x
self.y_mean: float = y
self.size: int = n
self.min_life_time: float = 0
self.max_life_time: float = 0
self.timer: float = 0
self.ax1: float = 0
self.ax2: float = 0
self.ay1: float = 0
self.ay2: float = 0
self.x_desv: float = 1
self.y_desv: float = 1
self.colors: List[pygame.Color] = []
self.particles: List[Particle] = []
self.on_finish = on_finish or (lambda: None)
[docs]
def set_life_time(self, minimum: float, maximum: float) -> None:
self.min_life_time = minimum
self.max_life_time = maximum
[docs]
def set_linear_acceleration(
self, x1: float, y1: float, x2: float, y2: float
) -> None:
self.ax1 = x1
self.ay1 = y1
self.ax2 = x2
self.ay2 = y2
[docs]
def set_colors(self, colors: List[pygame.Color]) -> None:
self.colors = colors
[docs]
def set_area_spread(self, rx: float, ry: float) -> None:
self.x_desv = rx
self.y_desv = ry
[docs]
def generate(self) -> None:
for _ in range(self.size):
ax: float = np.random.uniform(self.ax1, self.ax2)
ay: float = np.random.uniform(self.ay1, self.ay2)
px: float = np.random.normal(self.x_mean, self.x_desv)
py: float = np.random.normal(self.y_mean, self.y_desv)
color: pygame.Color = self.colors[np.random.choice(len(self.colors))]
life_time: float = np.random.uniform(self.min_life_time, self.max_life_time)
self.particles.append(Particle(px, py, ax, ay, life_time, color))
[docs]
def update(self, dt: float) -> None:
if len(self.particles) == 0:
return
self.timer += dt
if self.timer >= self.max_life_time:
self.timer = 0
self.particles = []
self.on_finish()
for particle in self.particles:
if self.timer < particle.life_time:
particle.update(dt)
[docs]
def render(self, surface: pygame.Surface) -> None:
for particle in self.particles:
if self.timer < particle.life_time:
particle.render(surface)