Show Navigation

8: Collision Detection

What We Will Cover


Illuminations

Questions from last class or the Reading?

Homework Questions?

  • A6: Visualizer Redux (3/28/12)
  • In your team, review each others programming projects from the last assignment
  • Select one good example project to share with the rest of the class using the criteria:
    1. Completeness
    2. Person has not shared before
    3. Extra features or creativity

8.1: Collision Detection: Asteroids

Learner Outcomes

At the end of the lesson the student will be able to:

  • Paint background scenes in a world
  • Simulate firing bullets in a scenario
  • Test for collisions in Greenfoot

8.1.1: About Asteroids

  • Asteroids is a video arcade game released in 1979 by Atari
  • At that time, most games were produced for and played on large arcade boxes like this one:

    Asteroids arcade game

  • Asteroids was a very popular game and spawned three arcade sequels
  • In addition, several competitors produced Asteroids clones
  • Versions have since been released for PlayStation, Nintendo 64 and Windows, among others

Asteroids Scenario

  • The book scenario is a recreation of an Asteroids game
  • Lets download and install this scenario of the game:

    Scenario file: asteroids-2b.zip.

  • Save the file to a convenient location like the Desktop.
  • Start Greenfoot and open the scenario (Greenfoot unzips the file for us)
  • Before we continue, lets review some key concepts from the chapter

Concept Review

  • Bounding box: the enclosing rectangle of an image
  • Collision detection: detecting the intersection of two or more objects
  • Superclass type: an object can be both its own type and the type of its superclasses
  • Casting: specifying a more precise type for an object than the one the compiler knows about

Core Game Mechanics

  • Core game mechanics are the actions in a game that the player performs repeatedly
  • Sometimes these are referred to as rules
  • In a video game, the rules are enforced by the allowed gameplay
  • Hopefully, the core game mechanics are enjoyable for the player

Check Yourself

  1. The invisible rectangle around an image used to detect collisions in known as a ________ box.
  2. True or false: a subclass of Actor, like Bug, can also be called an Actor because Actor is the superclass type.
  3. What are the core game mechanics of the Asteroids game? Click to show answer

More Information

8.1.2: Painting the Background

  • The Asteroids scenario is different from previous scenarios in that the world does not have a background image
  • Instead, the scenario creates its own background image
  • When we do not specify a background image for a world, Greenfoot uses a default white background
  • We want a different color than white for outer space

Changing the Color

  • To change colors we need to first import the Java Color class:
    import java.awt.Color;
  • Then we can use Java to paint the background:
    GreenfootImage background = getBackground();
    background.setColor(Color.BLACK);
    background.fill();
    
  • Notice that the background is actually a GreenfootImage
  • Thus we can use methods of the GreenfootImage class to work with the background

Painting Stars

  • After painting the background black, the constructor calls the method createStars()
  • Let us step through each line of the method which is shown below
  • Notice that stars are drawn using the methods we discussed in lesson 6.2.3: Drawing Shapes
  • However, we are drawing multiple shapes onto a larger image
  • Also note that we can draw a gray color by using equal amounts of red, green and blue

Method createStars()

private void createStars(int number)
{
    GreenfootImage background = getBackground();
    for (int i = 0; i < number; i++)
    {
         int x = Greenfoot.getRandomNumber(getWidth());
         int y = Greenfoot.getRandomNumber(getHeight());
         int color = 120 - Greenfoot.getRandomNumber(100);
         background.setColor(new Color(color,color,color));
         background.fillOval(x, y, 2, 2);
    }
}

Check Yourself

  1. The default background color of a Greenfoot world is ________.
  2. The purpose of the following lines of code from createStars() is to generate ________ coordinates.
    int x = Greenfoot.getRandomNumber(getWidth());
    int y = Greenfoot.getRandomNumber(getHeight());
    
  3. The following lines of code generates random colors that are shades of ________.
    int color = 120 - Greenfoot.getRandomNumber(100);
    background.setColor(new Color(color,color,color));
    

More Information

8.1.3: Turning and Moving

  • Turning and moving uses techniques we have discussed in the past
  • The checkKeys() method of Rocket responds to key presses by the user
  • Turning occurs when the user presses the left or right arrow keys
  • To review how to use the rotation methods see 1.4.6: Creating an Actor

Method checkKeys() of Rocket

private void checkKeys()
{
    ignite(Greenfoot.isKeyDown("up"));

    if (Greenfoot.isKeyDown("left"))
    {
        setRotation(getRotation() - 5);
    }
    if (Greenfoot.isKeyDown("right"))
    {
        setRotation(getRotation() + 5);
    }
    if (Greenfoot.isKeyDown("space"))
    {
        fire();
    }
}

Moving Forward

  • The rocket accelerates forward when the user presses the up-arrow key
  • Method ignite() selects the rocket image and applies the acceleration as needed
  • The acceleration is applied with a Vector like we used in lesson 7.1.2

Method ignite() of Rocket

private void ignite(boolean boosterOn)
{
    if (boosterOn)
    {
        setImage(rocketWithThrust);
        addForce(new Vector(getRotation(), 0.3));
    }
    else
    {
        setImage(rocket);
    }
}

Screen Wrap Around

  • One interesting feature of the rocket movement is how it wraps around the screen
  • Screen wrap around was a feature of the original Asteroids game
  • The move() method of SmoothMover has changed to support this feature
  • Can you spot the changes in the code shown below?

Method move() of SmoothMover

public void move()
{
    exactX = exactX + movement.getX();
    exactY = exactY + movement.getY();
    if(exactX >= getWorld().getWidth()) {
        exactX = 0;
    }
    if(exactX < 0) {
        exactX = getWorld().getWidth() - 1;
    }
    if(exactY >= getWorld().getHeight()) {
        exactY = 0;
    }
    if(exactY < 0) {
        exactY = getWorld().getHeight() - 1;
    }
    super.setLocation((int) exactX, (int) exactY);
} 

