CS 124, Fall 2017
Lab 11: Sprites

For this lab, you can choose to work with a partner. 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.

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 is 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 eclipse project. You will have files named SpriteDemo1.java and SpriteDemo2.java in the default package and folders named "sprite" and "images." SpriteDemo2.java will have some errors that will go away when you do the first exercise.

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

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.

You should not modify any of the .java files in the sprite package. The programs that you write must work with the original versions.

This lab is due next week at the beginning of lab, as usual. You should submit three files named OvalSprite.java, MySpriteDemo.java, and SpriteGame.java. 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, ImageSprite represents a sprite that shows a small image, 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 other 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):

As an example of a program that uses sprites, you can read and run SpriteDemo1.java. The constructor for the SpriteDemo1 class adds 100 moving ImageSprites to the panel.

Exercise 1: A Simple Sprite Class

The program SpriteDemo2.java is supposed to show a colored oval-shaped sprite that the user can move around in the window by pressing the arrow keys. However, it uses a class named OvalSprite that does not exist. An OvalSprite should be displayed as a colored oval. For your first exercise, you will create that class. Most of the code is given to you, and you can just copy-and-paste it into the program.

Start by making a class named OvalSprite, using the New Class command. It can be in either the default package or in the sprite package. OvalSprite has to be a subclass of Sprite, so the first line must be modified to read

public class OvalSprite extends Sprite {

As soon as you save this modification, most of the errors in SpriteDemo2.java will go away. However, that class uses a constructor in the OvalSprite class that does not yet exist. The constructor specifies a width, a height, and a color for the new sprite. Any sprite already has a width and a height (which are inherited from the Sprite class). However, color is something that is added by OvalSprite. So, first, you should add a private instance variable named color, of type Color, to the OvalSprite class. Then you can define the constructor

public OvalSprite( int w, int h, Color c ) {
   setSpriteWidth( w );
   setSpriteHeight( h );
   color = c;
   setUseOvalForContains(true);
}

setSpriteWidth and setSpriteHeight were mentioned above. They are methods inherited from the Sprite class. The method setUseOvalForContains requires some explanation: When the system tests whether a bullet has hit a sprite, it has to use a different test, when it thinks that the sprite is oval-shaped, from the test it uses when it thinks the sprite is rectangle-shaped. Since the system doesn't actually know what the sprite looks like, you have to tell it. For an OvalSprite, of course, it should use the oval test. (All of which shows that sometimes you have to understand an API pretty throughly to use it effectively.)

At this point, SpriteDemo2 could be run, but the sprite will show up as a little black dot in the center of the panel, since the OvalSprite is using the draw() method that it inherits from Sprite, and that method is not appropriate for an OvalSprite. An OvalSprite needs to define a differenet draw() method, so that it can draw itself as an oval! So, add a draw() method to the OvalSprite class. This method draws a colored oval with a black border:

public void draw(Graphics g) {
    int x = (int)getX();  // Location of center of sprite.
    int y = (int)getY();
    int width = getSpriteWidth();    // Size of sprite.
    int height = getSpriteHeight();
    g.setColor(color);
    g.fillOval( x - width/2, y - height/2, width, height);
    g.setColor(Color.BLACK);
    g.drawOval( x - width/2, y - height/2, width, height);
}

With this addition, SpriteDemo2 will work correctly. It will show a largish red oval. The user can press the arrow keys to move the oval, and can press the "Q" key to end the program. (You should read the source code to see how installKeyPress and doKeyPress are used to implement the user actions.)

Although the class aleady works with SpriteDemo2, you should make the class more generally useful by adding getter and setter methods for the color. Consider using the "Generate Getters and Setters" command from the "Source" menu.

Also, of course, you should add comments to the class before you submit it!

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. Replace the entire 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();     // restart the animation
         else
            pause();  // pause the animation
      }
   }
   
   //-----------------------------------------------------------------
   
   /**
    * 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 "O" key, the program should add a plain OvalSprite 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 "E" key should add an Explosion to the panel in a random location.

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, such as ImageSprites with various images. Or add Bullets, which will blow up other sprites that hit them. Be sure to program at least two more keys, and try to do something interesting!

Here are a few more hints for writing the program: 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 "O" key, you need to say

installKeyPress("O");

Then, you have to modify the doKeyPress() method to program the responses to the "O", "E", "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

My SpriteGame 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 SpriteGame, but there is a .jar file in /classes/cs124 that you can run. I expect to demonstrate this program in class the day before the lab.

For the third exercise, you should program an interactive game using sprites. You could 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 fairly similar to my SpriteGame, but for full credit, you should make some significant changes or add some significantly different features.

To have a gun, you need a variable of type SimpleGun It makes sense for that variable to be a global instance variable. The gun should be added to the panel in the constructor, and you will need to use the gun variable when responding to keyboard input. 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(). My SpriteGame 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 my SpriteGame, this is done in response to the left and right arrow keys. (See SpriteDemo2.java for an example of using the arrow keys in a Sprite program.)

The SimpleGame 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. You do not have to follow exactly the same pattern.

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 get some extra credit for doing something that requires creating another subclass of Sprite. (However, not everything that you might want to do will be possible with the sprite API.) Here are some ideas: