(Extra) Creating an Explosion with a Particle System¶
In these notes you will learn:
- How to design an “explosion” consisting of multiple particles.
Introduction¶
You’ve no doubt seen lots of different kinds of explosions in video games, and so in this note we will try creating our own explosion. There’s no standard kind of explosion, so we will make things up as we go.
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.
Taking an OOP approach, this suggests that we ought to write a class to represent the individual particles of the explosion. Lets make them circles, so in addition to the position and velocity we will store a radius and radius change rate:
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); // 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() {
// ...
}
} // class Shard
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
.
The constructor calls the reset
function, which randomly assigns the
variables of the shard. We could have put the code in reset
directly into
the constructor, but by putting it in its own function it is much easier to
re-use particles for later explosions.
Now lets write the render()
and update()
functions. The render()
function is relatively simple:
void render() {
if (visible) {
ellipse(x, y, 2 * r, 2 * r);
}
}
Note
We could have written the if-statement in render()
like
this:
if (visible == true) {
ellipse(x, y, 2 * r, 2 * r);
}
This is equivalent to writing if (visible)
. You can use whatever
version you prefer, but keep in mind that most experienced programmers
prefer if (visible)
because it is shorter and still relatively
readable.
The update()
function needs a little bit more code. The shards move
outwards from the center of the explosion and get smaller as they move. Once
the radius of a shard is less than 1, it becomes invisible:
void update() {
x += dx;
y += dy;
r += dr;
if (r < 1) { // make the shard disappear when it is very small
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); // 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() {
if (visible) {
ellipse(x, y, 2 * r, 2 * r);
}
}
void update() {
x += dx;
y += dy;
r += dr;
if (r < 1) { // when the shard gets too small, make it disappear
visible = false;
}
}
} // class Shard
Testing a Single Explosion¶
Now that we have written the Shard
class, lets write a demo that make an
explosion wherever the user clicks:
void setup() {
size(500, 500);
smooth();
noStroke();
fill(255, 255, 0); // yellow
// ...
}
void draw() {
// draw an explosion, or nothing if the explosion is not occurring ...
}
void mousePressed() {
// cause an explosion centered at the mouse pointer
}
For simplicity, lets permit only one explosion on the screen at a time. Thus
we only need to store one ArrayList
of Shard
objects:
final int NUM_SHARDS = 100; // number of particles in an explosion
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()
:
final int NUM_SHARDS = 100; // number of particles in an explosion
ArrayList<Shard> shards; // initialized to null
void setup() {
size(500, 500);
smooth();
noStroke();
fill(255, 255, 0); // yellow
// an explosion consists of many small shards
shards = new ArrayList<Shard>();
int i = 0;
while (i < NUM_SHARDS {
Shard s = new Shard();
shards.add(s);
++i;
}
}
Note we follow a standard pattern for using an ArrayList
of objects:
Declare the
ArrayList
variable:ArrayList<Shard> shards; // initially null
This just creates an
ArrayList
variable: it doesn’t create theArrayList
itself, and it doesn’t create the objects inside it. By default,ArrayList
variables are given the initial value ofnull
.Create the
ArrayList
:shards = new ArrayList<Shard>();
The expression
new ArrayList<Shard>()
creates an emptyArrayList
with nothing in it yet.Add the objects:
while (i < NUM_SHARDS { Shard s = new Shard(); shards.add(s); ++i; }
Every time the body of the loop is executed, a new
Shard
is created and added to the right end ofshards
.
The code for draw()
calls the render
and update
function for each
object:
void draw() {
background(0);
for(Shard s : shards) {
s.render();
s.update();
}
}
Lastly, lets write the code for mousePressed
. We decided above that an
explosion occurs when and where the user clicks:
void mousePressed() {
for(Shard s : shards) {
s.reset();
s.x = mouseX;
s.y = mouseY;
}
}
Recall that mousePressed()
is automatically called every time the
mouse is clicked.
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.
The Program So Far¶
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); // 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() {
if (visible) {
ellipse(x, y, 2 * r, 2 * r);
}
}
void update() {
x += dx;
y += dy;
r += dr;
if (r < 1) { // when the shard gets too small, make it disappear
visible = false;
}
}
} // class Shard
//////////////////////////////////////////////////////////////////////
final int NUM_SHARDS = 100; // number of particles in an explosion
ArrayList<Shard> shards; // initialized to null
void setup() {
size(500, 500);
smooth();
noStroke();
fill(255, 255, 0); // yellow
// an explosion consists of many small shards
shards = new ArrayList<Shard>();
for(int i = 0; i < NUM_SHARDS; ++i) {
Shard s = new Shard();
shards.add(s);
}
}
void draw() {
background(0);
for(Shard s : shards) {
s.render();
s.update();
}
}
void mousePressed() {
for(Shard s : shards) {
s.reset();
s.x = mouseX;
s.y = mouseY;
}
}
An Explosion 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 them in another program, then its best to create a class to handle the explosion.
So lets create a class called Explosion
:
class Explosion {
ArrayList<Shard> shards;
Explosion(int numParticles, float x, float y) {
shards = new ArrayList<Shard>();
int i = 0;
while (i < numParticles) {
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();
}
}
} // class Explosion
This contains all the code from our explosion demo program in the previous section, but re-organized. Now the demo program can be easily re-written:
Explosion e;
void setup() {
size(500, 500);
smooth();
noStroke();
fill(255, 255, 0); // yellow
}
void draw() {
background(0);
if (e == null) return;
e.render();
e.update();
}
void mousePressed() {
e = new Explosion(50, mouseX, mouseY);
}
We’ve introduced a new idea in draw()
:
if (e == null) return;
This if-statement immediately exits the function if variable e
does not refer 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()
are both errors
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 a run-time error when it tries to execute
e.render()
.
Adding Gravity¶
Lets add gravity to our explosions so that as the particles fly away from the center they also get pulled downwards by a gravitational force.
We’ll do this by adding gravity to 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
boolean visible;
Shard() {
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;
}
void render() {
if (visible) {
ellipse(x, y, 2 * r, 2 * r);
}
}
void update() {
x += dx;
y += dy;
r += dr;
dy += gravity;
if (r < 1) { // when the shard gets too small, make it disappear
visible = false;
}
}
} // class Shard
What we’ve done here is add a new variable called gravity
that we set to
random value in the constructor. Then in update
we add gravity
to
dy
.
The result is quite nice: the particles now get pulled downwards as they fly out.
Programming Questions¶
- Modify the
Shard
class so that shards are drawn with a color. Set the color randomly in the constructor. Test your changes with one of the sample programs. - Create a demo program that allows 2 (or more, if you wish) explosions to appear on the screen at the same time.
- Make a demo program that initially displays a balloon slowly
floating up the screen. When the mouse pointers clicks on the
balloon, it pops (disappears) and explodes. Use an
Explosion
object to make the explosion. - Create a fireworks demo.
The Program¶
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
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;
}
void render() {
if (visible) {
ellipse(x, y, 2 * r, 2 * r);
}
}
void update() {
x += dx;
y += dy;
r += dr;
dy += gravity;
if (r < 1) { // when the shard gets too small, make it disappear
visible = false;
}
}
} // class Shard
/////////////////////////////////////////////////////////////////////////
class Explosion {
ArrayList<Shard> shards;
Explosion(int numParticles, float x, float y) {
shards = new ArrayList<Shard>();
for(int i = 0; i < numParticles; ++i) {
Shard s = new Shard();
s.x = x;
s.y = y;
shards.add(s);
}
}
void render() {
for(Shard s : shards) {
s.render();
}
}
void update() {
for(Shard s : shards) {
s.update();
}
}
} // class Explosion
/////////////////////////////////////////////////////////////////////////
Explosion e;
void setup() {
size(500, 500);
smooth();
noStroke();
fill(255, 255, 0); // yellow
}
void draw() {
background(0);
if (e == null) return;
e.render();
e.update();
}
void mousePressed() {
e = new Explosion(50, mouseX, mouseY);
}