Check Yourself

  1. When the user presses the left or right arrow keys, the rocket rotates ________ degrees.
  2. When the user presses the up-arrow key, the value ________ is passed to the ignite() method in the following code:
    ignite(Greenfoot.isKeyDown("up"));
  3. True or false: if the ignite() method is called from the checkKeys() method, which is in turn called from act(), then the ignite method() is called every game cycle.
  4. For the following vector created in the ignite() method, the direction is the same as the Actor and the length is ________.
    addForce(new Vector(getRotation(), 0.3));
    
  5. Describe the screen wrap-around concept in English.

8.1.4: Firing Bullets

  • Method checkKeys() calls one other method:
    if (Greenfoot.isKeyDown("space"))
    {
        fire();
    }
    
  • The fire() method creates a new bullet and launches it from the rocket
  • Notice how bullets fire only after a delay and not continuously
  • The delay is controlled by the counting variable reloadDelayCount with the same technique we used in Exercise 7.3:
    1. Declare an instance variable to hold counting values and assign it a starting value
      private int reloadDelayCount;
    2. Change the value of the count in the act() method
      reloadDelayCount++;
    3. Use an if-statement to test if the count has reached its goal
      if (reloadDelayCount >= gunReloadTime)
  • This is a common pattern for controlling timing over multiple scenario cycles

Method fire() of Rocket

private void fire()
{
    if (reloadDelayCount >= gunReloadTime)
    {
        Bullet bullet = new Bullet(
            getMovement().copy(), getRotation());
        getWorld().addObject(bullet, getX(), getY());
        bullet.move();
        reloadDelayCount = 0;
    }
}

The Flight of the Bullet

  • The Bullet constructor adds 15 to the speed of the bullet so it moves faster than the rocket
  • The bullet disappears after 30 act() method calls with the counting technique describe above
  • If the bullet collides with an asteroid, the asteroid takes a hit
  • When hit, the asteroid breaks into two smaller pieces
  • If the asteroid was already small, it disappears instead

Check Yourself

  1. The three parts to controlling timing over multiple scenario cycles are:
    1. ________
    2. ________
    3. ________
  2. Find the lines numbers of each of the three steps for controlling timing in the Bullet class.

8.1.5: Collisions and Explosions

  • Oftentimes we want objects in a game to react when then collide
  • We saw this in the scenario when the rocket collides with an asteroid
  • The most commonly used technique is the idea of a bounding box
  • Bounding boxes are an invisible box around an image
  • The box is usually about the same size as the entire image as shown below:

    Bounding box from the textbook p. 110

  • As you can see, bounding boxes are not perfect
  • The transparent parts of an image may overlap before the collision takes place
  • Despite its problems, bounding boxes continue to be used because the computation is relatively fast and easy
  • Greenfoot has three methods in the Actor class that check for intersection of bounding boxes
  • The following example from the asteroids scenario in the textbook shows how to use one of these methods

Methods of the Actor Class to Detect Intersections of Objects

Method Description
getIntersectingObjects() Returns a list of Actor objects that intersect this object.
getOneIntersectingObject() Returns a single Actor that intersects this object.
intersects(Actor other) Returns true if the specified Actor intersects, otherwise returns false.

Example of Collision Detection

private void checkCollision()
{
    Actor a = getOneIntersectingObject(Asteroid.class);
    if (a != null)
    {
        World world = getWorld();
        world.addObject(new Explosion(), getX(), getY());
        world.removeObject(this);
    }
}

Realistic Collisions

  • To make collisions look realistic, we need to minimize the transparent pixels around the outside of an image
  • If a bounding box is not enough then we need other collision detection techniques
  • We will look at some other collision detection techniques in the future

Check Yourself

  1. True or false: When using bounding boxes, transparent pixels are ignored during collision detection.
  2. True or false: Bounding boxes are always accurate because all images are rectangles.
  3. True or false: To improve the accuracy of bounding box collision detection, leave transparent pixels around the image.
  4. True or false: Checking a bounding box for collisions is a relatively fast computation.

8.1.6: Game Over and Casting

  • Lets take a look at keeping score
  • The scenario has a ScoreBoard class, of which we can create an object
  • As we can see, the score board should be shown when the game is over
  • To track the game over condition, the Space class has a gameOver() method:
    public void gameOver()
    {
        // TODO: show the score board here. Currently missing.
    }
    
  • We can do many things when the game is over, including showing the score
  • To show the score board when the game is over, we construct a new ScoreBoard object
    ScoreBoard sb = new ScoreBoard(999);
  • Then we add the new ScoreBoard object to the world
    addObject(sb, getWidth() / 2, getHeight() / 2);
    
  • For now we are using a dummy number, which we will fix later

Calling gameOver()

  • Now we need to call the gameOver() method at the right time
  • One possible time is when the rocket hits an asteroid and explodes
  • We can add this call to the checkCollision() method of Rocket like in the following code
  • However, we get an error if we do

    cannot find symbol - method gameOver()

Method checkCollision() calling gameOver()

private void checkCollision()
{
    Actor a = getOneIntersectingObject(Asteroid.class);
    if (a != null)
    {
        World world = getWorld();
        world.addObject(new Explosion(), getX(), getY());
        world.removeObject(this);
        world.gameOver(); // error: will not compile
    }
}

Casting Needed

  • The problem with the above code is that World does not have a gameOver() method
  • However, Space has a gameOver() and getWorld() does return the subclass Space
  • So we change World to Space and recompile:
    Space world = getWorld();
  • Now we get a different error:

    incompatible types - found greenfoot.World but expected Space

  • The problem in this case is that getWorld() returns a Space object but the compiler thinks it is only a World object
  • To solve the problem, we need to tell the compiler explicitly that we have a Space object
  • We tell the compiler by using a cast:
    Space world = (Space) getWorld();
  • Note that casting does not change the type of the object
  • Casting just provides the compiler with more information

