29. Creating an Explosion with a Particle System

In these notes you will learn:

  • How to design an “explosion” that consists of multiple particles.

29.1. Introduction

You’ve no doubt seen lots of different kinds of explosions in video games. In this note we will create our own explosion, using particle systems.

29.2. Shards: The Pieces of an Explosion

What is an explosion? Roughly speaking, it is a collection of particles that begin at the same point, and then quickly move outwards. As the shards move away from the source of the explosion, they decrease in size until they finally disappear.

Taking an OOP approach, this suggests that we should write a class to represent the individual parts of the explosion. We will think of a single shard as a circle. Each shard will have a random size and direction:

class Shard {
    float x, y    // position
    float dx, dy; // velocity
    float r;      // radius
    float dr;     // rate of change of radius

    color shardColor;

    boolean visible;

    Shard() {
        reset();
    }

    void reset() {
        dx = random(-2.0, 2.0);   // try changing these
        dy = random(-2.0, 2.0);   // initial random
        r = random(4.0, 10.0);    // values to get different
        dr = random(-0.5, 0.0);   // kinds of explosions
        visible = true;
    }

    void render() {
        // ...
    }

    void update() {
        // ...
    }
}

The variable visible controls whether or not the shard is to be drawn on the screen: we will only draw the shard if visible is true.

Notice that the constructor calls the reset function, which randomly assigns the variables of the shard. We could have put the code in reset into the constructor, but by putting it in its own function it is much easier to re-use particles for later explosions.

Now let’s write the render() and update() functions. For now all of the shards will have the same colour. This makes the render() function relatively simple:

void render() {
    if (visible) {
        ellipse(x, y, 2 * r, 2 * r);
    }
}

Notice that we only draw the shard if it is visible.

Note

We could have written the if-statement if render() like this:

if (visible == true) {
    //...
}

This is equivalent to the way it is written above (why?).

The update() function is responsible for modelling the Shard object’s motion. Recall that we think of the explosion as starting at a single point and as shards get farther from the centre, they get smaller.

void update() {
    x += dx;
    y += dy;
    r += dr;

    if (r < 1) { // play with this value to see what happens.
        visible = false;
    }
}

Here is the final Shard class:

class Shard {
    float x, y;     // position
    float dx, dy;   // velocity
    float r;        // radius
    float dr;       // rate of change of radius

    boolean visible;

    Shard() {
        reset();
    }

    void reset() {
        dx = random(-2.0, 2.0);
        dy = random(-2.0, 2.0);
        r = random(4.0, 10.0);
        dr = random(-0.5, 0.0);
        visible = true;
    }

    void render() {
        if (visible) {
            ellipse(x, y, 2 * r, 2 * r);
        }
    }

    void update() {
        x += dx;
        y += dy;
        r += dr;

        if (r < 1) {
            visible = false;
        }
    }
}

29.3. Animating a Single Explosion

Now that we have written the Shard class, let’s write a demo that makes an explotion where ever the user clicks. Since all of our shards have the same colour, we can save some time by setting the fill and stroke once in setup:

void setup() {
    size(500, 500);
    noStroke();
    fill(255, 155, 0); // Yellow

    //...
}

void draw() {
    // draw an explosion, or nothing if the explosion is not occurring
}

void mouseClicked() {
    // cause an explosion centred at the mouse pointer.
}

For simplicity, we’ll permit only one explosion on the screen at a time. This means we only need to store one ArrayList of Shard objects:

final int NUM_SHARDS = 100;
ArrayList<Shard> shards;

This creates a variable of type ArrayList<Shard>. It does not create the ArrayList, nor the Shard objects within it. We do that in setup():

void setup() {
    size(500, 500);
    noStroke();
    fill(255, 155, 0);

    // an explosion consists of many small shards
    shards = ArrayList<Shard>();
    int i = 0;
    while (i < NUM_SHARDS) {
        Shard s = new Shard();
        shards.add(s);
        ++i;
    }
}

Notice that in the last few lectures, we’ve seen a standard pattern for using arrays of objects:

  1. Declare the ArrayList variable:

    ArrayList<Shard> shards; // initially null
    
  2. Create the (empty) array:

    shards = new ArrayList<Shard>();
    
  3. Add the objects:

    while (i < NUM_SHARDS) {
        Shard s = new Shard();
        shards.add(s);
        ++i;
    }
    

