Chapter7-CollisionDetection-Asteroids

advertisement
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.
What does a Proton Wave do?
Placing a ProtonWave
object has no effect
except a ProtonWave
visual.
What does the Counter do?
Apparently nothing
No effect except a
ProtonWave visual.
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()
The method
{
checkKeys handles
if (Greenfoot.isKeyDown("space"))
keyboard input
{
fire();
}
}
Actor Class Methods
Turning the Rocket
if (Greenfoot.isKeyDown("left"))
setRotation(getRotation() - 5);
if (Greenfoot.isKeyDown("right"))
setRotation(getRotation() + 5);
Left: Negative
Degrees
Right: Positive
Degrees
Turning the Rocket
/*
* Check whether there are any key pressed and react to them.
If left arrow key is
*/
down rotate left 5
private void checkKeys()
degrees
{
if (Greenfoot.isKeyDown ("space"))
{
fire();
}
if (Greenfoot.isKeyDown ("left"))
setRotation (getRotation() - 5);
}
Turning the Rocket
Turning the Rocket
/*
* Check whether there are any key pressed and react to them.
*/
private void checkKeys()
If right arrow key is down
{
rotate right 5 degrees
if (Greenfoot.isKeyDown("space"))
{
fire();
}
if (Greenfoot.isKeyDown ("left"))
setRotation (getRotation() - 5);
if (Greenfoot.isKeyDown ("right"))
setRotation (getRotation() + 5);
}
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
/*
Add an initial movement
* Initialize this rocket.
to the rocket constructor.
*/
public Rocket()
{
reloadDelayCount = 5;
addForce ( new Vector (13, 0.3)); //initially slow drifting
}
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.)
*/
Add the move () method
public void act()
{
move ();
checkKeys();
reloadDelayCount++;
}
Flying Forward
The rocket drifts slowly
toward the right of the World
Ignite the Engines
Ignite Algorithm
1) 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
Define a stub method for
ignite
/*
* Go with thrust on
*/
private void ignite (boolean boosterOn)
{
}
Ignite the Engines
A boolean
parameter
/*
* Go with thrust on
*/
private void ignite (boolean boosterOn)
{
if (boosterOn)
{
setImage (rocketWithThrust);
addForce (new Vector (getRotation(), 0.3));
}
else
{
setImage (rocket);
}
}
Ignite the Engines
/*
* Check whether there are any key pressed and react to them.
*/
private void checkKeys()
Add a call to ignite
{
if (Greenfoot.isKeyDown("space"))
fire();
ignite (Greenfoot.isKeyDown ("up"));
if (Greenfoot.isKeyDown("left"))
setRotation(getRotation() - 5);
if (Greenfoot.isKeyDown("right"))
setRotation(getRotation() + 5
}
Flying Forward
Colliding with Asteroids
Colliding Algorithm
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 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()
Make a call to
{
checkCollision from the
move ();
Rocket Act method
checkKeys();
checkCollision();
reloadDelayCount++;
}
Intersecting Objects
Bounding Box
Intersection
Visible Image
Intersecting Objects Methods
List getIntersectingObjects (Class cls)
Actor getOneIntersectingObject (Class cls)
Intersection
Bounding
Boxes
Visible Image
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 Worldmethods.
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()
{
The ScoreBoard
this(100);
class has two
}
constructors, a
Default Constructor
/*
and a second
* Create a score board for the final result.
Constructor with
*/
parameters
public ScoreBoard(int score)
{
makeImage("Game Over", "Score: ", score);
}
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 Line
Draw Oval
Draw
Rectangle
Fill Oval
Point Coordinates in Greenfoot

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
Drawing A Line
drawLine(10, 20, 150, 45);
or
drawLine(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.
Painting Stars
Calling the
createStars
methods in the
Space
constructor
Painting Stars
Painting Stars With Random
Brightness
/*
* Method to create stars. The integer number is how many.
*/
Generate a random
private void createStars(int number)
number for color in the
{
GreenfootImage background = getBackground(); range 0 to 255
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);
}
}
Will be more or less bright,
but always on the whiteblack 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 Move
Does not Disappear
Does not cause Damage
The Proton Wave
public ProtonWave()
{
initializeImages();
}
Already implemented code:
Constructor
initializeImages ()
act ()
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()
{
}
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
Grow Algorithm
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
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());
}
Create ProtonWave from Rocket
/*
* 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);
}
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;
private static final int protonReloadTime = 200;
private int reloadDelayCount;
private int protonDelayCount;
// Minimum delay in firing gun.
// Minimum delay in proton wave bursts.
// How long ago we fired gun the last time.
// 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 Collisions
import 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
Parameter to
determine the
amount of damage
/*
* 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.
ProtonWave Collissions
/* The damage this wave will deal */
private static final int DAMAGE = 30;
…
…
…
/*
* 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);
}
}
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
Download