Check Yourself

  1. True or false: getWorld() returns the current subclass of world as a World object.
  2. True or false: casting permanently changes one type of object into another type
  3. The following code causes a compiler error:
    Space world = getWorld();
    Enter the code to correct the problem.

    answer

Exercise 8.1

In this exercise, we implement collision detection for the rocket in Asteroids. Also, we implement a simple scoring system.

Specifications

  1. Download the following file and save it in the scenario folder of Greenfoot.
  2. Scenario file: asteroids-2b.zip.

  3. Start Greenfoot and open the scenario.

    Greenfoot unzips the file for us.

  4. Open the Rocket class and add the line shown in bold to following method:
    private void checkCollision()
    {
        Actor a = getOneIntersectingObject(Asteroid.class);
        if (a != null)
        {
            World world = getWorld();
            world.addObject(new Explosion(), getX(), getY());
            world.removeObject(this);
            world.gameOver(); // error: will not compile
        }
    }
    
  5. Refer to section 8.1.6 and change the code so that it compiles and runs correctly.

    When running correctly, the rocket explodes when it hits an asteroid. If you have problems, ask a classmate or instructor for help as needed.

  6. Open the Space class and locate the gameOver() method, and add the following code:
    int finalScore = scoreCounter.getValue();
    ScoreBoard sb = new ScoreBoard(finalScore);
    addObject(sb, getWidth() / 2, getHeight() / 2);
    removeObjects(getObjects(Bullet.class));
    

    Note that the last line removes bullets from the scenario so that no more asteroids are destroyed after the rocket explodes.

  7. Compile the class and run the scenario with your changes and verify it works correctly.

    When running correctly, the Game Over Screen appears after the rocket hits an asteroid. Resolve any problems you find, getting help from a classmate or the instructor as needed.

  8. Add a method to the Space class to add to the score, like:
    public void addToScore(int amount)
    {
        scoreCounter.add(amount);
    }
    
  9. In the Asteroid class, locate the hit() method and add a call to the addToScore() method at the start of the method, like:
    Space space = (Space) getWorld();
    space.addToScore(1);
    

    Note that if you call addToScore() at the end of the hit() method, you may get a NullPointerException error. Can you see why?

  10. Compile and run your scenario to verify the score changes when an asteroid is hit.

    If you have problems, ask a classmate or the instructor for help as needed.

  11. Save your scenario as you may find it useful in completing the next programming project.

    Only submit the final scenario with all the lesson exercises completed, not the intermediate scenarios.

As time permits, read the following sections and be prepared to answer the Check Yourself questions in the section: 8.1.7: Review.

8.1.7: Review

Answer these questions to check your understanding. You can find more information by following the links after the question.

  1. Asteroids is a video arcade game released in _______. (8.1.1)
  2. What are core game mechanics of Asteroids? (8.1.1)
  3. What is the default background color of a Greenfoot world? (8.1.2)
  4. What is the purpose of the following lines of code from createStars()? (8.1.2)
    int x = Greenfoot.getRandomNumber(getWidth());
    int y = Greenfoot.getRandomNumber(getHeight());
    
  5. What does the following lines of code do? (8.1.2)
    int color = 120 - Greenfoot.getRandomNumber(100);
    background.setColor(new Color(color,color,color));
    
  6. Which of the following statements rotates the actor 5 degrees? (8.1.3)
    1. setRotation(getRotation() + 5);
    2. setRotation(5);
    3. setRotation(getRotation(), 5);
    4. setRotation(getRotation() = 5);
  7. Fact or fiction: a boolean parameter accepts one of two values, either true or false. (8.1.3)
  8. True or false: screen wrap around moves the actor from one side of the screen to the other when the actor's location exceeds the screen boundaries. (8.1.3)
  9. Which of the following is NOT part of controlling timing of multiple scenario cycles? (8.1.4)
    1. Declare a counter instance variable.
    2. Change the value of the counter in the act() method.
    3. Use an if-statement to test if the counter has reached its goal.
    4. Call the move() method of the actor.
  10. What is a bounding box? (8.1.5)
    1. A collision between two objects.
    2. The box that is drawn around an object.
    3. A highlighted area of an image.
    4. An imaginary box around an image.
  11. True or false: When using bounding boxes, transparent pixels are ignored during collision detection. (8.1.5)
  12. True or false: Bounding boxes are always accurate because all images are rectangles. (8.1.5)
  13. True or false: To improve the accuracy of bounding box collision detection, leave transparent pixels around the image. (8.1.5)
  14. True or false: Checking a bounding box for collisions is a relatively fast computation. (8.1.5)
  15. For the following statements, what is the name of the technique for the code inside the parenthesis? (8.1.6)
    World world = /* reset of statement here */
    Space space = (Space) world;
    
    1. Altering
    2. Casting
    3. Changing
    4. Redefining
  16. True or false: getWorld() returns the current subclass of world as a World object. (8.1.6)
  17. True or false: casting changes one type of object into another type (8.1.6)

8.2: Proton Waves

Learner Outcomes

At the end of the lesson the student will be able to:

  • Write code to cache images and other data
  • Make use of static variables
  • Animate multiple images of many game cycles
  • Interact with objects in a range

8.2.1: More Firepower!

  • The Asteroids scenario has support for a second weapon: the proton wave
  • The proton wave, once fire, radiates outward from the rocket
  • When the wave touches an asteroid, it damages or destroys the asteroid
  • Since it works in all directions at once, it is a more powerful weapon