As before, the approach we’re using means draw() is very simple:

void draw {
    background(0); // Try placing this line in mouseClicked instead.
    for (Shard s : shards) {
        s.render();
        s.update();
    }
}

Finally, mouseClicked(), which is called whenever the user clicks the mouse button, needs to start the explosion:

void mouseClicked() {
    for (Shard s : shards) {
        s.reset();
        s.x = mouseX;
        s.y = mouseY;
    }
}

Notice that this code re-uses the particles from the explosion instead of creating new ones. This is to save memory. If you use a lot of explosions in a program than the particles could take up a significant chunk of memory that could eventually slow your program down.

29.4. Adding More Colours to the Explosion

You might want to add some colour variation to your explosion. One thing we can do is simply decide the colour of each shard randomly:

class Shard {
    // .. earlier code

    color shardColor;

    // constructor is unchanged.

    void reset() {
        // earlier code

        shardColor = color(255, random(155, 255));
    }

    void render() {
        if (visible) {
            float diam = 2 * r;
            fill(shardColor);
            ellipse(x, y, diam, diam);
        }
    }
}

Here we have the shard colour vary randomly between orange and yellow.

If we look at a dew pictures if explosions, however, one detail might pop up. The centre of the explosion is a very hot yellow, and the colours “cool off” as we get farther away. Let’s modify our Shard class to model this behaviour.

The trick is to realize that to get variation from yellow to orange, we need to change the amount of green in the shardColor:

Hellfire

We will keep track of the amount of green that we need as a class variable:

class Shard {
    //..

    int greenAmount;
    color shardColor;

    // ..

    void reset() {
        // ..

        greenAmount = 255;
        shardColor = color(255, greenAmount, 0);
    }
}

Now as we update the shard’s attributes we will also update greenAmount, taking care that it doesn’t fall under zero:

void update() {
    // ...

    greenAmount -= 1;
    greenAmount = constrain(greenAmount, 0, 255); // or max(greenAmount, 0);
    shardColor = color(255, greenAmount, 0);

    //..

}

Here is the entire modified Shard class:

class Shard {
    float x, y;
    float dx, dy;
    float r;
    float dr;

    color shardColor;
    int greenAmount;
    boolean visible;

    Shard() {
        reset();
    }

    void reset() {
        dx = random(-2.0, 2.0);   // try changing these
        dy = random(-2.0, 2.0);   // initial random
        r = random(4.0, 10.0);    // values to get different
        dr = random(-0.5, 0.0);   // kinds of explosions
        visible = true;

        greenAmount = 255;
        shardColor = color(255, greenAmount, 0);
    }

    void render() {
        if (visible) {
            fill(shardColor);
            ellipse(x, y, 2 * r, 2 * r);
        }
    }

    void update() {
        x += dx;
        y += dy;
        r += dr;

        greenAmount--;
        greenAmount = constrain(greenAmount, 100, 255);
        shardColor = color(255, greenAmount, 0);

        if (r < 1) {
            visible = false;
        }
    }
}

29.5. An Explostion Class

Our demo program draws a single explosion, which is useful for testing. However, if you want multiple explosions, or if you want to use then in another program, then its best to create a classto handle the explosion.

As before, our strategy will be to encapsulate everything we need in order to create an explosion in a class:

class Explosion {
    ArrayList<Shard> shards;

    // Create explosion with numShards shards at (x, y)
    Explosion(int numShards, float x, float y) {
        shards = new ArrayList<Shard>();
        int i = 0;
        while (i < numShards) {
            Shard s = new Shard();
            s.x = x;
            s.y = y;
            shards.add(s);
            ++i;
        }
    }

    void render() {
        for (Shard s : shards) {
            s.render();
        }
    }

    void update() {
        for (Shard s : shards) {
            s.update();
        }
    }
}

This contains all of the code from our previous program, but re-organized. Now the rest of the program can be easily re-written:

// class Shard and Explosion here

Explosion e;

