Project - Asteroids
Part 5 - Shoot the Asteroids
A fundamental aspect of our game is collision detection: how do we know when two elements of our game have come into contact with each other? and what do we do about it when they do?
Let's figure out how to manage that.
5.1. Getting hit by an asteroid
Two objects have "collided" when they come into contact with each other, or—in our game—when their edges or boundaries overlap.
In the example shown here, if these two boxes are located in the positions shown here, we can see that they have collided.
One way of check of checking whether these two boxes overlap is shown in code here:
Rectangle r1 = new Rectangle(x1, y1, width1, height1);
Rectangle r2 = new Rectangle(x2, y2, width2, height2);
// Check to see if horizontal edges overlap
if (r1.getX() + r1.getWidth() > r2.getX() ||
r2.getX() + r2.getWidth() > r1.getX())
{
System.out.println("Horizontal collision!");
}
Another way of check for overlap, perhaps easier to manage for round-ish objects like asteroids and... our spaceship (?), involves checking the distance between their centers. If the distance between their centers is less than the sum of their radii, then their edges have come into contact.
Let's set up our game so that the game will end if an asteroid runs into our shape.
- In the AsteroidsGame tab, in the draw() method, in the loop chat shows all the asteroids, check each asteroid to see if it overlaps with the spaceship. If it does, execute a noLoop() instruction which stops the game. (Ending a game after one collision is pretty severe, but we'll modify that later on.)
- Your ship and your asteroids may have different dimensions from mine, and there's no getRadius() method that we can use for them anyway. We could write one, perhaps, but for now, let's just use Processing's dist(x1, y1, x2, y2) function to identify the distance between the x,y values for the asteroids and the spaceship. Any asteroid that has a distance closer than... 40?... to my spacecraft will be a game ender. (I can adjust that value if it turns out it doesn't work the way I want it to.)
Here's my asteroid loop now:
for (int i = 0; i < asteroids.size(); i++)
{
asteroids.get(i).move();
asteroids.get(i).show();
if (dist(asteroids.get(i).getX(), asteroids.get(i).getY(),
s.getX(), s.getY()) < 40)
{
noLoop(); // collision!
}
}
5.2. Shooting at asteroids
There's one more important element to our game: we need to be able to shoot at the asteroids, and for this we'll need a "laser," or "raygun," or "missiles," or... Yeah, missiles. Let's go with missiles.
Should we create a standalone Missile class, or should it inherit from Floater? Missiles don't really rotate in the way that asteroids do, but in all other respects, the superclass is probably a good fit. Let's have Missile inherit from Floater.
- Create a new Missile class that inherits from Floater. Note that our spaceship will be shooting missiles, but so will the UFO at some point, so it will be convenient to identify as a parameter who this missile belongs to as a parameter:
public Missile(Spaceship s)
- A new missile will be created when the user hits the space bar. That missile will have as its initial movement values all the characteristics of the ship that's firing it. It will also, however, need to accelerate away from that ship initially.
Here's a Missile class:
public class Missile extends Floater
{
/**
* Constructs a bullet for the spaceship s and
* gives it an initial velocity by accelerating
* it.
*/
public Missile(Spaceship s)
{
corners = 4;
xCorners = new int[] {2, -2, -2, 2};
yCorners = new int[] {1, 1, -1, -1};
myColor = 255;
myCenterX = s.myCenterX;
myCenterY = s.myCenterY;
myXspeed = s.myXspeed;
myYspeed = s.myYspeed;
myPointDirection = s.myPointDirection;
accelerate(6);
}
/**
* Override the Floater move method so we can
* avoid wrapping
*/
public void move()
{
myCenterX += myXspeed;
myCenterY += myYspeed;
}
}
- How do we actually fire a missile? How will the main program manage missiles? One strategy is to do what we did with the asteroids: we'll make an empty ArrayList, and put a missile in there every time the space bar gets pressed.
- And like the asteroid objects, we'll need to set up a loop that runs through all the missiles, calling the move() and show() methods on them all. Consider the idea of going backwards through the ArrayList of missiles as you check their status.
- The reason you want to go backwards through the missiles is that, if a missile hits an asteroid, we're going to want to remove both the missile and the asteroid from the game. When you use the remove() method on an ArrayList, recall that the other elements of the array slide over to "eliminate the gap" left behind by the element removed. If you're counting your way forwards through the array, you'll skip over the next element (unless you do something fancy with the index counter). It's easier to work your way backwards through the loop—elements that are removed just get removed, and as you continue to count backwards through the elements, nothing gets lost along the way.
Here's a missile loop in the main program:
// Go backwards through loop so we can remove
// without bounds errors
for (int i = missiles.size() - 1; i >= 0; i--)
{
Missile m = missiles.get(i);
m.move();
m.show();
// Check for distance and remove if out of bounds
if (m.getX() < 0 || m.getX() > width ||
m.getY() < 0 || m.getY() > height)
{
missiles.remove(i);
}
else
{
// Check for asteroid collisions while we're at it!
for (int j = asteroids.size() - 1; j >= 0; j--)
if (dist(m.getX(), m.getY(),
asteroids.get(j).getX(), asteroids.get(j).getY()) < 40)
{
asteroids.remove(j);
missiles.remove(i);
}
}
}
At this point we can maneuver through space to avoid asteroids and shoot asteroids to clear them out. We can also, unfortunately die very easily. It would be nice if we could have an extra life or two to play with!
Next...
Part 6 - Keeping score