The Proton Wave Scenario

  • Lets download and install the proton wave scenario:

    Scenario file: asteroids-3.zip.

  • Save the file inside the Greenfoot scenarios folder.
  • Start Greenfoot and open the scenario (Greenfoot unzips the file for us)
  • We can see the power of the proton wave in action by placing one in the scenario
  • As you can see, the wave starts small and grows larger

How the Proton Wave Works

  • Looking at the ProtonWave source code, we find three key methods
  • initializeImages() creates the images for the expanding wave
  • grow() manages the growing of the wave and removing it when it reaches full size
  • checkCollision() detects and explodes all the asteroids intersected by the wave
  • We will take a look at how the proton wave works in more detail in the following sections

Check Yourself

  1. The second weapon for the rocket in Asteroids is a ______ ______.
  2. True or false: the proton wave grows, destroying all asteroids in its path.
  3. The three key methods of the ProtonWave class are ________, ________ and ________.

8.2.2: Initializing Images

  • The initializeImages() method creates 30 images of the wave from small to large
  • The images are stored in an array variable:
    private static GreenfootImage[] images;
    
  • The reason for creating and storing the images is so the program can serve the images faster when needed
  • Images are large blocks of data and image manipulation takes large amounts of computer power
  • Storing images, or other data, for faster retrieval later is known as caching
  • The array, or other container, that stores the data is known as a cache
  • We can see how the array stores images in the following figure from the textbook

Array of Images for the Proton Wave

Textbook figure 7.3

Method initializeImages()

public static void initializeImages()
{
    if (images == null)
    {
        GreenfootImage baseImage =
            new GreenfootImage("wave.png");
        images = new GreenfootImage[NUMBER_IMAGES];
        for (int i = 0; i < NUMBER_IMAGES; i++)
        {
            int size = (i + 1) * (baseImage.getWidth()
                / NUMBER_IMAGES);
            images[i] = new GreenfootImage(baseImage);
            images[i].scale(size, size);
        }
    }
}

Notes on the Code

  • The 30 images are created by first loading a base image: wave.png
  • Then the for-loop uses the scale() method from GreenfootImage to change the size

    scale(width, height): changes this image to a new size.

  • The size is adjusted by multiplying the base size by (index + 1) / 30
  • Notice the test for the images array:
    if (images == null)
  • The test condition ensures the major part of the method is executed only once

Use of static Keyword

  • Notice that the images array is declared static
    private static GreenfootImage[] images;
    
  • We used the keyword static for constants in lesson 6.1.4
  • Using static means that the array is part of the class and is shared by all ProtonWave objects, which saves memory space
  • If you want to share data between all objects then declare the variable static
  • Since the images array is static, the initializeImages() method can be static as well
  • However, making the initializeImages() method static is not required
  • Methods declared static cannot access instance variables, only static member variables

Check Yourself

  1. Storing data so that future requests for the data can be served faster is known as ________.
  2. True or false: the images array variable stores all the image data.
  3. True or false: the array of GreenfootImages referred to by the images variable stores all the image data.
  4. True or false: each image of the proton wave is stored at various locations in main computer memory.
  5. The GreenfootImage method that changes the size of an image is ________.
  6. True or false: static member variables can be accessed by static methods
  7. True or false: instance member variables can be accessed by static methods

8.2.3: Growing the Wave

  • Now that we have the images ready, we can animate the proton wave
  • The animation is an effect that happens over multiple scenario cycles
  • What are the three parts to controlling timing over multiple scenario cycles? Click to show answer
  • Note that the grow() method is called from the act() method
  • As you can see, the grow() method follows the same pattern we discussed in lesson 8.1.4

Method grow()

private void grow()
{
    if (imageCount >= NUMBER_IMAGES)
    {
        getWorld().removeObject(this);
    }
    else
    {
        setImage(images[imageCount]);
        imageCount++;
    }
}

Smallest to Largest

  • The images in the array are arranged from smallest to largest
  • For every act() call, the grow() method is called and increments the imageCount variable
  • After the wave shows its largest size, the ProtonWave object is removed from the world

Check Yourself

  1. The three parts to controlling timing over multiple scenario cycles are:
    1. ________
    2. ________
    3. ________
  2. The array images are arranged from ________ to ________.
  3. How often is the grow() method called? answer

8.2.4: Interacting with Objects in Range

  • We looked at how the proton wave grows
  • Now we need to look at how it detects and destroys the asteroids
  • To detect the asteroids, the code calls the method:

    getObjectsInRange(int radius, Class cls): Returns a list of objects within a given radius of the calling Actor.

  • The method will return a list of all the actors within a given radius, like with radar
  • As with other collision detection methods, you can restrict the type to a single class of actors such as Asteroid
  • Once we get the list of objects in range, we process the list with a loop
  • Each asteroid on the list gets its hit() method called to apply damage
  • We can see how the method is used in the following code

Detecting Asteroids in Range

Textbook figure 7.4

Method checkCollision()

private void checkCollision()
{
    int range = getImage().getWidth() / 2;
    List<Asteroid> asteroids =
        getObjectsInRange(range, Asteroid.class);

    for (Asteroid a : asteroids) {
        a.hit(DAMAGE);
    }
}

Breaking Up

  • The hit() method call the breakUp() method
  • If the size is not too small, the breakUp() method executes the following code
  • The code replaces the current asteroid with two smaller asteroids
  • First the code calculates a direction and speed for the new asteroids
    int r = getMovement().getDirection()
        + Greenfoot.getRandomNumber(45);
    double l = getMovement().getLength();
    Vector speed1 = new Vector(r + 60, l * 1.2);
    Vector speed2 = new Vector(r - 60, l * 1.2);
    
  • Then it constructs the new asteroids and adds them to the world
    Asteroid a1 = new Asteroid(size/2, speed1);
    Asteroid a2 = new Asteroid(size/2, speed2);
    getWorld().addObject(a1, getX(), getY());
    getWorld().addObject(a2, getX(), getY());
    
  • Finally, the code removes the current asteroid from the world
    getWorld().removeObject(this);
    