void setup() {
    size(500, 500);
    noStroke();
}

void draw() {
    background(0);
    if (e != null) {
        e.render();
        e.update();
    }
}

void mousePressed() {
    e = new Explosion(100, mouseX, mouseY);
}

Notice we’re doing something new in draw():

if (e != null) { ... }

This if-statement only executes if variable e refers to an object.

Recall that when you create an object variable like e, it’s initialized with the special value null. When e is null, the statements e.render and e.update() because null is not an object and so doesn’t have any functions named render() or update(). Without this if-statement, the program would crash with an error.

29.6. Adding Gravity

Let’s add gravity to our explosions so that as the particles fly away from the centre, they also get pulled downwards by a gravitational force.

We will have gravity acting on each Shard:

class Shard {
    float x, y;    // position
    float dx, dy;  // velocity
    float r;       // radius
    float dr;      // rate of change of radius
    float gravity; // force of gravity

    color shardColor;
    int greenAmount;
    boolean visible;

    Shard() {
        reset();
    }

    void reset() {
        dx = random(-2.0, 2.0);   // try changing these
        dy = random(-2.0, 2.0);   // initial random
        r = random(4.0, 10.0);    // values to get different
        dr = random(-0.5, 0.0);   // kinds of explosions
        gravity = random(0.1, 0.2);
        visible = true;

        greenAmount = 255;
        shardColor = color(255, greenAmount, 0);
    }

    void render() {
        if (visible) {
            fill(shardColor);
            ellipse(x, y, 2 * r, 2 * r);
        }
    }

    void update() {
        x += dx;
        y += dy;
        r += dr;

        greenAmount--;
        greenAmount = constrain(greenAmount, 100, 255);
        shardColor = color(255, greenAmount, 0);

        dy += gravity;

        if (r < 1) {   // when the shard gets too small, make it disappear
            visible = false;
        }
    }
}

29.7. Programming Questions

  1. Create a demo program that allows 2 (or more, if you wish) explosions to appear on the screen at the same time.
  2. Add an explosion sound using the Minim library.
  3. Create a fireworks demo.
  4. Add code so each explosion displays a shockwave - a transparent circle that grows outwards from the centre of the explosion.

29.8. The Program

class Shard {
    float x, y;
    float dx, dy;
    float r;
    float dr;
    float gravity;

    color shardColor;
    int greenAmount;
    boolean visible;

    Shard() {
        reset();
    }

    void reset() {
        dx = random(-2.0, 2.0);   // try changing these
        dy = random(-2.0, 2.0);   // initial random
        r = random(4.0, 10.0);    // values to get different
        dr = random(-0.5, 0.0);   // kinds of explosions
        gravity = random(0.01, 0.02);
        visible = true;

        greenAmount = 255;
        shardColor = color(255, greenAmount, 0);
    }

    void render() {
        if (visible) {
            fill(shardColor);
            ellipse(x, y, 2 * r, 2 * r);
        }
    }

    void update() {
        x += dx;
        y += dy;
        r += dr;

        dy += gravity;

        greenAmount--;
        greenAmount = constrain(greenAmount, 100, 255);
        shardColor = color(255, greenAmount, 0);

        if (r < 1) {
            visible = false;
        }
    }
}

class Explosion {
    ArrayList<Shard> shards;

    // Create explosion with numShards shards at (x, y)
    Explosion(int numShards, float x, float y) {
        shards = new ArrayList<Shard>();
        int i = 0;
        while (i < numShards) {
            Shard s = new Shard();
            s.x = x;
            s.y = y;
            shards.add(s);
            ++i;
        }
    }

    void render() {
        for (Shard s : shards) {
            s.render();
        }
    }

    void update() {
        for (Shard s : shards) {
            s.update();
        }
    }
}

Explosion e;

void setup() {
    size(500, 500, P2D);
    noStroke();
    background(0);
    fill(color(0, 255, 0));
    rectMode(CENTER);
    rect(width/2, height / 2, 100, 100);
}

void draw() {
    //background(0);
    if (e != null) {
        e.render();
        e.update();
    }
}

void mousePressed() {
    background(0);
    e = new Explosion(1000, mouseX, mouseY);
}