Moving things in shoebot - simple particles...

Wed 20 March 2013
By stu

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()

```