Code From Method breakUp()

int r = getMovement().getDirection()
    + Greenfoot.getRandomNumber(45);
double l = getMovement().getLength();
Vector speed1 = new Vector(r + 60, l * 1.2);
Vector speed2 = new Vector(r - 60, l * 1.2);
Asteroid a1 = new Asteroid(size/2, speed1);
Asteroid a2 = new Asteroid(size/2, speed2);
getWorld().addObject(a1, getX(), getY());
getWorld().addObject(a2, getX(), getY());
a1.move();
a2.move();

getWorld().removeObject(this);

Check Yourself

  1. True or false: The method getObjectsInRange() returns a list of actors within a specified range
  2. In the checkCollision() method, the loop used to process the list of Asteroid objects is officially known as the ________ for loop, but most people call it the ________ loop.
  3. True or false: the breakUp() method will replace the current asteroid with two smaller asteroids as long as the asteroids have not gotten too small.

8.2.5: Adding Reload Delay

  • The proton wave could be fired at any time
  • However, the game would be boring if we could continuously use a proton wave
  • To improve the game play, Rocket adds a delay to the firing of the proton wave
  • The code involved in firing a proton wave is shown below
  • What do you notice about the pattern of the code? Click to show answer
  • Notice that the protonReloadTime variable is a constant (see lesson 6.1.4)

Rocket Code to Fire a Proton Wave

private static final int protonReloadTime = 500;
private int protonDelayCount;

public void act()
{
    // Other code omitted
    protonDelayCount++;
}

private void startProtonWave()
{
    if (protonDelayCount >= protonReloadTime)
    {
        ProtonWave wave = new ProtonWave();
        getWorld().addObject (wave, getX(), getY());
        protonDelayCount = 0;
    }
}

Check Yourself

  1. What are the three parts to controlling the reload delay over multiple scenario cycles? Click to show answer
  2. True or false: the protonReloadTime variable sets the number of act() cycles before the proton wave can fire again.
  3. The variable protonReloadTime is hard to differentiate from other variables. A better coding style to make its purpose more clear would be to use all ________ letters in its name.

Exercise 8.2

In this exercise, we add a proton wave recharge indicator.

Specifications

  1. Download the following file and save it in the scenario folder of Greenfoot.
  2. Scenario file: asteroids-3.zip.

  3. Start Greenfoot and open the scenario.

    Greenfoot unzips the file for us.

  4. Create a new actor named StatusBar without an image.

    For more information see lesson: 1.4.6: Creating an Actor.

  5. Add an import statement to the new class to import the Color library.
    import java.awt.Color;
    
  6. Add a constant for the default color.
    private static final Color DEFAULT_COLOR =
        new Color(0, 255, 0, 128);
    
  7. Add instance variables to store the data for the status bar.
    private Color barColor = DEFAULT_COLOR;
    private GreenfootImage image;
    private int width;
    private int height;
    private int percentFilled;
    
  8. Add a method named makeImage() that makes a rectangular image of size width by height containing a bar controlled by the percentFilled variable:
    private void makeImage()
    {
        image = new GreenfootImage(width, height);
        image.setColor(Color.WHITE);
        image.drawRect(0, 0, width - 1, height - 1);
        int barWidth = (int) (percentFilled / 100.0
            * (width - 2));
        image.setColor(barColor);
        image.fillRect(1, 1, barWidth , height - 2);
        setImage(image);
    }
    
  9. Add two constructors to the class that call the makeImage() method:
    public StatusBar()
    {
        this (100, 25);
    }
    
    public StatusBar(int newWidth, int newHeight)
    {
        width = newWidth;
        height = newHeight;
        makeImage();
    }
    
  10. Add a method named setPercentageFilled() to set the percentFilled variable:
    public void setPercentageFilled(int percentageFilled)
    {
        if (percentageFilled < 0)
        {
            percentageFilled = 0;
        }
        else if (percentageFilled > 100)
        {
            percentageFilled = 100;
        }
        if (percentFilled != percentageFilled)
        {
            percentFilled = percentageFilled;
            makeImage();
        }
    }
    
  11. Add a method named setBarColor() to set the color of the status bar:
    public void setBarColor(Color newBarColor)
    {
        if (newBarColor == null)
        {
            newBarColor = DEFAULT_COLOR;
        }
        if (!barColor.equals(newBarColor))
        {
            barColor = newBarColor;
            makeImage();
        }
    }
    
  12. Add two get-methods for accessing the width and height:
    public int getWidth() {
        return width;
    }
    
    public int getHeight() {
        return height;
    }
    
  13. Compile the class and verify there are no errors.

    If you have problems, compare your work to the StatusBar.java file or ask a classmate or the instructor for help as needed.

  14. Open the Rocket class and add an import statement to import the Color library.
    import java.awt.Color;
    
  15. In the Rocket class and add the following variable:
    private StatusBar protonLoad;
    
  16. Also in the Rocket class and add the following method:
    public void addedToWorld(World world)
    {
        protonLoad = new StatusBar(80, 20);
        protonLoad.setPercentageFilled(100);
        protonLoad.setBarColor(new Color(0, 255, 255, 128));
        int x = world.getWidth()
            - protonLoad.getWidth() / 2 - 10;
        int y = world.getHeight()
            - protonLoad.getHeight() / 2 - 10;
        world.addObject(protonLoad, x, y);
    }
    
  17. Finally, in the Rocket class, add the following statement to the act() method:
    protonLoad.setPercentageFilled(protonDelayCount / 5);
    
  18. Compile and run your updated scenario and verify there are no errors.

    If you have problems, ask a classmate or the instructor for help as needed.

  19. Save a copy of your scenario as we will be adding to it in the next exercise.

    Only submit the final scenario with all the lesson exercises completed, not the intermediate scenarios.

