Created
November 27, 2021 06:04
-
-
Save f0ursqu4r3/9aaf810377c7b94948a5230735672253 to your computer and use it in GitHub Desktop.
Not very efficient particle emitter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import pygame | |
from pygame import Vector2 | |
class ParticleEmitter: | |
__slots__ = [ | |
'pos', 'vel', 'speed', | |
'particles', 'age', 'spawn_rate', | |
'last_spawn', 'shape', 'particle_class', | |
'particle_kwargs', 'active', | |
'deactivate_after_burst', 'debug' | |
] | |
class Shape: | |
pass | |
class Point(Shape): | |
def __init__(self, spread=0): | |
self.spread = spread | |
class Line(Shape): | |
def __init__(self, vec): | |
self.vec = Vector2(vec) | |
class Circle(Shape): | |
def __init__(self, radius): | |
self.radius = radius | |
class Rectangle(Shape): | |
def __init__(self, size): | |
self.size = Vector2(size) | |
def __init__(self, pos, vel=None, speed=None, | |
spawn_rate=None, shape=None, | |
particle_class=None, particle_kwargs=None, | |
debug=False): | |
self.pos = Vector2(pos) | |
self.vel = Vector2(vel).normalize() if vel else None | |
self.speed = speed | |
self.spawn_rate = spawn_rate if spawn_rate is not None else 10 | |
self.last_spawn = 0 | |
self.shape = shape if shape is not None and isinstance( | |
shape, self.Shape) else self.Point() | |
self.particle_class = particle_class or Particle | |
self.particle_kwargs = particle_kwargs or {} | |
self.active = True | |
self.deactivate_after_burst = False | |
self.debug = debug | |
self.particles = [] | |
self.age = 0 | |
def update(self, dt): | |
self.last_spawn += dt | |
spawn_rate = (1/self.spawn_rate) if self.spawn_rate > 0 else 0 | |
if spawn_rate and self.last_spawn >= spawn_rate: | |
for _ in range(max(1, int(dt/spawn_rate))): | |
self.create_particle() | |
self.last_spawn = 0 | |
for particle in self.particles[:]: | |
particle.update(dt) | |
if not particle.alive: | |
self.particles.remove(particle) | |
if self.deactivate_after_burst and not self.particles: | |
self.active = False | |
self.age += dt | |
def create_particle(self): | |
vel = self.vel | |
if not vel: | |
vel = Vector2( | |
random.random() - .5, | |
random.random() - .5 | |
).normalize() | |
speed = self.speed or random.randint(5, 10) | |
pos = self.pos | |
if isinstance(self.shape, self.Point): | |
# alter the velocity angle +/- point spread | |
vel = vel.rotate( | |
random.uniform(-self.shape.spread, self.shape.spread)) | |
if isinstance(self.shape, self.Line): | |
length = self.shape.vec.length() | |
particle_pos = self.shape.vec.normalize() * (random.random() * length) | |
half = self.shape.vec / 2 | |
pos = pos - half + particle_pos | |
elif isinstance(self.shape, self.Circle): | |
pos = pos + Vector2( | |
random.random() - .5, | |
random.random() - .5 | |
).normalize() * random.random() * self.shape.radius | |
elif isinstance(self.shape, self.Rectangle): | |
center = self.shape.size/2 | |
point = Vector2( | |
random.random() * self.shape.size.x, | |
random.random() * self.shape.size.y | |
) | |
pos = pos - center + point | |
self.particles.append( | |
self.particle_class(pos, vel * speed, **self.particle_kwargs) | |
) | |
def burst(self, count=None, deactivate_after=False): | |
if isinstance(count, list): | |
count = range(*count) if len(count) == 2 else count[0] | |
else: | |
count = range(random.randint(5, 10) | |
) if count is None else range(count) | |
for _ in count: | |
self.create_particle() | |
self.deactivate_after_burst = deactivate_after | |
def draw(self, surface): | |
if self.debug: | |
c = (0, 200, 200) | |
if isinstance(self.shape, self.Point): | |
surface.set_at([*map(int, self.pos)], c) | |
elif isinstance(self.shape, self.Line): | |
half = self.shape.vec/2 | |
start = self.pos - half | |
end = self.pos + half | |
pygame.draw.line(surface, c, start, end) | |
elif isinstance(self.shape, self.Circle): | |
pygame.draw.circle(surface, c, self.pos, self.shape.radius, 1) | |
elif isinstance(self.shape, self.Rectangle): | |
center = self.shape.size/2 | |
pygame.draw.rect( | |
surface, c, (self.pos - center, self.shape.size), 1) | |
for particle in self.particles: | |
particle.draw(surface) | |
class Particle: | |
__slots__ = [ | |
'pos', 'vel', 'age', | |
'lifetime', 'color', 'speed' | |
] | |
def __init__(self, pos, vel): | |
self.pos = Vector2(pos) | |
self.vel = Vector2(vel) | |
self.age = 0 | |
self.lifetime = 3 | |
self.color = [0, 200, 0, 255] | |
self.speed = random.randint(5, 10) | |
@property | |
def alive(self): | |
return self.age < self.lifetime | |
def update(self, dt): | |
self.pos += self.vel * self.speed * dt | |
self.age += dt | |
def draw(self, surface): | |
surface.set_at([*map(int, self.pos)], self.color) | |
class FadeOutParticle(Particle): | |
def __init__(self, pos, vel, color): | |
super().__init__(pos, vel) | |
self.color = color | |
self.lifetime = .5 | |
self.speed = random.randint(1, 3) | |
self.surf = pygame.Surface((1, 1)) | |
self.surf.fill(self.color) | |
def update(self, dt): | |
super().update(dt) | |
self.surf.set_alpha(max(0, (1 - (self.age / self.lifetime)) * 255)) | |
def draw(self, surface): | |
surface.blit(self.surf, self.pos) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment