(Extra) Creating an Explosion with an Image¶
In these notes you will learn:
- How to design an “explosion” by making an image fade away.
- How to make an object explode.
Introduction¶
In these notes we’ll create a demo program that lets you click on hamburgers as they fall from the sky. A clicked burger explodes into an image of an explosion that quickly fades away.
We’ll create this program in multiple steps.
A Fading Image Explosion¶
Our first step is to create a simple demo that causes an explosion whenever we click on the screen:
PImage explosion;
boolean exploding;
float transparency; // current transparency of the explosion
float dtrans; // rate of change of transparency
void setup() {
size(500, 500);
explosion = loadImage("explosion.png");
exploding = false;
}
void draw() {
background(255);
if (exploding == true) {
int mtrans = int(map(transparency, 0.0, 1.0, 0, 255));
tint(255, mtrans);
image(explosion, 250 - explosion.width / 2, 250 - explosion.height / 2);
transparency += dtrans;
if (transparency <= 0) {
exploding = false;
}
}
}
void mousePressed() {
exploding = true;
transparency = 1.0;
dtrans = -0.04;
}
Note the following:
A file named “explosion.png” must be in the program’s data folder. This is an image of an explosion that you create (e.g. using a paint program), or copied from somewhere else.
The variable
explodingis of typeboolean, meaning it either has the valuetrueorfalse. We need this variable to keep track of when the explosion is occurring, and when nothing is happening.The variable
transparencystore’s the image’s current transparency level as a number in the range 0.0 to 1.0. The variabledtransis the rate of change of the transparency when the explosion is occurring.Inside the
draw()function, we only draw the explosion if the variableexplosionistrue; if it’sfalse, then nothing is drawn.The first step of drawing the explosion is to map the value of
transparency, which is in the range 0.0 to 1.0, into the range 0 to 255 required by thetintfunction.The statement
tint(255, mtrans)sets the transparency of the images that are drawn after it. The first value, 255, sets the tint to white, and the second value,mtransis in the range 0 to 255 and sets the transparency of the image. This is a bit of a roundabout way to set transparency, but it works.The explosion is drawn so that it’s upper-left corner is at the point (
250 - explosion.width / 2,250 - explosion.height / 2). This ensures that the middle of the image is in the center of the screen.The explosion is considered to have finished when it has become totally transparent, and so this code resets the value of
exploding:if (transparency <= 0) { exploding = false; }
Whenever the user of the program clicks the mouse, a new explosion is initialized:
void mousePressed() { exploding = true; transparency = 1.0; dtrans = -0.04; }
Notice that
dtransis negative — the value oftransparencystarts at 1.0 and then decreases.
Making it Object-Oriented¶
The previous program is useful for experimenting with different kinds of explosions. When you’ve settled on the basic style of the explosion, then it is a good idea to encapsulate it in a class:
class Explosion {
PImage explosion;
boolean exploding;
float x, y; // center of the explosion
float transparency; // current transparency of the explosion
float dtrans; // rate of change of transparency
// constructor
Explosion() {
explosion = loadImage("explosion.png");
exploding = false;
}
void render() {
if (exploding == true) {
int mtrans = int(map(transparency, 0.0, 1.0, 0, 255));
tint(255, mtrans);
image(explosion, x - explosion.width / 2, y - explosion.height / 2);
}
}
void update() {
if (exploding) {
transparency += dtrans;
if (transparency <= 0) {
exploding = false;
}
}
}
void explode() {
exploding = true;
transparency = 1.0;
dtrans = -0.04;
x = mouseX;
y = mouseY;
}
} // class Explosion
Explosion e;
void setup() {
size(500, 500);
e = new Explosion();
}
void draw() {
background(255);
e.render();
e.update();
}
void mousePressed() {
e.explode();
}
Note the following:
This object-oriented version of the program is essentially a re-organization of the code in the first demo program. It greatly simplifies the “calling code” in
setup(),draw(), andmousePressed().A difference between this program and the previous one is that the explosion is drawn at the point (
x,y):void render() { if (exploding == true) { // ... image(explosion, x - explosion.width / 2, y - explosion.height / 2); } }
This allows an explosion to appear anywhere on the screen.
A subtle but important detail is that this
Explosionclass callsloadImageinside of its constructor. While this is fine for a small demo program, it becomes a serious problem in larger programs that might have hundreds (or more) of explosions: callingloadImagefor each explosion is bad becauseloadImagehas to load the image from the disk — and disk operations are always relatively slow compared to the rest of the program. Another serious problem is that each explosion object has its own personal copy of the explosion image, which is a significant waste of memory.A more efficient approach is to load the image once outside of the class:
class Explosion { PImage explosion; // ... // constructor Explosion() { explosion = explosionImage; // ... } // ... } // class Explosion PImage explosionImage; void setup() { explosionImage = loadImage("explosion.png"); } // ...
In this code, the image for the explosion is loaded one time in
setup(). If you were to create, say, 100Explosion, thenloadImageis called only one time, and all theExplosionobjects would share this one copy of the explosion image. This saves both time and memory.
Adding a Hamburger¶
Next, lets create a demo that displays hamburgers on the screen. When you click on a hamburger, it explodes.
The basic idea of the program is to create a class called Burger that
displays either a hamburger image, or the explosion. Each Burger object
stores an Explosion object, plus an image of a hamburger, and then uses a
boolean variable called exploding to keep track of what should be
displayed (either the burger, or the explosion).
Here’s the code:
class Explosion {
PImage explosion;
boolean exploding;
float x, y; // center of the explosion
float transparency; // current transparency of the explosion
float dtrans; // rate of change of transparency
// constructor
Explosion() {
explosion = loadImage("explosion100.png");
exploding = false;
}
void render() {
if (exploding) {
int mtrans = int(map(transparency, 0.0, 1.0, 0, 255));
tint(255, mtrans);
image(explosion, x - explosion.width / 2, y - explosion.height / 2);
}
}
void update() {
if (exploding) {
transparency += dtrans;
if (transparency <= 0) {
exploding = false;
}
}
}
} // class Explosion
// returns true if point (a, b) is the rectangle with upper-left
// corner (x, y), and width w and height h
boolean pointInRect(float a, float b, // point
float x, float y, // rectangle's upper-left corner
float w, float h) // width and height
{
if ((a >= x && a <= x + w) && (b >= y && b <= y + h)) {
return true;
} else {
return false;
}
}
class Burger {
PImage burger;
Explosion e;
float x, y; // location of the burger
float dx, dy; // speed of the burger
boolean exploding;
// constructor
Burger(float init_x, float init_y, float init_dx, float init_dy) {
burger = loadImage("burger100.gif");
x = init_x;
y = init_y;
dx = init_dx;
dy = init_dy;
exploding = false;
// We create the explosion here so that the explosion graphic
// is loaded only once at the beginning. The explosion is only
// drawn when exploding is true.
e = new Explosion();
}
// A burger is drawn either as the regular burger image (when it is
// not exploding), or the explosion image (when it is exploding).
void render() {
if (exploding) {
e.render();
if (e.transparency < 0) {
exploding = false;
}
}
else {
tint(255, 255); // draw the burger totally opaque
image(burger, x, y);
}
}
void update() {
if (exploding) {
e.update();
}
else {
x += dx;
y += dy;
}
}
// make the hamburger explode
void explode() {
if (!exploding) {
exploding = true;
e.exploding = true;
e.transparency = 1.0;
e.dtrans = -0.02;
e.x = x + burger.width / 2;
e.y = y + burger.height / 2;
}
}
boolean contains(float a, float b) {
return pointInRect(a, b, x, y, burger.width, burger.height);
}
} // class Burger
///////////////////////////////////////////////////////////////////////
Burger b1, b2, b3;
void setup() {
size(500, 500);
b1 = new Burger(200, 250, 0, 0);
b2 = new Burger(100, 200, 0, 0);
b3 = new Burger(400, 300, 0, 0);
}
void draw() {
background(255);
b1.render();
b1.update();
b2.render();
b2.update();
b3.render();
b3.update();
}
void mousePressed() {
if (b1.contains(mouseX, mouseY)) {
b1.explode();
} else if (b2.contains(mouseX, mouseY)) {
b2.explode();
} else if (b3.contains(mouseX, mouseY)) {
b3.explode();
}
}
Note the following:
The function in
Burgercalledcontains(a, b)tests if the point (a,b) is inside the bounding box of the burger image. Recall that the bounding box of the image is the (imaginary) rectangle that the image resides in.The function
pointInRectwas borrowed from earlier notes.The
booleanvariableexplosioninBurgeris used to keep track of how aBurgerobject should be rendered and updated: ifexplosionistrue, then it is rendered/updated as if it were anExplosion, and if it isfalsethen it is rendered/updated as an image.In the
Burgerrender()function, the code for drawing the image of the burger is this:tint(255, 255); // draw the burger totally opaque image(burger, x, y);
If you left out the
tintstatement, then the burger would often be transparent due to thetintstatement inExplosion. The tint of an image is always whatever the last call totintset it to be, and the last call totintinExplosionsets it to be totally transparent.When the
mousePressedfunction is called, it checks to see if one of the three burgers contains the point (mouseX,mouseY). If one does, that burger explodes.
Programming Questions¶
- Modify the basic image explosion so that, in addition to fading away, the size of the explosion increases.
- In the final program, if you click on the intersection of two (or more) overlapping burgers, then both burgers will explode. Modify the program so that, in this case, only the top burger explodes (and the burgers underneath don’t).
- To give explosions more variety, modify the explosions in the final program so that the image used for the explosion is chosen at random from two (or more) different explosion images.
- Create a simple game by modifying the final program (below) as follows:
- Show a score on the screen.
- Score (say) 1 point for clicking a burger before it reaches the bottom of the screen.
- Score (say) -1 points when a burger hits the bottom of the screen without being clicked.
- After a burger is clicked, or hits the bottom of the screen, re-set it so that it starts from the top of the screen again.
- An introductory screen that explains the game.
- Add “levels”, so that higher levels are harder to play (e.g. the burgers drop faster).
- Have the game end after, say, 10 levels, or after too many burgers have been missed. Show a final screen that summarizes the player’s score.
The Final Program¶
//
// In this demo, hamburgers rain down from the top of the screen.
// If you click one it explodes.
//
PImage burgerImage;
PImage explosionImage;
class Explosion {
PImage explosion;
boolean exploding;
float x, y; // center of the explosion
float transparency; // current transparency of the explosion
float dtrans; // rate of change of transparency
// constructor
Explosion() {
explosion = explosionImage;
exploding = false;
}
void render() {
if (exploding) {
int mtrans = int(map(transparency, 0.0, 1.0, 0, 255));
tint(255, mtrans);
image(explosion, x - explosion.width / 2, y - explosion.height / 2);
}
}
void update() {
if (exploding) {
transparency += dtrans;
if (transparency <= 0) {
exploding = false;
}
}
}
} // class Explosion
// returns true if point (a, b) is the rectangle with upper-left
// corner (x, y), and width w and height h
boolean pointInRect(float a, float b, // point
float x, float y, // rectangle's upper-left corner
float w, float h) // width and height
{
if ((a >= x && a <= x + w) && (b >= y && b <= y + h)) {
return true;
}
else {
return false;
}
}
class Burger {
PImage burger;
Explosion e;
float x, y; // location of the burger
float dx, dy; // speed of the burger
boolean exploding;
boolean visible;
// default constructor
Burger() {
this(random(100, width - 100), random(-175, -120), 0, random(1, 3));
}
// constructor
Burger(float init_x, float init_y, float init_dx, float init_dy) {
burger = burgerImage; //loadImage("burger100.gif");
x = init_x;
y = init_y;
dx = init_dx;
dy = init_dy;
exploding = false;
visible = true;
// We create the explosion here so that the explosion graphic
// is loaded only once at the beginning. The explosion is only
// drawn when exploding is true.
e = new Explosion();
}
// A burger is drawn either as the regular burger image (when it is
// not exploding), or the explosion image (when it is exploding).
void render() {
if (!visible) return;
if (exploding) {
e.render();
}
else {
tint(255, 255); // draw the burger totally opaque
image(burger, x, y);
}
}
void update() {
if (exploding) {
e.update();
if (e.transparency < 0) {
exploding = false;
visible = false;
}
}
else {
x += dx;
y += dy;
}
}
// make the hamburger explode
void explode() {
if (!exploding) {
exploding = true;
e.exploding = true;
e.transparency = 1.0;
e.dtrans = -0.05;
e.x = x + burger.width / 2;
e.y = y + burger.height / 2;
}
}
boolean contains(float px, float py) {
return pointInRect(px, py, x, y, burger.width, burger.height);
}
} // class Burger
///////////////////////////////////////////////////////////////////////
ArrayList<Burger> burgers;
int numBurgers = 10;
void setup() {
size(1000, 500);
explosionImage = loadImage("explosion100.png");
burgerImage = loadImage("burger100.gif");
burgers = new ArrayList<Burger>();
int i = 0;
while (i < numBurgers) {
Burger b = new Burger();
burgers.add(b);
++i;
}
}
void draw() {
background(255);
for (Burger b : burgers) {
b.render();
b.update();
}
}
void mousePressed() {
for (Burger b : burgers) {
if (b.contains(mouseX, mouseY)) {
b.explode();
}
}
}