As time permits, read the following sections and be prepared to answer the Check Yourself questions in the section: 8.2.6: Review.

8.2.6: Review

Answer these questions to check your understanding. You can find more information by following the links after the question.

  1. Storing data so that future requests for the data can be served faster is known as __________. (8.2.2)
  2. True or false: a GreenfootImage array stores all the image data in the reference variable. (8.2.2)
  3. True or false: the array of GreenfootImages referred to by the reference variable stores all the image data. (8.2.2)
  4. True or false: each image of an array of images is stored at various locations in main computer memory. (8.2.2)
  5. The GreenfootImage method that changes the size of an image is __________. (8.2.2)
  6. True or false: static member variables can be accessed by static methods. (8.2.2)
  7. True or false: instance member variables can be accessed by static methods. (8.2.2)
  8. The three parts to controlling timing over multiple scenario cycles are: (8.2.3)
    1. _____________________________
    2. _____________________________
    3. _____________________________
  9. True or false: The method getObjectsInRange() returns a list of actors within a specified range. (8.2.4)
  10. Rewrite the following for-loop as an enhanced for loop: (8.2.4)
    for (int i = 0; i < rocks.size(); i++)
    {
        Asteroid rock = rocks.get(i);
        rock.hit(DAMAGE);
    }
    
  11. The variable protonReloadTime is hard to differentiate from other variables. What would be a better coding style to make its purpose more clear? (8.2.5)

8.3: Scoring and Text

Learner Outcomes

At the end of the lesson the student will be able to:

  • Write code to keep track of game scores
  • Display text and numbers in a scenario

8.3.1: Counting the Score

  • Asteroids has a simple scoring keeping system implemented in the Counter class
  • The Counter class displays the text "Score: " followed by a number
  • As the score changes, the number displayed changes to keep up
  • Let us look at the code for the Counter class

Constructing the Data

  • Starting at the top, we see one constant and 4 variables
    private static final Color textColor =
        new Color(255, 180, 150);
    
    private int value = 0;
    private int target = 0;
    private String text;
    private int stringLength;
    
  • The value is the number displayed and target is the actual score
  • The variable text is the string displayed before the number
  • The variable stringLength is the width of the text in pixels and is calculated in the constructor

Constructors for Class Counter

public Counter()
{
    this("");
}

public Counter(String prefix)
{
    text = prefix;
    stringLength = (text.length() + 2) * 10;

    setImage(new GreenfootImage(stringLength, 16));
    GreenfootImage image = getImage();
    image.setColor(textColor);

    updateImage();
}

Initializing a Counter

  • The job of the constructor is to initialize variables, which this one does
  • As part of the initialization, the constructor creates a new GreenfootImage:
    setImage(new GreenfootImage(stringLength, 16));
    
  • The variable stringLength is the width of the image
  • Looking at the last line of the constructor, we see it calls a method:
    updateImage();
  • The updateImage() method shows the key steps to displaying text in Greenfoot
  • Looking at this method, what steps do you see?

Method updateImage() Displays Text

private void updateImage()
{
    GreenfootImage image = getImage();
    image.clear();
    image.drawString(text + value, 1, 12);
}

