chapter 7 – collision detection: asteroids. the asteroids game
TRANSCRIPT
Chapter 7 – Collision Detection:
Asteroids
The Asteroids Game
How Are Bullets Fired?
We handle a “space” input and call the fire()
method.
How Are Asteroids Created?
We call the addAsteroids() method in
the Space constructor
What does an Explosion do?
Placing an Explosion object creates the
Explosion Visual and makes an Explosion
Sound.
Placing a ProtonWave
object has no effect except a
ProtonWave visual.
What does a Proton Wave do?
No effect except a ProtonWave
visual.
What does the Counter do?
Apparently nothing
What is currently NOT coded?When experimenting with the current scenario, you will notice that some fundamental functionality is missing.
The rocket does not move. It cannot be turned, nor can it be moved forward.
Nothing happens when an asteroid collides with the rocket. It flies straight through it, instead of damaging the rocket.
As a result of this, you cannot lose. The game never ends, and a final score is never displayed.
The ScoreBoard, Explosion, and ProtonWave classes, which we can see in the class diagram, do not seem to feature in the scenario.
What should be coded?
Controls for the Rocket Collision: Asteroid vs. Rocket Explosion Logic ScoreBoard/Counter Logic ProtonWave Logic
Turning the Rocket
We want to make the rocket turn left or right using the left and right arrow keys.
Handling Key Presses
/* * Check whether there are any key pressed and react to them. */private void checkKeys() { if (Greenfoot.isKeyDown("space")) { fire(); }}
The method checkKeys
handles keyboard input
Actor Class Methods
Turning the Rocket
Left: Negative Degrees
Right: Positive Degrees
if (Greenfoot.isKeyDown("left")) setRotation(getRotation() - 5);
if (Greenfoot.isKeyDown("right")) setRotation(getRotation() + 5);
Turning the Rocket
/* * Check whether there are any key pressed and react to them. */private void checkKeys() { if (Greenfoot.isKeyDown ("space")) { fire(); } if (Greenfoot.isKeyDown ("left")) setRotation (getRotation() - 5);}
If left arrow key is down rotate left 5
degrees
Turning the Rocket
Turning the Rocket
/* * Check whether there are any key pressed and react to them. */private void checkKeys() { if (Greenfoot.isKeyDown("space")) { fire(); } if (Greenfoot.isKeyDown ("left")) setRotation (getRotation() - 5);
if (Greenfoot.isKeyDown ("right")) setRotation (getRotation() + 5);}
If right arrow key is down rotate right 5
degrees
Turning the Rocket
Flying Forward
Our Rocket class is a subclass of the SmoothMover class. This means that it holds a movement vector that determines its movement and that it has a move () method that makes it move according to this vector
Flying Forward
The rocket does not move because we did
not specify a movement vector yet.
Flying Forward
/* * Initialize this rocket. */public Rocket(){ reloadDelayCount = 5; addForce ( new Vector (13, 0.3)); //initially slow drifting}
Add an initial movement to the
rocket constructor.
Flying Forward
/* * Do what a rocket's gotta do. (Which is: mostly * flying about, and turning, * accelerating and shooting when the right keys are pressed.) */public void act(){ move (); checkKeys(); reloadDelayCount++;}
Add the move () method
Flying Forward
The rocket drifts slowly toward the right of the
World
Ignite the Engines
Ignite Algorithm1) If “up” arrow key is pressed then
1.1) change image to show engine fire 1.2) add movement
2) If “up” arrow key is released then 2.1) change back to normal rocket image
Ignite the Engines
/* * Go with thrust on */private void ignite (boolean boosterOn){
}
Define a stub method for ignite
Ignite the Engines
/* * Go with thrust on */private void ignite (boolean boosterOn){ if (boosterOn) { setImage (rocketWithThrust); addForce (new Vector (getRotation(), 0.3)); } else { setImage (rocket); }}
A boolean parameter
Ignite the Engines
/* * Check whether there are any key pressed and react to them. */private void checkKeys() { if (Greenfoot.isKeyDown("space")) fire(); ignite (Greenfoot.isKeyDown ("up"));
if (Greenfoot.isKeyDown("left")) setRotation(getRotation() - 5);
if (Greenfoot.isKeyDown("right")) setRotation(getRotation() + 5}
Add a call to ignite
Flying Forward
Colliding with Asteroids
1) If we have collided with an asteroid then 1.1) remove the rocket from the world 1.2) place an explosion into the world 1.3) show final score (game over)
Colliding Algorithm
Colliding with Asteroids
/* * Check for a collision with an Asteroid */private void checkCollision(){
}
Define a stub method for checkCollision
Colliding with Asteroids
/* * Do what a rocket's gotta do. (Which is: mostly flying about, and turning, * accelerating and shooting when the right keys are pressed.) */public void act(){ move (); checkKeys(); checkCollision(); reloadDelayCount++;}
Make a call to checkCollision from
the Rocket Act method
Intersecting Objects
Bounding Box
Visible Image
Intersection
Intersecting Objects Methods
Bounding Boxes
Visible Image
List getIntersectingObjects (Class cls)
Actor getOneIntersectingObject (Class cls)
Intersection
getOneIntersectingObject()
/* * Check for a collision with an Asteroid */private void checkCollision(){ Actor a = getOneIntersectingObject (Asteroid.class);
}
Even though we can specify what class we are looking for, the
method always returns an Actor object
getOneIntersectingObject()
/* * Check for a collision with an Asteroid */private void checkCollision(){ Asteroid a = (Asteroid) getOneIntersectingObject (Asteroid.class);
}
However, we can re-interpret the returned Actor to a more specific
class via a process called “Casting”.
More on that later.
getOneIntersectingObject()
/* * Check for a collision with an Asteroid */private void checkCollision(){ Actor a = getOneIntersectingObject (Asteroid.class); if (a != null) {
}}
We have a collision if: a is not “null”, meaning if
the getOneIntersectingObject has returned something
Colliding with Asteroids
/* * Check for a collision with an Asteroid */private void checkCollision(){ Actor a = getOneIntersectingObject (Asteroid.class); if (a != null) { World space = getWorld();
space.removeObject (this); space.addObject (new Explosion(), getX(), getY()); }}
Adding the Explosion and removing the Rocket has to be done via World-
methods.
Colliding with Asteroids
/* * Check for a collision with an Asteroid */private void checkCollision(){ Actor a = getOneIntersectingObject (Asteroid.class); if (a != null) { World space = getWorld();
space.addObject (new Explosion(), getX(), getY()); space.removeObject (this); }}
The sequence of commands is very important when
removing objects!
Colliding with Asteroids
Game Over
After a collision, we want to show a ScoreBoard object in
the middle of the Level
Game Over Method in the Space class
/* * This method is called when the game is over to display the final score. */public void gameOver() { // TODO: show the score board here. Currently missing.}
ScoreBoard
/* * Create a score board with dummy result for testing. */public ScoreBoard(){ this(100);}
/* * Create a score board for the final result. */public ScoreBoard(int score){ makeImage("Game Over", "Score: ", score);}
The ScoreBoard class has two constructors, a Default Constructor and a second Constructor with parameters
Game Over
/* * This method is called when the game is over to display the final score. */public void gameOver() { addObject(new ScoreBoard(999), getWidth()/2, getHeight()/2);}
Making sure that the Game Over screen appears right in the middle of the screen.
Game Over
Colliding with Asteroids II
/* * Check for a collision with an Asteroid */private void checkCollision(){ Actor a = getOneIntersectingObject (Asteroid.class); if (a != null) { World space = getWorld();
space.addObject (new Explosion(), getX(), getY()); space.removeObject (this);
space.gameOver(); }}
Why does this code produce an error?
Colliding with Asteroids II
/* * Check for a collision with an Asteroid */private void checkCollision(){ Actor a = getOneIntersectingObject (Asteroid.class); if (a != null) { World space = getWorld();
space.addObject (new Explosion(), getX(), getY()); space.removeObject (this);
space.gameOver(); }} The gameOver() method is
a method of the Space class. Our Compiler cannot find it in the World class. Thus, we cannot call it from objects of type World!
Colliding with Asteroids II/* * Check for a collision with an Asteroid */private void checkCollision(){ Actor a = getOneIntersectingObject (Asteroid.class); if (a != null) { Space space = (Space) getWorld();
space.addObject (new Explosion(), getX(), getY()); space.removeObject (this);
space.gameOver(); }}We can use Casting to specify that the object
returned by the getWorld() method is not only a World object, but actually an object of the more
specific Subclass Space.This does NOT change the actual type of the object,
it only changes the information available to our program.
Painting Stars
The Asteroid Scenario does not use an image file for the background. Instead, the background image is generated directly in the Constructor.
Painting Stars
The Background is Created by These Three Statements.
Painting Stars
Code to Create the Background is
Commented Out
No Background
The GreenfootImage Class
Draw Rectangle
Draw Oval
Fill Oval
Draw Line
Each point of a World object can be identified using a Cartesian coordinate system
The origin of the coordinate system (i.e. the point (0,0) ) is in the top-left corner
Point Coordinates in Greenfoot
Drawing A Line
drawLine(10, 20, 150, 45); ordrawLine(150, 45, 10, 20);
Drawing A Rectangle
drawRect (50, 20, 100, 40);
Drawing An Oval
drawOval (175, 20, 50, 80);
Filling an Oval
fillOval (175, 20, 50, 80);
Method to create stars
/* * Method to create stars. The integer number is how many. */private void createStars(int number) { GreenfootImage background = getBackground(); for (int i = 0; i < number; i++) {
}}
We want to draw a set amount of stars. This means that our new methods
requires a parameter.
Method to create stars
/* * Method to create stars. The integer number is how many. */private void createStars(int number) { GreenfootImage background = getBackground(); for (int i = 0; i < number; i++) { int x = Greenfoot.getRandomNumber( getWidth() ); int y = Greenfoot.getRandomNumber( getHeight() ); background.setColor (new Color(255, 255, 255)); background.fillOval(x, y, 2, 2); }}
Note the sequencing: We first specify the color, then what we want to draw in
said color.
Also note how we apply the operations to a specific image, using dot notation.
Calling the createStars methods in the Space
constructor
Painting Stars
Painting Stars
Painting Stars With Random Brightness
/* * Method to create stars. The integer number is how many. */private void createStars(int number) { GreenfootImage background = getBackground(); for (int i = 0; i < number; i++) { int x = Greenfoot.getRandomNumber( getWidth() ); int y = Greenfoot.getRandomNumber( getHeight() ); int color = Greenfoot.getRandomNumber (256); background.setColor(new Color(color, color, color)); background.fillOval(x, y, 2, 2); }}
Generate a random number for color in the range 0 to 255
Will be more or less bright, but always on
the white-black spectrum
Painting Stars With Random Brightness
Adding Fire Power: The Proton Wave
The idea is this: Our proton wave, once released, radiates outward from our rocket, damaging or destroying every asteroid in its path. Since it works in all directions simultaneously, it is a much more powerful weapon than our bullets.
The Proton Wave
Does not MoveDoes not DisappearDoes not cause Damage
The Proton Wave
public ProtonWave() { initializeImages();} public static void initializeImages() { if(images == null) { GreenfootImage baseImage = new GreenfootImage("wave.png"); images = new GreenfootImage[NUMBER_IMAGES]; int i = 0; while (i < NUMBER_IMAGES) { int size = (i+1) * ( baseImage.getWidth() / NUMBER_IMAGES ); images[i] = new GreenfootImage(baseImage); images[i].scale(size, size); i++; } }} public void act(){ }
Already implemented code:ConstructorinitializeImages ()act ()
initializeImages() creates an Array of Growing Images
GreenfootImage [ ] images 0 1 2 3 4 29
Setting up The Proton Wave
/* * Index of the currently used image. */private int imageCount = 0;
/* * Create a new proton wave. */public ProtonWave() { initializeImages(); setImage(images [0]); Greenfoot.playSound ("proton.wav");}
The Proton Wave
The Proton Wave
/* * Act for the proton wave is: grow and check whether we hit anything. */public void act(){ grow();}
/* * Grow the wave. If we get to full size remove it. */private void grow (){}
The Proton Wave
1) If our index has exceed the number of images then 1.1) Remove the wave from the world
2) Otherwise 2.1) set the next image in the array 2.2) increment the index
Grow Algorithm
The Proton Wave
/* * Grow the wave. If we get to full size remove it. */private void grow (){ if (imageCount >= NUMBER_IMAGES) getWorld().removeObject (this); else setImage(images[imageCount++]);}
The Proton Wave
Create a new ProtonWave by hand
Click “> Act” to watch the wave grow
sequentially
Create ProtonWave from Rocket
/* * Release a proton wave (if it is loaded). */private void startProtonWave(){ ProtonWave wave = new ProtonWave(); getWorld().addObject (wave, getX(), getY());}
/* * Check whether there are any key pressed and react to them. */private void checkKeys() { if (Greenfoot.isKeyDown("space")) fire(); if (Greenfoot.isKeyDown("z")) startProtonWave(); ignite (Greenfoot.isKeyDown ("up"));
if (Greenfoot.isKeyDown("left")) setRotation(getRotation() - 5); if (Greenfoot.isKeyDown("right")) setRotation(getRotation() + 5);}
Create ProtonWave from Rocket
Test It
Test It
Proton Wave Can Be Released Too Fast by Holding Down the z Key
Managing Delays
private static final int gunReloadTime = 5; // Minimum delay in firing gun.private static final int protonReloadTime = 200; // Minimum delay in proton wave bursts. private int reloadDelayCount; // How long ago we fired gun the last time.private int protonDelayCount; // How long ago we fired proton wave the last time.
private GreenfootImage rocket = new GreenfootImage("rocket.png"); private GreenfootImage rocketWithThrust = new GreenfootImage("rocketWithThrust.png");
A Delay Count of 200 Seems Reasonable
Managing Delays
/* * Do what a rocket's gotta do. (Which is: mostly flying about, and turning, * accelerating and shooting when the right keys are pressed.) */public void act(){ move (); checkKeys(); checkCollision(); reloadDelayCount++; protonDelayCount++;}
Managing Delays
/* * Release a proton wave (if it is loaded). */private void startProtonWave(){ if (protonDelayCount >= protonReloadTime) { ProtonWave wave = new ProtonWave(); getWorld().addObject (wave, getX(), getY()); protonDelayCount = 0; }}
The method only produces a new Proton Wave if sufficient time
as passed since the last one
Test It
Interacting with Objects in Range
List getObjectsInRange (int radius, Class cls)
Check for ProtonWave Collisions
/* * Act for the proton wave is: grow and check whether we hit anything. */public void act(){ checkCollision(); grow();}
/* * Explode all intersecting asteroids. */private void checkCollision(){}
ProtonWave Collisionsimport greenfoot.*; // (World, Actor, GreenfootImage, and Greenfoot)import java.util.List;………/* * Explode all intersecting asteroids. */private void checkCollision(){ int range = getImage().getWidth() / 2; List<Asteroid> asteroids = getObjectsInRange (range, Asteroid.class); for (Asteroid a : asteroids) { }}
Asteroid Hit Method
/* * Hit this asteroid dealing the given amount of damage. */public void hit(int damage) { stability = stability - damage; if(stability <= 0) breakUp (); }
Every Asteroid has “hit points” in the form of an integer variable called stability. An
Asteroid breaks up if the variable reaches 0 or less.
Parameter to determine the
amount of damage
ProtonWave Collissions
/* * Explode all intersecting asteroids. */private void checkCollision(){ int range = getImage().getWidth() / 2; List<Asteroid> asteroids = getObjectsInRange(range, Asteroid.class); for (Asteroid a : asteroids) { a.hit (DAMAGE); }}
/* The damage this wave will deal */ private static final int DAMAGE = 30;………
Extra: Hit Invincibility
The ProtonWave currently completely destroys all
Asteroids it comes in contact with. Meaning it
destroys both the Asteroids it hits and the
Asteroids that are then spawned.
Depending on how we want our game to
function, this might not be desirable.
How can we make sure that the ProtonWave does
not destroy the new Asteroids?
Summary of Programming Techniques