OK, part 3 - now for something fun - extending parts 1 + 2 into a simple particle system.
Particles, generally means - a lot of things moving around (the particles) and a way to generate them, an "emitter"
Here we're going to take the code from the previous two parts and add a couple of things to make a basic particle system.
Note - shoebot, isn't the fastest; but we do get nice looking results.
Here's a video of our arrows as particles (arrowsplosion!):
So the parts needed are the bits that move 'particles', this is made by extending the Moveable class to give it an hp, some functions to manage the list of particles (when their hp goes below 0 they are removed from the list).
``` {: .brush:python} class Particle(Moveable): def init(self, hp = 250, kwargs): Moveable.__init__(self, draw_func = self.draw_func, kwargs) self.hp = hp
def update(self):
super(Particle, self).update()
self.hp -=1
return self.hp > 0
def draw_func(self):
a = 1.0
if 0 < self.hp < 250:
a = self.hp / 250.0
arrow(0, 0, 80, fill=(1, 0, 0, a))
```
The last part is particle creation, we create a function random_emitter, this just adds a particle to the list of particles.
{: .brush:python}
def random_emitter(particles):
if random() > 0.1:
p = Particle(
velocity = random() * 2.0,
angle = random() * 360,
controller = inertia_controller)
particles.append(p)
the draw function is modified to call these:
``` {: .brush: .python} def update_particles(particles): ''' Update particles
return: list of particles to update
'''
part = []
for p in particles:
if p.update():
part.append(p)
return part
def draw(): global particles
random_emitter(particles)
particles = update_particles(particles)
for p in particles:
p.draw()
```
Complete listing.
``` {: .brush:python} from random import random
from planar.py import Vec2, Affine from collections import namedtuple
class Moveable(object): """ Moveable object.
Controller functions manage the movement and are called on 'update'
"""
def __init__(self, pos = None, velocity = None, angle = None, controller = None, controllers = None, draw_func = None):
self.pos = pos or Vec2(WIDTH / 2, HEIGHT / 2)
self.velocity = velocity or 0.0
self.angle = angle or 0
self.draw_func = draw_func
if controller:
self.controllers = [ controller ]
else:
self.controllers = []
if controllers:
self.controllers.extend(controllers)
def update(self):
"""
Call all the controllers to update coordinates.
Angles are always wrapped to 360 degrees.
"""
pos, angle, velocity = self.pos, self.angle, self.velocity
for controller in self.controllers:
pos, angle, velocity = controller(pos, angle % 360, velocity)
self.pos, self.angle, self.velocity = pos, angle % 360, velocity
self.pos += Vec2.polar(angle = angle, length = velocity)
def draw(self):
push()
translate(self.pos.x, self.pos.y)
rotate(-self.angle) # nodebox1/shoebot rotation is anticlockwise
self.draw_func()
pop()
class Particle(Moveable): def init(self, hp = 250, kwargs): Moveable.__init__(self, draw_func = self.draw_func, kwargs) self.hp = hp
def update(self):
super(Particle, self).update()
self.hp -=1
return self.hp > 0
def draw_func(self):
a = 1.0
if 0 < self.hp < 250:
a = self.hp / 250.0
arrow(0, 0, 80, fill=(1, 0, 0, a))
def key_controller(pos, angle, velocity): if keydown: if keycode == KEY_UP: velocity += 0.2 elif keycode == KEY_DOWN: velocity -= 0.2 elif keycode == KEY_LEFT: angle -= 5 elif keycode == KEY_RIGHT: angle += 5 elif keycode == KEY_SPACE: velocity *= 0.9 elif keycode == KEY_RETURN: pos = Vec2(WIDTH / 2, HEIGHT / 2) velocity = 0.0 angle = 0 return pos, angle, velocity
def inertia_controller(pos, angle, velocity): return pos, angle, velocity * 0.99
def bounds_controller(pos, angle, velocity): if pos.x < 10 or pos.x > WIDTH: angle = 180 - angle if pos.y < 10 or pos.y > HEIGHT - 10: angle = - angle return pos, angle, velocity
def random_emitter(particles): if random() > 0.1: p = Particle( velocity = random() * 2.0, angle = random() * 360, controller = inertia_controller) particles.append(p)
def update_particles(particles): ''' Update particles
return: list of particles to update
'''
part = []
for p in particles:
if p.update():
part.append(p)
return part
def setup(): global particles speed(60) size(800, 600) transform(mode=CENTER)
particles = []
def draw(): global particles
random_emitter(particles)
particles = update_particles(particles)
for p in particles:
p.draw()
```