Check Yourself

  1. For the following code snippet, the value of stringLength is ________.
    scoreCounter = new Counter("Score: ");
    ...
    public Counter(String prefix)
    {
        text = prefix;
        stringLength = (text.length() + 2) * 10;
    // rest of class omitted
    
  2. Using the stringLength calculation from the previous question, what is the width of the image and what is the height in the following code?
    new GreenfootImage(stringLength, 16)
  3. True or false: to display text in Greenfoot, we draw it on an image.

8.3.2: Drawing Text

  • Remember in lesson 6.2.3 we discussed how to draw shapes
  • To draw shapes, we called drawing and filling methods of the GreenfootImage class
  • One of the drawing methods is drawString():

    drawString(string, leftX, baselineY): draws the string on the image at the specified location.

  • Where:
    • string: the text to draw on the image
    • leftX is the x-coordinate from the left edge of the image
    • baselineY is y-coordinate of the baseline of the text
  • We can see these locations in the following image:

  • When we call drawString(), we are drawing text onto the image
    image.drawString(text + value, 1, 12);
  • In order to draw all the text, we must have an image that is of sufficient size
  • When providing space, we must allow room for the entire height including the descent
  • At the same time, we do not want an image that is overly large or we waste memory space
  • Leading is usually referred to as line spacing in word processing software
  • If we are drawing a single line of text then leading is optional
  • Note that when we draw the string, it will be drawn in the current color and font setting of the image
  • The following example shows how to create a transparent label
  • The image must be set into an Actor subclass
  • Images can only be displayed in World or Actor subclasses.

Creating a Label

String text = "test string";
int width = text.length() * 10;
GreenfootImage label = new GreenfootImage(width, 16);
label.setColor(Color.WHITE); // text color
label.drawString(text, 1, 12);
setImage(label); // set image for this Actor

Simplified Text Images

  • In version 2.0.1, Greenfoot introduced a new simplified way to create images with text
  • We can now use the following constructor of the GreenfootImage class:

    public GreenfootImage(string, fontSize, foregroundColor, backgroundColor)

  • Where:
    • string: the string to draw on the image
    • fontSize: the approximate size of the font in pixels
    • foregroundColor: the color of the text
    • backgroundColor: the color of the background behind the text
  • For example, we can construct a simple label like the above using:
    GreenfootImage label = new GreenfootImage("test string",
        16, Color.WHITE, new Color(0, 0, 0, 0));
    setImage(label);
    
  • We must still set our image into an Actor subclass
  • Note that we have no control over the placement of the text within the image

Check Yourself

  1. The GreenfootImage method to draw a string on an image is ________.
  2. True or false: a zero value for the y-coordinate of the drawString() method would hide most of the string.
  3. True or false: leading is another term for line spacing.
  4. Which letters of the alphabet have a descent?
  5. True or false: to display a label (image with text) we must set the image into an Actor subclass.
  6. True or false: Starting with Greenfoot 2.0.1, we can create an image with text drawn on it in one statement.

8.3.3: Text and Fonts

  • A font is a set of text characters
  • We can control the font of the text we write onto an image using class Font
  • Class Font contains methods and constants for specifying fonts
  • To use fonts, we must import the API class:
    import java.awt.Font;
  • The usual Font constructor takes three arguments:

    Font(fontName, fontStyle, fontSize)

  • Where:
    • fontName: name of the font like "Monospaced", "SansSerif", "Serif", etc.
    • fontStyle: Font.PLAIN, Font.ITALIC or Font.BOLD
    • fontSize: size of the font measured in points (1/72 of inch)
  • For example:
    int FONT_SIZE = 24;
    Font f = new Font("Serif",
                      Font.BOLD | Font.ITALIC,
                      FONT_SIZE);
    
  • Java guarantees that at least three font names will be available:
    • Monospaced
    • SanSerif
    • Serif
  • Any other font depends on the users system
  • Thus it is a good idea to use one of the guaranteed fonts in a scenario
  • Notice how we can combine multiple font styles by separating each style with a vertical bar "|"
  • The single vertical bar is known as the bitwise inclusive OR operator

Changing Fonts

  • The GreenfootImage class has methods to set and get fonts as shown below
  • Notice that the Counter class does not set a font
  • Instead, it uses the default font of GreenfootImage
  • However, the ScoreBoard class does get and set fonts in the makeImage() method
  • Here is an excerpt from the makeImage() method:
    Font font = image.getFont();
    font = font.deriveFont(FONT_SIZE);
    image.setFont(font);
    
  • Notice the calling of method deriveFont() in the above excerpt
  • This is another way to create a font: derive one font from another
  • The overloaded method deriveFont() creates a new font with a different style, size or other transformation

Some GreenfootImage Methods for Working with Fonts

Method Description
getFont() Returns the current font.
setFont(Font f) Set the current font for subsequent text operations.

Check Yourself

  1. A font is a set of text ________.
  2. Of the following, font ________ is not a parameter of the Font constructor.
    1. font name
    2. font style
    3. font size
    4. font character
  3. Java guarantees three fonts will exist: ________, ________ and ________.
  4. The three styles are available for a font are ________, ________ and ________.
  5. True or false: the only way to create a Font object is to call a Font constructor.

More Information

8.3.4: Making a Text Area

  • We have looked at how to make single lines of text
  • Now we look at how to make text areas with multiple lines of text
  • As an example, we look at the code for makeImage() in Scoreboard

Method makeImage() of ScoreBoard

private void makeImage(String title, String prefix,
        int score)
{
    GreenfootImage image =
        new GreenfootImage(WIDTH, HEIGHT);
    image.setColor(new Color(255,255,255, 128));
    image.fillRect(0, 0, WIDTH, HEIGHT);
    image.setColor(new Color(0, 0, 0, 128));
    image.fillRect(5, 5, WIDTH - 10, HEIGHT - 10);

    Font font = image.getFont();
    font = font.deriveFont(FONT_SIZE);
    image.setFont(font);
    image.setColor(Color.WHITE);
    image.drawString(title, 60, 100);
    image.drawString(prefix + score, 60, 200);
    setImage(image);
}

Preparing the Image

  • The first block of code prepares an image for drawing multiple lines of text
  • The following two lines make a translucent background used for the border:
    image.setColor(new Color(255,255,255, 128));
    image.fillRect(0, 0, WIDTH, HEIGHT);
    
  • Then the next two lines hollow out the inner area by overlaying a black translucent background:
    image.setColor(new Color(0, 0, 0, 128));
    image.fillRect(5, 5, WIDTH - 10, HEIGHT - 10);
    

Drawing the Text

  • The next block of code draws the text onto the prepared image
  • First the code creates and sets a new font:
    Font font = image.getFont();
    font = font.deriveFont(FONT_SIZE);
    image.setFont(font);
    
  • Then the code sets the color of the text:
    image.setColor(Color.WHITE);
    
  • After this the two lines of text are written onto the image:
    image.drawString(title, 60, 100);
    image.drawString(prefix + score, 60, 200);
    
  • Finally, the image is set for display:
    setImage(image);
    

Centering the Text Area

  • The game adds the score board in the gameOver() method of Space
  • The code makes sure that the score board is centered no matter the size of the scenario screen
  • Can you see how the centering works?

Method gameOver() of Space

public void gameOver()
{
    int finalScore = scoreCounter.getValue();
    ScoreBoard sb = new ScoreBoard(finalScore);
    addObject(sb, getWidth() / 2, getHeight() / 2);
    removeObjects(getObjects(Bullet.class));
}

Check Yourself

  1. A text area contains ________ lines of text.
  2. Of the following, ________ is NOT one of the steps for making a text area?
    1. Prepare an image
    2. Draw text on an image using drawString()
    3. Set the image to be displayed in an Actor
    4. Create a subclass of the Text class and calling its methods
  3. True or false: The (x, y) coordinates of an Actor are located at the Actor's center point when placed into a world

8.3.5: Restarting the Scenario

  • When the Asteroids game ends, we want to let players restart the game
  • To restart the game, we need to:
    • Remove the Asteroids from the world
    • Add a new set of asteroids to the world
    • Add a new rocket to the world
    • Set the score to 0
    • Remove the score board from the world
  • Most of the code belongs is in the Space class
  • However, we need to start the process in the ScoreBoard class

Starting the Restart

  • One way to restart the scenario is to have the user click the score board
  • In essence, we turn the ScoreBoard class into a simple button
  • To turn the class into a button we add code to the act() method as shown below

Method act() of ScoreBoard

public void act()
{
    if (Greenfoot.mousePressed(this))
    {
        Space space = (Space) getWorld();
        space.restartGame();
        space.removeObject(this);
    }
}
  • When the user clicks the score board, the method gets a reference to the Space world
  • With a reference to the Space world, we can call the restartGame() method
  • Method restartGame() is a new method that must be added to Space
  • We can see the method below

Method restartGame() Added to Space

public void restartGame()
{
    List asteroids = getObjects(Asteroid.class);
    removeObjects(asteroids);
    addAsteroids(startAsteroids);
    Rocket rocket = new Rocket();
    addObject(rocket, getWidth()/2 + 100, getHeight()/2);
    scoreCounter.add(-scoreCounter.getValue());
}
  • The first two lines get a list of all the Asteroid objects and removes them from the world
  • To remove the asteroids, the code calls the method:

    getObjects(Class cls): Returns a list of all the objects in the world.

  • We can restrict the type to a single class of actors like Asteroid
  • With the list we can call another method:

    removeObjects(Collection objects): Remove a list of objects from the world.

  • Using these two methods in sequence makes it easy to remove all the objects of a single type from a world
  • The next three lines create a new set of asteroids and add a rocket
  • Next, the scoreCounter is set to zero

Check Yourself

  1. To make a button out of an Actor, test for ________ in the act() method.
  2. The two methods of the World class do we call in sequence to remove all the objects of a single type from a world are ________.
    1. findObjects() and deleteObjects()
    2. gameOver() and stop()
    3. getObjects() and deleteObjects()
    4. getObjects() and removeObjects()

Exercise 8.3

In this exercise, we modify the ScoreBoard and add code to restart the game.

Specifications

  1. Start Greenfoot and open your completed Asteroids scenario from Exercise 8.2.

    If you do not have your completed exercise 8.2 scenario, then use scenario from the start of Exercise 8.2.

  2. Compile and run the scenario to verify it works well before you start.

    If you have problems, ask a classmate or the instructor for help as needed.

  3. Open the Space class and add a statement to import the List library.
    import java.util.List;
    
  4. Add the following method to the Space class:
    public void restartGame()
    {
        List asteroids = getObjects(Asteroid.class);
        removeObjects(asteroids);
        addAsteroids(startAsteroids);
        Rocket rocket = new Rocket();
        addObject(rocket, getWidth()/2 + 100, getHeight()/2);
        scoreCounter.add(-scoreCounter.getValue());
    }
    

    Compile the class and verify there are no errors. Resolve any errors you find, getting help from a classmate or the instructor as needed.

  5. In the ScoreBoard class, add the following act() method:
    public void act()
    {
        if (Greenfoot.mousePressed(this))
        {
            Space space = (Space) getWorld();
            space.restartGame();
            space.removeObject(this);
        }
    }
    
  6. Compile and run your scenario to verify all the changes work well.

    The scenario should restart when the player clicks the score board. If you have problems, ask a classmate or the instructor for help as needed.

  7. Save a copy of your completed lesson scenario to upload to Blackboard as part of assignment 7.

As time permits, read the following sections and be prepared to answer the Check Yourself questions in the section: 8.3.6: Review.

8.3.6: Review

Answer these questions to check your understanding. You can find more information by following the links after the question.

  1. True or false: to display text in Greenfoot, we draw it on an image. (8.3.1)
  2. For the following code snippets, what is the value of stringLength? (8.3.1)
    scoreCounter = new Counter("Score: ");
    ...
    public Counter(String prefix)
    {
        text = prefix;
        stringLength = (text.length() + 2) * 10;
    // rest of class omitted
    
  3. Using the stringLength calculation from the previous question, what is the width of the image and what is the height in the following code? (8.3.1)
    new GreenfootImage(stringLength, 16)
  4. The GreenfootImage method to draw a string on an image is _______________. (8.3.2)
  5. The optimal image size when drawing text on a transparent image is (8.3.2)
    1. the size of the scenario's world.
    2. large enough for the text and no larger.
    3. WIDTH wide and HEIGHT high.
    4. as large as need be to make the image look good.
  6. Images can only be displayed in World or _____________ subclasses. (8.3.2)
  7. True or false: a zero value for the y-coordinate of the drawString() method would hide most of the string. (8.3.2)
  8. True or false: Starting with Greenfoot 2.0.1, we can create an image with text drawn on it in one statement. (8.3.2)
  9. What is a font? (8.3.3)
  10. Which of the following is not a parameter of the Font constructor?
    1. font name
    2. font style
    3. font size
    4. font alphabet
  11. What three font names are guaranteed by Java to exist on all computer systems? (8.3.3)
  12. What three styles are available for a font? (8.3.3)
  13. True or false: the only way to create a Font object is to use a Font constructor. (8.3.3)
  14. Which of the following is NOT one of the steps for making a multi-line text area? (8.3.4)
    1. Prepare an image
    2. Draw text on an image using drawString()
    3. Set the image to be displayed in an Actor
    4. Create a subclass of the Text class and calling its methods
  15. True or false: The (x, y) coordinates of an Actor are located at the Actor's center point. (8.3.4)
  16. To make a button out of an Actor, test for _____________ in the act() method. (8.3.5)
  17. What two methods do we call in sequence to remove all the objects of a single type from a world? (8.3.5)

Wrap Up

Due Next:
A6: Visualizer Redux (3/28/12)
A7: Asteroids Deluxe (4/4/12)
  • When class is over, please shut down your computer
  • You may complete unfinished lesson exercises at any time before the due date.

Show Navigation

Last Updated: May 05 2012 @19:40:34