(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
exploding
is of typeboolean
, meaning it either has the valuetrue
orfalse
. We need this variable to keep track of when the explosion is occurring, and when nothing is happening.The variable
transparency
store’s the image’s current transparency level as a number in the range 0.0 to 1.0. The variabledtrans
is the rate of change of the transparency when the explosion is occurring.Inside the
draw()
function, we only draw the explosion if the variableexplosion
istrue
; 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 thetint
function.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,mtrans
is 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
dtrans
is negative — the value oftransparency
starts 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
Explosion
class callsloadImage
inside 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: callingloadImage
for each explosion is bad becauseloadImage
has 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
, thenloadImage
is called only one time, and all theExplosion
objects 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
Burger
calledcontains(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
pointInRect
was borrowed from earlier notes.The
boolean
variableexplosion
inBurger
is used to keep track of how aBurger
object should be rendered and updated: ifexplosion
istrue
, then it is rendered/updated as if it were anExplosion
, and if it isfalse
then it is rendered/updated as an image.In the
Burger
render()
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
tint
statement, then the burger would often be transparent due to thetint
statement inExplosion
. The tint of an image is always whatever the last call totint
set it to be, and the last call totint
inExplosion
sets it to be totally transparent.When the
mousePressed
function 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();
}
}
}