CS302, UW-Madison
In today's lab we're going to have some fun with object-oriented programming as we use it to complete a game like the classic game Space Invaders. You'll write an instantiable class to represent UFOs that, together with other classes we provide, will make a complete computer game. First we'll need to familiarize you with the code you'll use, and then you'll get a simple UFO class working in four steps. Once this part is completed you'll be challenged to extend and create different kinds of UFOs.
Create a new project in Eclipse called SpaceLab. When creating the new project, remember to select "Use project folder as root for sources and class files" in the "Project layout" section. Next, download the following files to your SpaceLab folder (right-click on a link and then choose the "Save Link As..." option):
Once you've gotten copies of these files:
Now open UFO.java (in the default package), and notice the method stubs in this source file whose bodies have been completed with "dummy" code so that the program can be compiled. You'll be replacing those bodies with your own code as you work on this lab.
Planet Earth is under attack by evil silicon-based life forms flying around in UFOs:
You are the only one who can possibly stem the tide in your fully armed lunar module, the Defender:
You move the Defender module by moving the mouse side to side (but you must keep the cursor in the game window!) and each time you click fires a Laser. If you disable all of the UFOs with your lasers, you win. If you get hit by three alien lasers, you lose. The UFO, Laser, and Defender objects are coordinated in the SpaceGame window, which is a class we've already written, so you just need to set it up and tell it to go.
Note that, while there are links to the Javadoc for the objects mentioned above, do not use any methods besides those that are specifically mentioned below. Otherwise, you may break the program. On your own after this lab, you're invited to experiment with the program as you wish.
The Defender, Laser, and UFO classes have the code that specify these objects. One thing they all need to know is where they're located in the SpaceGame window. Each object will do this by storing its position in the window using pixel coordinates (a pixel is the smallest dot in a picture that a computer can make and is about the size of the dot over an i on the screen). The coordinate system is counter-intuitive -- instead of having the origin of the coordinates at the lower-left corner of the display, Java puts it at the upper-left corner.
A game like this has essentially three main tasks that it repeatedly does:
The game makes it appear that these happen at the same time using a programming technique called multithreading, which is beyond the scope of this course. We'll focus on determining position of UFO objects, and we'll enable the game to access information about them as specified in the UFO Javadoc.
We have a few methods to implement to get our UFOs to appear on the screen. These methods allow the game to construct UFOs and access a UFO's position so that it can draw it in the right location on the screen.
We've initially specified that UFOs have three instance variables:
Start by completing the bodies the constructor and the accessor (getter) methods getXPosition(), getYPosition(), and getUFOType(). The constructor should initialize the three instance variables using the appropriate parameters.
Once you have completed these, create a new class, named SpaceGameMain, in the source file named SpaceGameMain.java. This will be your main class. Add the following code to your SpaceGameMain.java file:
import java.util.Random; public class SpaceGameMain { //for random numbers public static Random rng = new Random(); public static void main(String[] args) { //create game SpaceGame theGame=new SpaceGame(800,800); //add UFOs theGame.addUFO(UFO.SIMPLE_SAUCER); theGame.addUFO(UFO.SIMPLE_SAUCER); theGame.addUFO(UFO.SIMPLE_SAUCER); //run game theGame.start(); } }
Now go ahead and run it (note it will crash if you haven't modified the UFO class as described above). If you wrote the methods correctly, you'll see the Defender module is ready to defend, but the UFO objects merely hang in various locations on the screen. They need some enhancement, specifically:
In the next three tasks we'll implement these enhancements, but before you move on show your Lab TA that you gotten UFOs to appear.
We make it appear that the UFO is smoothly moving by drawing it on the screen, then changing its position by a little bit and redrawing it on the screen at the new position. By repeating this at a fast rate, our eyes interpret it as continuous movement, which is an essential idea behind animation as seen in low-tech but high-fun flip books. The time between each screen redraw we call a tick. Once every tick, the game program we've already coded repeatedly calls takeOneStep(), a mutator (setter) method that updates the UFO object's position. Give this a try. Change the body of the takeOneStep method to this.xPosition+=3; and run the program (watch carefully since the ships will move off the window). Now every time the screen redraws itself the ship moves three pixels to the right; in other words, the ship moves to the right at a rate of three pixels per tick.
Next, modify the takeOneStep() method so the ship moves back and forth across the length of the screen at three pixels per tick. Here are some hints:
Once you've gotten UFO's to move across the screen, show your program to your Lab TA.
Switch so that your partner is in control of the keyboard
It's impossible to lose if the UFOs don't fire at you. Both the Defender and the UFO objects can fire Laser objects, but only the Defender has been programmed to work correctly. We've already implemented the Laser class, which you'll use in this task to make UFOs fire lasers. Two methods control the firing: shootsThisTurn() and fireWeapon() and both must be completed as described below for firing to work. During each tick of the game, our program will check each UFO object to see if it shoots this turn by calling shootsThisTurn(). If the UFO shoots, then our program will call fireWeapon().
public boolean shootsThisTurn() returns true if the UFO is supposed to fire during this tick, false otherwise. Since this method is called by our code each tick, you'll want to keep a count of the tick and only fire when that count reaches a limit. We suggest that you have your UFO fire once every seventy ticks. Later, if you wish, you can make your method vary firing for more interesting game play.
public Laser fireWeapon()
returns a new Laser object that the UFO fires. This method simply makes a
Laser object by using the the constructor we've already implemented in the
Laser class:
where the first two ints specify the position of the center of the laser object and the second two indicate the x and y components of the velocity in pixels per tick. Create the new laser object at the same location as the UFO and have the laser object head straight down at four pixels per tick veolcity.
Here are some hints:
Now that your UFOs are firing lasers, show your program to your Lab TA.
It's impossible to win if you can't hit the UFOs. We've programmed the Defender to be able to shoot, so you just need to check if your UFO object has been hit by a laser. If it has, you need to react appropriately. Three methods control how this works: isHitByLaser(Laser theLaser), recordHit(), and removeMeFromGame(). During each tick of the game, our program will check each UFO object to see if it was hit by a laser by calling isHitByLaser. If it was hit, our program will call the recordHit method so the UFO can record the hit, and then our program will call the removeMeFromGame to see if the UFO was destroyed and should be removed from the game.
public boolean isHitByLaser(Laser theLaser)
returns true if the laser is close enough to "hit" the UFO. Check if the center
of the given Laser object and the center of the UFO object
are a minimum distance apart. In order to do this you can get position of Laser
objects using the accessor methods from the Laser class:
public void recordHit()
is a mutator method that tells the UFO that it was hit so that it can keep
track of how many times it has been hit.
public boolean removeMeFromGame()
is an accessor method that
returns true only when the UFO has been hit enough times to be destroyed.
Fill in the recordHit() and removeMeFromGame() methods so that the UFO is destroyed after it is hit twice.
Here some hints:
You should now have a working game! Ask your Lab TA to give it a try.
In any time remaining in the lab (and on your own if you like), give this a try.
The game is sort of neat, but all the UFOs are doing exactly the same thing. We can fix that! Let's use that uFOType variable that we've been ignoring. First, let's add more constants to the UFO class (add more constants if you wish):
public static final int FAST_SHIP = 1; public static final int SUPER_SAUCER = 2;
Let's test it by modifying the SpaceGameMain class. Create several ships of each type and run the game. If you wrote your constructor and your getUFOType() method correctly, you should see several different UFO objects floating back and forth. That's a start, but they all still do the same thing. How can we change that?
Well, each ship has a UFO type. We can use conditional statements to check the type and execute different code based on the type. Here's a simple example that could go in the takeOneStep() method:
int pixelsPerTick; if (this.uFOType == FAST_SHIP) { pixelsPerTick=6; } else { pixelsPerTick=3; }
If we move the ship based on pixelsPerTick, our FAST_SHIP UFOs will go twice as fast as the others. Keep in mind that a switch statement works extremely well here too:
switch(this.uFOType){ case SIMPLE_SAUCER: //your code here break; case FAST_SHIP: //your code here break; case SUPER_SAUCER: //your code here break; default: System.out.println("No such UFO type " + this.uFOType); }
So what makes a SUPER_SAUCER so super? Now it's your turn. Try to make several different types of UFOs that do different things. Have fun! Below are some suggestions for ways you can experiment with your UFOs:
You might also consider adding randomness to your UFOs. Use the game's random number generator with the code: SpaceGameMain.rng.nextInt(...), for example. Here are some ideas for adding randomness:
You might also use the Defender parameter passed to the UFO methods. Here are some ideas for using this parameter:
Create a new project in Eclipse called SpaceLab2. When creating the new project, remember to select "Use project folder as root for sources and class files" in the "Project layout" section. Next, download the following files to your SpaceLab2 folder (right-click on a link and then choose the "Save Link As..." option):
Once you've gotten copies of these files:
Now open the doc2/UFO Javadoc. The new jar files includes classes with added getters and setters to Laser, Defender, and SpaceGame to let you better control the parameters of the Game.