CS 124, Spring 2017
Lab 11: Sprites

For this lab, you can choose to work with a partner, and you are encouraged to do so. If you work with a partner, you should make sure that both partners' names are listed in the comment at the top of the classes that you submit. Your work can be submitted by either partner. Note that there is one part of the third exercise where people who are working as a team are required to do some extra work.

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. The files for the lab include classes to represent several kinds of sprite and a class to represent the panel 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. (The framework used for handling keyboard events it different from the "KeyListener" framework that was used in the MidiKeyboard program.)

To start the lab, you will need to copy a lot of files into your project. Open the folder /classes/cs124/lab11-files in a file browser. Select all four items in that folder, copy them, and paste them directly into the src folder of your lab11 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. The "images" folder contains images for use with the ImageSprite class. (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 you should not edit existing file.

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.

This lab is due by 3:00 PM on Friday, April 21 (changed from original due date on April 19). You should submit three files named RectSprite.java, MySpriteDemo.java, and SpriteGame.java. All three files should be in the default package, if you want them to be compiled by the submit124 web application. If you make an extra subclass of Sprite for the third exercise, you can also submit that file.

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. 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 represents a simple oval-shaped sprite, a Bullet is a specil kind of sprite that can kill other sprites by hitting them, and a SimpleGun is a kind of "gun" that can fire Bullets.

A SpritePanel is where sprites live. A SpritePanel is subclass of JPanel, so it represents a rectangular area in a window. A SpritePanel has an instance 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 sample program SpriteDemo2, on the ohter hand, does not use an animation.

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. Ordinarily, you will call installKeyPress, and you will override doKeyPress in a subclass of SpritePanel. These methods are discussed in Exercise 2, below.

The Sprite class defines many methods, and you should remember that these can be used with any subclass of Sprite, because of inheritance. See Sprite.java or the Javadoc to learn about all of the available methods. But here are some important instance methods contained in any object of type Sprite (including subclasses of Sprite):

Exercise 1: A Simple Sprite Class

(Note: You won't be able to test your work for this exercise until you at least get started on Exercise 2.)

There is already a class named 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, it would be preferable 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 even copy some code from it. (Even if you do decide to 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 default package. To do this, right-click the src folder and select "New" / "Class" from the pop-up menu.

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 {

and add an import statement to the top of the file to get access to the classes from the sprite package:

import 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.

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 absolutely 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.)

6. One essential method in a sprite is draw, which is responsible for drawing the picture of the sprite. You need to provide a definition for draw that will draw the rectangle. The draw method has to start like:

public void draw(Graphics g) {

The method should 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 draw() in OvalSprite.java. (In this case, you are overriding a method that is already defined in the Sprite class. Again, you can get 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 either with or without sa black border. Add an instance variable of type boolean to the RectSprite class to say whether or not the border should be drawn. You should provide getter and setter methods for the variable. Add code to the end of the draw() method in RectSprite to draw a black rectangle as a border for the filled rectangle, if it's needed.

Exercise 2: A Simple Sprite Program

For the second exercise, you will create a simple program that uses sprites. In this case, you should begin by making a new class named MySpriteDemo in the default package. Replace the contents of the new file with the following code, which is similar to the first demo program, SpriteDemo1.java with the code specific to that application stripped out.

import java.awt.*;
import javax.swing.*;
import sprite.*;

/**
 * A simple demo of key handling in the sprite framework.
 */
public class MySpriteDemo extends SpritePanel {
   
   /**
    * Creates an empty panel. Sprites will be added later in response
    * to key presses by the user.  This constructor sets up handling
    * for the appropriate keys.
    */
   public MySpriteDemo() {
      installKeyPress("SPACE");
      installKeyPress("Q");
   }
   
   /**
    * Respond to the key presses from the user.  (The constructor sets up
    * the event-handler to send those key presses to this method.)
    */
   protected void doKeyPress( String keyStrokeString ) {
      if (keyStrokeString.equals("Q")) {
         System.exit(0);
      }
      else if (keyStrokeString.equals("SPACE")) {
         if (isPaused())
            go();
         else
            pause();
      }
   }
   
   //-----------------------------------------------------------------
   
   /**
    * This main routine makes it possible to run the class MySpriteDemo
    * as an application. This routine opens a window that contains a panel 
    * of type MySpriteDemo.
    */
   public static void main(String[] args) {
      JFrame window = new JFrame("My Sprite Demo");
      SpritePanel panel = new MySpriteDemo();
      window.setContentPane(panel);
      window.pack();
      window.setResizable(false);
      window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
      window.setLocation( (screen.width - window.getWidth())/2, 
            (screen.height - window.getHeight())/2 );
      window.setVisible(true);
      panel.go(); // Required to start the animation!!
   }

}

Note that the MySpriteDemo class is defined as a subclass of SpritePanel, which is itself a subclass of JPanel. Thus, a MySpriteDemo object 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() routine, which 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 plain RectSprite to the window, and pressing the "B" key should add a RectSprite that has a black border. 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() instance method, which is defined in class SpritePanel. You should also program a few other keys. You can decide what they do. You could add some other kinds of sprites, for example. (Try adding a Explosion sprite at a random position in the window!) Be sure to program at least two more keys, and try to do something interesting!

Here are some more detailed instructions: First, 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");

The installKeyPress method is defined in the SpritePanel class. See the documentation for the method for more information.

Then, you have to modify the doKeyPress method to program the responses to the "R", "C", and other keys that you want to use in the program. 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: A Simple Game

SpriteDemo3 is a very simple game that allows the user to shoot at moving balls. The user controls the gun with the arrow keys. Left and right arrow keys rotate the gun, and the up arrow key fires the gun. I didn't give you the source code for SpriteDemo3, but there is a .jar file in /classes/cs124 that you can run. I expect to demonstrate this program at the beginning of lab.

For the third exercise of the lab, you should program an interactive game using sprites. You can start with a copy of your MySpriteDemo class, since the outline of the program will be similar. The new class should be named SpriteGame.

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 farily similar to SpriteDemo3, but for full credit, you should make some significant changes or add some significantly different features. If you are working on this lab with a partner, one of the things you add must involve creating another new subclass of Sprite.

To have a gun, you need to add a sprite of type SimpleGun to the panel. You need to do that in the SpriteGame constructor, since the gun is a permanent part of the game. You need an instance variable to represent the gun. A gun is already programed with the ability to fire bullets that can kill other sprites; you just have to call the fire() method in the SimpleGun object. (If the variable gun refers to the gun, that would really mean calling gun.fire(). SprintDemo3 fires the gun when the user presses the up-arrow key.) 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. See SpriteDemo2.java for an example of using the arrow keys in Sprite program.

The SimpleDemo3 constructor also adds a bunch of sprites to the panel to serve as targets for the gun. 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. (However, not everything that you might want to do will be possible with the sprite API.) Here are some ideas: