In these notes you will learn:
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.
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; } } }
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:
Declare the ArrayList variable:
ArrayList<Shard> shards; // initially null
Create the (empty) array:
shards = new ArrayList<Shard>();
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.
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:
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; } } }
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.
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; } } }
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); }