CS 124, Spring 2013
Lab 10: Subclasses (and Events)
In this lab, you will work with an API for programming with "sprites." A sprite is a graphical element, like an icon, that can be placed on the screen, and possibly move around on the screen. There are classes to represent several kinds of sprites, and a class to represent the panel -- a rectangular area in a window -- where the sprites are displayed. All these classes are defined in a Java package named sprite. You will create several subclasses of these classes. In addition, the API includes some basic support for responding to keyboard events. That is, you can program responses that will happen when the user presses certain keys on the keyboard.
You should create a lab10 project in Eclipse. Open the folder /classes/cs124/lab10-files in a file browser. Select all four items, copy them, and paste them into the src folder of your lab10 project. You will have files named SpriteDemo1.java and SpriteDemo2.java in the default package and folders named sprite and images. The sprite folder contains the .java files for the package named sprite. (You can also browse the files on-line or download a zip archive of all the files.)
You should not modify any of the .java files that you have just added to the project in any way. You can copy files, if you want, and work on the copies, and you can make new files. But I will not print out or grade any work that you do in files that already exist.
The lab has three exercises. The first two are meant to be fairly quick, and there are detailed instructions for how to do them. The third exercise is longer and open-ended and asks you to design and write a simple game that uses sprites.
Because of the test next week, this lab is due by 10:00 AM on the morning of Tuesday, April 16.
About Sprites and SpritePanel
The sprite API is relatively complex, compared to other things you have worked with in this course. I have generated the documentation in JavaDoc format, which you can browse online. I also did a demonstration in class. However, I include some basic information here.
The two main classes are Sprite.java and SpritePanel.java. But to use these classes, you really have to define subclasses of them. To have actual Sprites, you need to have subclasses of Sprite. I have provided a variety of such subclasses, each representing a different kind of sprite. For example, OvalSprite representing a simple oval-shaped sprite, Bullet is a kind of sprite that can kill other sprites by hitting them, and SimpleGun represents a kind of "gun" that can fire Bullets.
A SpritePanel is where sprites live. A SpritePanel has a method
addSprite(s)
which takes a sprite, s, and adds it to the panel.
The panel keeps a list of sprites that it contains (actually two lists, since Bullet
sprites are treated in a special way and are kept in a separate list from other sprites).
Sprites can "die", and when they die they are removed from the list and no longer appear
in the panel.
A SpritePanel can run an animation. A sprite has a velocity and moves during the
animation according to its velocity. However, the animation doesn't start automatically.
To start it, you have to call the panel's go()
method. This is done, for
example, at the end of the main() program in SpriteDemo1.
The other two SpritePanel methods that you are most likely to use are
installKeyPress
and doKeyPress
, which are designed to let
the user interact with the program by pressing keys on the keyboard. These methods are
discussed in Exercise 3, below.
The Sprite class defines many methods, and you should remember that these can be used with any subclass of Sprite. See Sprite.java for the details. Here are some important instance methods:
setX(x)
,setY(y)
,setLocation(x,y)
-- Every sprite has a location, given by x and y coordinates. These methods allow you to set the position of the sprite. The coordinates give the position of the center of the sprite. Note that the coordinates are real numbers.getX()
,getY()
-- getter methods for the x and y coordinates.setDx(dx)
,setDy(dy)
,setVelocity(dx,dy)
,getDx()
,getDy()
-- getters and setters for the velocity of the sprite. dx tells how many pixels it moves horizontally in each frame; and dy tells how many pixels it moves vertically. The velocity can be zero, meaning the sprite doesn't move. The sprite only moves when the SpritePanel is running an animation. There is also a method for giving the sprite a random velocity.setSpriteWidth(w)
,setSpriteHeight(h)
,getSpriteWidth()
,getSpriteHeight()
-- getter and setter methods for the width and height of the sprite. A sprite has a width and height, which should match the actual size of the drawing. (However, aside from anything you might do with them, they are only used for testing for collision with bullets and with the edge of the window.)setExitPolicy(policyCode)
, where policyCode is one of the constants Sprite.EXIT_POLICY_BOUNCE, Sprite.EXIT_POLICY_WRAP, Sprite.EXIT_POLICY_STOP, or Sprite.EXIT_POLICY_DIE -- says what should happen when a moving sprite reaches the edge of the panel. See the documentation for details.- A sprite has a
draw
method for drawing its picture in the panel. However, you are not meant to call this method. Instead, you are meant to write the method in any subclass of Sprite that you write, to say how it should be drawn. Them method is actually called by the SpritePanel class. Several other methods in the Sprite class work the same way.
Exercise 1: A Simple Sprite Class
(Note: You might want to start Exercise 2 before doing this exercise, to make it easier to test the code that you write for Exercise 1.)
There is already a class OvalSprite. An OvalSprite is drawn as a colored oval. However, there is no class to represent a rectangle. The first exercise is to write such a class. Although you could do that by making a copy of OvalSprite.java, I would like you to work through the process of creating the class from scratch. Working through the instructions for this exercise will help you learn how to create a subclass of Sprite from scratch. You will probably want to take a look at OvalSprite.java before you start, and maybe copy some code from it. (Even if you start with a copy of OvalSprite.java, you will have to add some code to implement step 7, below.)
1. You want to a make a new class named RectSprite in the package sprite. To do this, right-click the sprite package in the Package Explorer view, on the left of the window. Select "New" / "Class" from the pop-up menu. In the dialog box, note that the package for the new class is already filled in as "sprite." When the class has been created, note that it says "package sprite;" at the top, to say that it is in the sprite package.
2. You want RectSprite to be a subclass of Sprite, so edit the first line of the class to read
public class RectSprite extends Sprite {
3. The class needs an instance variable to represent the color of the sprite. Add a private instance variable of type Color. You will get an error, because the Color class has not been imported. Add "import java.awt.Color;" to the file, or let Eclipse do it for you. Note that the import statement comes after the package statement.
4. Write getter and setter methods for the color variable. Better yet, let Eclipse do it for you: From the "Source" menu, select the command "Generate Getters and Setters". You will see a dialog box with a list of variables for which getters and setters can be generated. In this case, the only thing in the list is the color. Check the box next to the variable name, click OK, and the methods will be added to the class.
5. The class could use a constructor. It's not absolute required, but a RectSprite needs a non-zero width and height for it to make sense. Add a constructor that specifies a width and height for the sprite. The width and height properties are actually defined in the sprite class. To set them, you can call the setter methods setSpriteWidth(w) and setSpriteHeight(h). (See the constructor in OvalSprite.java; however, for RectSprite, you don't want to call setUseOvalForContains(true).)
6. One essential method in a sprite is draw, which is responsible for drawing the picture of the sprite. The draw method has to start like:
public class draw(Graphics g) {
You have to draw the sprite with its center at the point whose coordinates are getX() and getY(). These methods are getter methods defined in the Sprite class that give the current location of the sprite. They return double values that have to be type-cast to int before you can use them for drawing. Don't forget to set the drawing color that you want to use for the sprite! Again, see the example in OvalSprite.java. (In this case, you are overriding a method that is already defined in the Sprite class. Again, you can Eclipse to help with this. From the "Source" menu, select "Override/Implement Methods". In the dialog box, select that method that you want to override, and click OK.)
7. Finally, to make things more interesting, make it possible to draw the rectangular sprite with a black border. Whether or not to draw the border should be specified by a private instance variable of type boolean. You should provide getter and setter methods for the variable, and you should use the variable when drawing the sprite: After filling the rectangle, call drawRect to draw the border.
Exercise 2: A Simple Sprite Program
For the second exercise, you will create a simple program that uses sprites. In this case, I suggest that you begin by making a copy of the first demo program, SpriteDemo1.java. You could call the copy, for example, FirstSprites.
Note that the class is defined as a subclass of SpritePanel. That is, an object created from the class is a rectangular area in a window that can hold and display sprites and has methods for managing the sprites that it contains. (The class also has a main() program. The main program is static and so is not any part of any object created from the class. This would probably be considered poor style by most people -- the main program should probably be in another class altogether. But having a main() in the class makes it possible to run the class as a program.
The goal of this exercise is to create a program that will allow the user to add sprites to the window by pressing keys on the keyboard. In particular, when the user presses the "R" key, the program should add a RectSprite to the window. The new sprite should have a random velocity. You can decide what its starting position should be. You will want to look at some of the code in the sample programs to see how to make a sprite and add it to the panel.
Pressing the "C" key should remove all sprites from the window. You can do that very easily by calling the clear() method, which is defined in class SpritePanel. You should program a few other keys, as well. You could add some other kinds of sprites, for example. (Try adding Explosion sprites at random positions.) Here are some more detailed instructions:
1. First, delete most of the code from the constructor. In the demo program, the constructor added some sprites to the panel when the panel is first created. You want to start with an empty panel and add sprites only when the user presses the appropriate keys.
2. You will need some "installKeyPress" commands in the constructor. For a key to have any effect, you must install it with this command. For example, to activate the "R" key, you need to say
installKeyPress("R");
This method is defined in the SpritePanel class. See the documentation for the method for more information.
3. Finally, you have to modify the doKeyPress method to program the responses to the "R", "C", and other keys. Note that you don't call this method anywhere in your program. It will be called by SpritePanel when the user presses a key that has been installed with installKeyPress(). When you write this method, your job is to say what will happen when the user presses the key.
Exercise 3:
For the third exercise, you should program an interactive game using sprites. You can begin the program in the same way that you started Exercise 2, by copying SpriteDemo1.java. Most likely the game will include a gun that the user can fire and sprites for the user to shoot at. You can make something very similar to SpriteDemo3, but for full credit, you should make some changes or add some new features. (I didn't give you the source code for SpriteDemo3, but there is a .jar file in /classes/cs124 that you can run.)
To have a gun, you want to add a sprite of type SimpleGun to the panel. You will want to do this in the constructor, since the gun is a permanent part of the game. A gun is already programed with the ability to fire bullets that can kill other sprites; you just have to call its fire() method. (If the variable gun refers to the gun, that would really mean calling gun.fire().) To change the direction that the gun points, you can use its getAngle() method to get the current angle, and then use its setAngle method to point it in a different direction. In SpriteDemo3, this is done in response to the left and right arrow keys.
SimpleDemo3 also adds a bunch of sprites to the panel in the constructor to serve as sprites. They are given random positions and random horizontal velocities. Their vertical velocities are zero.
There are many ways that you could change or enhance the game, and you should try to be creative if you can come up with ideas of your own. You might even create an entirely new kind of sprite to use in the game. (However, not everything that you might want to do will be possible with the sprite API.) Here are some ideas:
- Make it possible for the user to start a new game, for example by pressing the "N" key.
- Don't change the angle of the gun. Instead, use the arrow keys to move it left and right. (See SpriteDemo2.java.) Or put the gun in the middle and let it point in any direction.
- You can add a method "protected void prepareForNextFrame(int frameNumber)" to your program. The method will be called by SpritePanel just before each frame is drawn. (This redefines a method in SpritePanel that does nothing; it exists just to be redefined in subclasses.) For example, you might use this method to occasionally add new sprite targets to the panel.
- You can write a method "protected void drawBackground(Graphics g, int frameNumber)" to draw a background picture behind the sprites.
- Use a TextSprite to show messages to the user. It doesn't have to move. A TextSprite has a method setText(str) to change the text that it displays. For example, you could set the text in the prepareForNextFrame method mentioned in the previous idea. You could show the number of remaining sprites or the time that the game has been going on or some kind of score. (There is a getter method in SpritePanel to tell you how many sprites there are.)
- Don't let the user fire the gun too rapidly. Record the time when it is fired (by calling System.getCurrentTimeMilllis(). When the user tries to fire it again, see how much time has passed. If the time is too short, don't fire the gun. Another idea is to limit the number of bullets that can exist at a given time. The method getBulletCount() returns the number of bullets in the panel.
- Make targets that move erratically by randomly changing their velocity. You can use a new kind of sprite to do this. For example, start with a copy of OvalSprite and add a prepareForNewFrame method to class. The method should be defined to occasionally change the sprite's velocity. This method is called in every frame. See Explosion.java for an example of using a prepareForNewFrame method.
- Make targets that split into two or three smaller targets (unless that would make them too small). To do this, you would make a new subclass of Sprite -- or perhaps of SpriteOval -- and write the method "protected void onDeath()". This method is called when the sprite dies. In this method, you could make several smaller sprites and add them to the SpritePanel. Before doing that, check the width of the sprite that is dying, and only make the new sprites if the width is larger than some limiting value. You need a reference to the SpritePanel that contains the original sprite. You get that by calling getHome(). You will also want to assign velocities to the new sprites. you could give them a random velocity, and perhaps add on some fraction of the original sprite's velocity.