12. Example Games

What We Will Cover


Illuminations

Questions from last class or the Reading?

Midterm Questions?

  • Midterm 2 statistics from Canvas
  • Code that does not compile contains an error
  • When given the tools to compile, your code must compile for a good score
  • How do you make a program with problems compile?
  • Post Midterm 2 Survey

Homework Questions?

Last Day to Withdraw

  • Last day to drop a full-term section with a grade of "W": 11/18/2017
  • See Admissions and Records: Dates and Deadlines
  • Grade calculation "what-if" available in Canvas Grades
  • Remember that there are many ways to earn XP
  • Approximate remaining XP available (NOT counting extra credit):
    Lab 11: 14
    Q11:    20
    Lab 12: 14
    Lab 13: 14
    Q13:    20
    Lab 14: 14
    Q14:    46
    Final: 350
    ----------
    Total  492 approx.
    

12.1: Making Sprites

Learner Outcomes

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

  • Describe the core mechanics of a platform game
  • Work with an abstract class
  • Create player characters from the Sprite class
  • Control Player movement
  • Manage player jumping, falling and landing

12.1.1: Platform Games

  • Today we look at how to make a platform game
  • A platform game requires the player to jump to and from suspended platforms
  • Also, the player may jump over obstacles or onto enemies
  • As we will see, jumping is a key part of platform games

Example Platformer

  • Here is a sequence from the game Wonder Boy (from Wikipedia)

    Wonder Boy platform sequence

  • What do you see the player doing in this platform game? Click to show answer
  • These actions that a player can make, and the rules governing them, are called game mechanics
  • In a platform game, jumping is a key activity -- a core game mechanic
  • Computer video games control the game mechanics through the programming of the game
  • Thus a platformer game provides the user with ways to walk, run and jump (among other actions)
  • To make a platformer game we will need to program ways to walk, run and jump

Example Scenario

  • Let us start exploring how to develop the basic actions for a platform game
  • We start by downloading and installing the Platformer1 scenario:

    Scenario file: platformer0.gfar or platformer0.zip.

  • Save the file to a convenient location like the Desktop
  • Start Greenfoot and open the scenario (Greenfoot unzips the file for us)
  • Looking at the classes we have:
    • GameManager: sets up the world and controls the game
    • Platform: something a character can stand on
    • Sprite: smooth moving superclass for sprites
  • We will take a closer look at Sprite in the next section

Check Yourself

  1. What a player can do and rules that define their actions are called ________ ________.
  2. In a computer video game, the actions a player can make and the rules of a game are controlled by the computer ________.
  3. What are the typical core mechanics of a platformer game? Click to show answer

12.1.2: Creating an Abstract Sprite

  • For a platform game, we need figures that will walk, run and jump
  • In games and computer graphics, characters are often called sprites
  • Greenfoot has an Actor class that handles most of the functions of a sprite
  • However, we want a sprite that moves more smoothly than the standard Actor
  • We will need smoother movement for jumping and enhanced animation, which we will discuss later
  • To enable smoother moving we start with a simple sprite class
  • The sprite class will include some parts of SmoothMover and Vector
  • Also, the class will include other methods common to all the characters in our platform game

Abstract Classes

  • When designing a program using inheritance, we often create a superclass that we do not want instantiated (created)
  • Instead, we only want subclasses of this class to be instantiated
  • The superclass contains code common to all the subclasses but should not be instantiated
  • In this case Sprite is abstract and cannot create an object
  • What would a Sprite object look like anyway?
  • What other classes have we seen before that are abstract?

Coding an Abstract Class

  • We can make any class abstract by adding the keyword abstract
  • Declaring a class abstract means that you cannot instantiate an object of the class
  • Instead, we must subclass the abstract superclass to make use of its operations
  • When a project starts we decide which actions should be common to a group of related classes
  • We develop these actions as methods and put the methods into an abstract superclass
  • Methods specific to a subclass remain in the subclass
  • As a project develops we may add more methods to the abstract superclass
  • What are common character actions in a platformer game? Click to show answer

Check Yourself

  1. You cannot create an object from a class that is declared ________.
  2. To write a class that from which you cannot create an object, use the keyword ________.
  3. True or false: an abstract superclass contains common methods shared by its subclasses.

12.1.3: Creating a Player Character

  • Our Sprite class is good for creating characters in a game
  • We can subclass the Sprite class to create either player or non-player characters (NPCs)
  • In this section we look at how to create player characters

Coding a Simple Player Class

  • We create a player character by subclassing Sprite
  • We discussed how to create a subclass in lesson 2.1.5
  • After subclassing Sprite, we look at the code for the Player class and see the following class header:
    public class Player extends Sprite
    
  • The keyword extends tells us that our Player is a subclass of Sprite
  • Player inherits all the public members of Sprite
  • By using the methods of Sprite, we make the code for Player -- simpler!
  • In addition, we can add other characters to the game by simply subclassing Sprite
  • Inheritance allows us to reuse the code from Sprite in other classes

Try It: Add a Player Character (4m)

  1. Download the following scenario file, save it to a convenient location like the Desktop and unzip the file.

    Scenario file: platformer0.gfar or platformer0.zip.

  2. Double-click the project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
  3. Right-click Sprite and add a player character named Player.java using the standing.gif image.

    Be careful of spelling to reduce coding problems in later exercises.

  4. Open the editor for the GameManager class and add a player character to the world like:
    Player player = new Player();
    addObject(player, getWidth() / 2, 209);
    
  5. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. In the class signature of the subclass, the keyword that identifies the superclass is ________.
    1. extends
    2. super
    3. super()
    4. inherits
  2. True or false: The purpose of inheritance is to reuse code.
  3. True or false: a subclass inherits only the public members of a superclass.
  4. True or false: inheritance allows subclasses to be simpler.

12.1.4: Controlling Movement

  • Now let us talk about moving the player character
  • Remember from lesson 2.2.1 that every object in a game has a position
  • We track the position in x and y screen coordinates
  • If we change the position at a certain rate of speed, the player seems to move
  • In lesson 3.5.2, we added player controls to our first game using the keyboard
  • We used the isKeyDown() method to check for keypresses:
    static boolean isKeyDown(String keyName)
    
  • We can use the same technique to add player movement to our platformer scenario
  • The following example code shows one way of implementing the keypress-checking code for our Player

Example Code to Control the Player Character

public void checkKeys()
{
    if (Greenfoot.isKeyDown("right"))
    {
        setVelocityX(2.25);
    }
    else if (Greenfoot.isKeyDown("left"))
    {
        setVelocityX(-2.25);
    }
    else
    {
        setVelocityX(0.0); // stop moving
    }
}

Multiple Alternatives

  • Notice the structure of the if-else statements
  • The second if statement is nested in the else clause of the first if statement
  • Also, that last else clause if part of the second if statement
  • We use this structure to allow us to make a single choice among three alternatives
  • This is a common programming pattern when we want only one result from a series of test conditions

Try It: Player Control (4m)

  1. Start Greenfoot and open the scenario from the last Try It! exercise.

    If you did not keep the scenario, then complete Try It: Add a Player Character now and then continue with these instructions.

  2. Open the editor for the Player class and add the checkKeys() method described above.
  3. Call the checkKeys() method from the act() method.
  4. In addition, add a call to move() at the end of the act() method.
  5. Test the player control by running the scenario and moving the player.
  6. Save your updated scenario as we will be adding to it as the lesson continues.
  7. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. True or false: you can nest if statements in the if clause, the else clause, or both.
  2. In the example code above, the test condition of the first if-statement must be ________ before the second if-statement executes.
  3. True or false: in the example code above, the player only stops moving if neither the right nor left cursor keys are pressed.
  4. What is another way to write the if-statements to control player characters?

12.1.5: Gravity and Falling

  • We still need to add the ability of our player character to jump and fall
  • Objects at the earths surface fall because of gravity
  • To add realism to our game, we need to simulate gravity

Gravity

  • Gravity is a force of attraction between objects with mass
  • Newton's famous formula for gravity is shown in the following image from Wikipedia:

    F=G(m1 x m2)/r^2

  • Since the Earth's gravity is huge compared to that of a person, we can ignore the mass of the smaller object
  • Also the change in the force of attraction is miniscule
  • Thus we can simplify the equation for the force of gravity to:
    F = m * G
  • Where:
    • F: is the force
    • m: mass of Earth
    • G: gravitational constant

Falling

  • The distance that an object moves as it falls near the Earth is given by the formula:

    formula for distance while falling

  • Where:
    • g = 9.81 m/s2 = 32.2 ft/s2 (Earth's gravitational force)
    • t = elapsed time
  • Gravity only acts in the vertical (y) direction near the Earth

Calculating Gravity in a Game

  • To apply the gravity formula to a game, we need to keep in mind:
    • Time is measured in game cycles
    • Vertical speed (dy) is updated each game cycle
    • The gravitational constant is measured in pixels moved per game cycle
  • By calculating vertical speed every single unit of time, we can eliminate the t variable
  • Also, we can multiply ½ by gravity to create a single constant
  • Thus, in programming terms, we write something like:
    velocityY = velocityY + GRAVITY;
    
  • Where:
    • velocityY is the current velocity in pixels per scenario cycle
    • GRAVITY is a gravitational constant that works for our game
  • Then we add velocityY to the y-position each game cycle to update the position
    y = y + velocityY;
    
  • Notice that gravity is increasing velocityY every game cycle

Try It: Adding Gravity (5m)

  1. Start Greenfoot and open the scenario from the last exercise.

    If you did not keep the scenario, then complete Try It: Player Control now and then continue with these instructions.

  2. Open the editor for the Player class and add a gravitational constant named GRAVITY like the following:
    public static final double GRAVITY = 0.7;
    
  3. In the Player class, add a method named applyGravity() like the following:
    public void applyGravity()
    {
        double velocityY = getVelocityY() + GRAVITY;
        setVelocityY(velocityY);
    }
    
  4. Add a call to applyGravity() in the act() method.
  5. Test the gravity by running the scenario in slow motion and watching how the player falls.

    Notice that we can still use the player controls to move while falling. Horizontal movement while falling is often allowed in platformer games.

  6. Save your updated scenario as we will be adding to it as the lesson continues.
  7. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. ________ is a force of attraction between objects with mass.
  2. When falling near the Earth, ________ only acts in the vertical direction.
  3. True or false: As you fall toward the ground, your speed gets faster every second that passes.
  4. When simulating falling, velocityY increases every game cycle due to ________.

12.1.6: Landing on Platforms

  • When a player falls they need to stop at some point
  • Traditionally in a platform game, a player stops falling when reaching a platform

Platforms

  • Platforms are objects in the game that a player can move upon
  • In our example class, we use a Platform class to build platforms
  • The Platform class just contains an image for the platform, like:

    tile image

  • We simply place Platform objects anywhere we want a platform
  • As we can see there is very little code in the Platform class

Landing

  • When the player is falling and nears a platform, we need to do two things:
    1. Move the player exactly to the top of the platform
    2. Set velocityY = 0 so the player stops falling
  • To detect when a player is nearing a platform and needs to stop, we use the Actor method:
    Actor getOneObjectAtOffset(int dx, int dy, Class cls)
    
  • Where:
    • dx: how far to look in the x-direction
    • dy: how far to look in the y-direction
    • cls: the type of object to look for (null for all objects)
  • Remember that we ran across this method before in lesson 8.1.3
  • To make use of the method, we write code like:

    Actor a = getOneObjectAtOffset(0, lookY, Platform.class);

  • If a Platform object exists within lookY distance below our player, the method returns the object
  • Otherwise, the method returns null (which means "no object")
  • We can use the returned value in an if statement to decide our course of action
    1. If there is no Platform object (null is returned) then the character keeps falling
    2. If the character is near a platform then we move the character to the platform and stop falling

How Far Will We Fall?

  • To use the getOneObjectAtOffset() method effectively, we need to calculate lookY
  • To calculate lookY, we first calculate how far the player will fall during the current cycle :
    velocityY + GRAVITY
    
  • Remember that Greenfoot measures location from the center of an Actor

    Falling

  • Thus we must add half the height of the sprite to the distance we check:
    int lookY = (int) (velocityY + GRAVITY + getHeight() / 2);
    

Checking the Vertical

  • Every scenario cycle we must check our falling and decide if we need to land
  • We manage the vertical movement by adding a checkVertical() method shown below
  • Then we call the checkVertical() method from the act() method instead of calling applyGravity()

Method checkVertical()

public void checkVertical()
{
    double velocityY = getVelocityY();
    int lookY = (int) (velocityY + GRAVITY + getHeight() / 2);
    // Check for vertical collision this cycle
    Actor a = getOneObjectAtOffset(0, lookY, Platform.class);
    if (a == null)
    {
        applyGravity();
    }
    else
    {
        // TODO: move to vertical contact
        setVelocityY(0.0); // stop falling
    }
}

Moving to Vertical Contact

  • With the above method, we see the player slows down just before landing
  • For a quicker landing, we add a move to vertical contact method
  • In the method we first cacluate the distance from the target to the player
    int h2 = (target.getImage().getHeight() + getHeight()) / 2;
    
  • Then we test to see if we are rising or falling and calculate our y-distance
  • Finally, we set our location with the new y-coordinate value

Method moveToContactVertical()

public void moveToContactVertical(Actor target)
{
    int h2 = (target.getImage().getHeight() + getHeight()) / 2;
    int newY = 0;
    if (target.getY() > getY()) // test up or down
    {
        newY = target.getY() - h2; // up
    }
    else
    {
        newY = target.getY() + h2; // down
    }
    setLocation(getX(), newY);
}

Try It: Landing on a Platform (4m)

  1. Start Greenfoot and open the scenario from the last exercise.

    If you did not keep the scenario, then complete Try It: Adding Gravity now and then continue with these instructions.

  2. Open the editor for the Player class and add the checkVertical() method described above.
  3. Change the act() method to call checkVertical() instead of applyGravity().
  4. Test the landing by running the scenario, moving the player off the platform and watching how the player lands.

    You will see a slow down before landing.

  5. Add the moveToContactVertical() method to the Player class and call the method from checkVertical()
  6. Save your updated scenario as we will be adding to it as the lesson continues.
  7. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. In platform games, players stop falling when they reach a ________.
  2. To detect if the character is nearing a platform, call the Greenfoot Actor method ________.
  3. True or false: to see if we are nearing a platform, we first calculate how far we will fall in one scenario cycle.
  4. The distance a character will fall is given by the formula ________.
    1. velocityY * GRAVITY
    2. velocityY * velocityY
    3. velocityY + GRAVITY
    4. velocityY + GRAVITY * GRAVITY

12.1.7: Jumping

  • While we can fall and land, we still need to add an important part of platformer games: jumping
  • Jumping is more complicated in that we cannot jump while in midair

    However, some platform games do allow double jumps, where the player character can jump again while at the top of a previous jump.

  • To control when the player can jump, we add a boolean instance variable to the Player class:
    private boolean canJump = true;
    
  • Then we add a method to control jumping like:
    public void jump()
    {
        if (canJump)
        {
            setVelocityY(-14);
            canJump = false;
        }
    }
    
  • Then when our player lands we re-enable jumping in the checkVertical() method:
    setVelocityY(0.0);
    canJump = true; // enable jumping
    
  • Finally, we add the following player control in checkKeys() method to start the jumping:
    if ((Greenfoot.isKeyDown("space") || Greenfoot.isKeyDown("up")))
    {
        jump();
    }
    
  • The space key is traditional for jumping though we can easily add more
  • Notice that the player is still able to walk through platforms
  • In the next section we discuss how to check for horizontal collision

Bumping Our Head

  • The current checkVertical() method only checks downwards
    int lookY = (int) (velocityY + GRAVITY + getHeight() / 2);
    
  • This means that our player can jump through a platform
  • To prevent this problem, we add the ability to check for collisions while rising as well as falling
  • In checkVertical() we change our lookY calculation as follows
    int lookY = 0;
    if (velocityY > 0) // falling
    {
        lookY = (int) (velocityY + GRAVITY + getHeight() / 2);
    }
    else // rising
    {
        lookY = (int) (velocityY + GRAVITY - getHeight() / 2);
    }
    

Try It: Add Jumping (4m)

  1. Start Greenfoot and open the scenario from the last exercise.

    If you did not keep the scenario, then complete Try It: Landing on a Platform now and then continue with these instructions.

  2. Open the editor for the Player class and add a boolean instance variable named canJump like:
    private boolean canJump = true;
    
  3. Add the following method to control jumping:
    public void jump()
    {
        if (canJump)
        {
            setVelocityY(-14);
            canJump = false;
        }
    }
    
  4. In the checkVertical() method, re-enable jumping when the player lands using code like:
    setVelocityY(0.0);
    canJump = true; // enable jumping
    
  5. Finally, add a player control in checkKeys() method to start the jumping:
    if ((Greenfoot.isKeyDown("space") || Greenfoot.isKeyDown("up")))
    {
        jump();
    }
    
  6. Test the jumping code by running the scenario, pressing the space bar and observing the jumping.

    Notice that we can jump through platforms.

  7. In checkVertical(), replace the current lookY calculation with the following:
    int lookY = 0;
    if (velocityY > 0) // falling
    {
        lookY = (int) (velocityY + GRAVITY + getHeight() / 2);
    }
    else // rising
    {
        lookY = (int) (velocityY + GRAVITY - getHeight() / 2);
    }
    
  8. There is no need to save the platformer0 scenario as we will start with platformer1 in the next exercise.
  9. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. True or false: players should be able to jump at any time.
  2. We control jumping with a ________ variable.
  3. Setting a negative y-velocity means our player character moves ________.
    1. left
    2. right
    3. up
    4. down
  4. After the character jumps, we re-enable jumping in the ________ method.

12.1.8: Horizontal Collisions

  • We use a similar approach for horizontal collisions as we did for vertical collisions:
    1. Calculate how far our player will move horizontally during the game cycle
    2. Check to see if a tile is within the distance the player will move
    3. If no collision is imminent then keep moving
    4. Otherwise, move to contact the tile and set velocity to zero
  • We can add these steps in the checkHorizontal() method of the Player class

Calculating the Look Ahead Distance

  • To detect if our player will collide with an object, we use the same Actor method:
    getOneObjectAtOffset(int dx, int dy, Class cls)
    
  • However, this time we will provide a value for dx rather than dy
  • The distance to look ahead depends on if we are moving left or right
    int lookX = 0;
    if (velocityX < 0)
    {
        lookX = (int) velocityX - getWidth() / 2;
    }
    else
    {
        lookX = (int) velocityX + getWidth() / 2;
    }
    
  • Remember that Greenfoot measures location from the center of an Actor

    Looking ahead

  • Thus we need to add half the width of the image to the lookX distance

Checking for a Collision

  • Once we have the correct values for lookX, we call the method:
    Actor a = getOneObjectAtOffset(lookX, 0, Platform.class);
    
  • If the method returns null, no object is in range and we keep moving left or right
    if (a != null) {
        moveToContactHorizontal(a);
        setVelocityX(0.0); // stop moving
    }
    
  • If the method returns an object, a collision is imminent
  • We then move to contact the actor and stop our movement

Check Yourself

  1. Given a velocityX, an Actor moves horizontally ________ during a cycle.
  2. Half the width of an actor's image is added to the look-ahead distance because Greenfoot tracks the coordinates of an actor from its ________.
  3. True or false: an actor needs to stop moving when it makes contact with a platform or wall so it does not penetrate the surface.
  4. True or false: when no horizontal collision is detected, an actor keeps moving.

Exercise 12.1: Horizontal Collisions (10m)

In this exercise, we do explore how to add horizontal collision checking to the Player class.

Specifications

  1. Download the Platformer1 scenario file and save it in the scenario folder of Greenfoot.
  2. Scenario file: platformer1.gfar or platformer1.zip.

    This scenario has minor enhancements from the Try it! exercises we completed previously.

  3. Start Greenfoot and open the scenario.

    Greenfoot unzips the file for us.

  4. Open the Player class and add a stub for the following method:
     public void checkHorizontal()
    

    A method stub is a syntactically correct method with few or no programming statements inside the method.

  5. In the act() method, call checkHorizontal() just before the call to checkVertical().

    Check to verify your code still compiles. If you have problems ask a guild member or the instructor for help.

  6. In the Player class, add a stub for the following method:
    public void moveToContactHorizontal(Actor target)
    

    Check to verify your code still compiles. If you have problems ask a classmate or the instructor for help.

  7. Finish writing the moveToContactHorizontal() using moveToContactVertical() method as a model.

    For more information about horizontal collisions see the top part of section 12.1.8: Horizontal Collisions. If needed, fill in the code using the listing below.

  8. Finish writing the checkHorizontal() using checkVertical() method as a model.

    For more information see section on Checking for a Collision in 12.1.8: Horizontal Collisions. If needed, fill in the code using the listing below.

  9. Save your Player.java file to submit to Canvas as part of assignment 10.

Listing of Horizontal Collision Methods

Listing of horizontal collision methods

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

12.1.9: Review

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

  1. In platform games, characters traditionally walk, run and jump on ______________. (12.1.1)
  2. What are core game mechanics of a platform game? (12.1.1)
  3. An image or animation, like a character, that is added to a larger scene is called a(n) ______________. (12.1.2)
  4. To make a class from which you cannot instantiate an object use the keyword _________. (12.1.2)
  5. The purpose of inheritance is to ________ code. (12.1.3)
  6. In the following code, when is the else clause executed? (12.1.4)
    if (Greenfoot.isKeyDown("right"))
    {
        moveRight();
    }
    else if (Greenfoot.isKeyDown("left"))
    {
        moveLeft();
    }
    else
    {
        stopMoving();
    }
    
    1. When the player presses the "right" key
    2. When the player presses the "left" key
    3. When the player presses both the "right" and "left" keys
    4. When the player does not press either the "right" or "left" keys
  7. ____________ is a force of attraction between objects with mass. (12.1.5)
  8. True or false: As you fall toward the ground, your speed gets faster every second that passes. (12.1.5)
  9. Which of the following statements simulates gravity in a platform game? (12.1.5)
    1. y = velocityY + GRAVITY;
    2. y = y + GRAVITY;
    3. velocityY = velocityY + GRAVITY;
      y = y + dy;
    4. y = y + GRAVITY;
      dy = dy + y
  10. When a character is closer to a platform than it can fall in one game cycle, it is time to __________. (12.1.6)
  11. What method can we use to detect if we are nearing a platform? (12.1.6)
  12. True or false: to see if we are nearing a platform, we first calculate how far we will fall in one scenario cycle. (12.1.6)

12.2: Animating Sprite Images

Learner Outcomes

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

  • Discuss animation using images
  • Write code to alternate between images
  • Use a list to shorten alternation code
  • Keep track of directions

12.2.1: Alternating Images

  • One problem we see with our character is that his arms and legs do not move
  • In other words, our character is not animated
  • To produce an animation, we need a series of images depicting movement
  • For example, we could use the following images to show our character running to the right

Images for Running Toward the Right

Run 1 Run 2 Run 3 Run 4 Run 5 Run 6 Run 7
run1 run2 run3 run4 run5 run6 run7
  • To run to the left we would need another seven images
  • What other sets of images might we need for our character in a platform game? Click to show answer
  • How many images total might we need for our main character?
  • To control all of these images, we need to improve our animation techniques

Displaying the Images

  • To improve our animation techniques, lets start with the easier bugs4.zip scenario from lesson 4
  • The technique we learned in lesson 4.3.5 was to use an instance variable for each image:
    public class Bug extends Mover
    {
        private GreenfootImage image1, image2, image3;
    
        public Bug()
        {
            image1 = new GreenfootImage("bug-a.gif");
            image2 = new GreenfootImage("bug-b.gif");
            image3 = new GreenfootImage("bug-c.gif");
            setImage(image1);
        }
    
        // methods omitted
    }
    
  • Then in lesson 4.3.6 we wrote code to change the image to switch the image, like:
    if (getImage() == image1)
    {
        setImage(image2);
    }
    else if (getImage() == image2)
    {
        setImage(image3);
    }
    else
    {
        setImage(image1);
    }
    
  • Sometimes we need to slow down how fast we switch the images to match other parts of the scenario
  • What technique have we used to control timing over multiple scenario cycles? Click to show answer
  • We can add the counter to the image switching code like:
    private int count = 0; // initialize counter
    //...
    count++; // increment counter
    //...
    if (count > 3 && getImage() == image1)
    {
        setImage(image2);
    }
    else if (count > 6 && getImage() == image2)
    {
        setImage(image3);
    }
    else if (count > 9)
    {
        setImage(image1);
        count = 1; // reset the counter
    }
    
  • Could we reverse the order of the if-statements?
  • How could we simplify the above code? Click to show answer

Check Yourself

  1. True or false: To show animations we need to display multiple images.
  2. True or false: we delay changing images by using a counting variable.
  3. What are the three steps to controlling timing over multiple scenario cycles? Click to show answer

12.2.2: Alternating Images in a List

  • We could set up an array to store the images like this:
    private GreenfootImage[] image;
    private int count = 0; // initialize counter
    
    public Bug()
    {
        image = new GreenfootImage[3];
        image[0] = new GreenfootImage("bug-a.gif");
        image[1] = new GreenfootImage("bug-b.gif");
        image[2] = new GreenfootImage("bug-c.gif");
        setImage(image[0]);
    //...
    
  • With a list, we can simplify our code to change images over multiple cycles
  • For instance, we could calculate the index of the current image from the count:
    int index = count / 3;
    
  • By calculating the image index, we can simplify our code like this:
    count++;
    if (count >= image.length * 3)
    {
        count = 0; // reset the count
    }
    setImage(image[count / 3]);
    

Adding More Images

  • If we used a series of if-statements to decode which image to display, our code becomes longer with every added image
  • However, by using a list, the code to select an image does not change
  • To add or delete images, we simply change the images on the list

Check Yourself

  1. Given the following code, the index selected for the image array is ________.
    count = 4;
    image[count / 3];
    
    1. 0
    2. 1
    3. 2
    4. 3
  2. True or false: the number three in the above code is the duration (number of game cycles) to display each image.
  3. True or false: By calculating the index of an array of images, instead of using if-statements, the code to chose which image to display in an animation becomes more complex to write as the number of images increases.

12.2.3: Setting Up an Animation

  • Now let us look at applying image animation for our platformer starting with this scenario:

    Scenario file: platformer2.gfar or platformer2.zip.

  • To animate images with arrays and loops, the first step is to load the images into a GreenfootImage object
  • The usual way to make an image is by loading the image from a file (see lesson 4.3.3)
    standImage = new GreenfootImage("standing.gif");
    
  • Oftentimes we will reuse images, such as when we have multiple actors of the same type in a scenario
  • If we want to reuse images then we cache them as we did with the Proton Wave images in lesson 10.2.2
  • The images are stored in "gif" files and named "runX.gif", where the X is the number of the image

    Run 1 Run 2 Run 3 Run 4 Run 5 Run 6 Run 7
    run1 run2 run3 run4 run5 run6 run7

  • The cached images are stored in an array
    private static GreenfootImage[] runRightImages;
    
  • After constructing the array, we use a loop to make a file name
    String fileName = "run" + (i + 1) + ".gif";
    
  • The file name is used to load the image, which is then assigned to an array element
  • We can see the complete method below

Method for Caching Images

private static final int RUN_COUNT = 7;
private static GreenfootImage[] runRightImages;

public static void initializeImages()
{
    if (runRightImages == null)
    {
        runRightImages = new GreenfootImage[RUN_COUNT];
        for (int i = 0; i < RUN_COUNT; i++)
        {
            String fileName = "run" + (i + 1) + ".gif";
            runRightImages[i] = new GreenfootImage(fileName);
        }
    }
}

Try It: Cache Images (5m)

  1. Start Greenfoot and open the platformer1 scenario from the last exercise.

    If you have not saved the previous exercises, then download the following scenario and merge all the prior scenarios with this exercise after class.

    Scenario file: platformer2.gfar or platformer2.zip.

  2. Open the editor for the Player class and add an array to cache the images like:
    private static final int RUN_COUNT = 7;
    private static GreenfootImage[] runRightImages;
    
  3. In addition, add the following method to the Player class:
    public static void initializeImages()
    {
        if (runRightImages == null)
        {
            runRightImages = new GreenfootImage[RUN_COUNT];
            for (int i = 0; i < RUN_COUNT; i++)
            {
                String fileName = "run" + (i + 1) + ".gif";
                runRightImages[i] = new GreenfootImage(fileName);
            }
        }
    }
    
  4. Add a default constructor to the player class that calls the initializeImages() method.
  5. Compile the code to verify it is correct.
  6. Add an instance variable to count the scenario cycles:
    private int runCount = 0;
    
  7. In addition, add a constant to set the duration of each image:
    private static final int DURATION = 5;
    
  8. Finally, update the moveRight() method as follows:
    public void moveRight()
    {
        setVelocityX(MOVE_SPEED);
        runCount++;
        if (runCount >= runRightImages.length * DURATION)
        {
            runCount = 0; // reset the count
        }
        setImage(runRightImages[runCount / DURATION]);
    }
    
  9. Compile and run your scenario to verify the player runs to the right.

    We will add moving to the left in the next section.

  10. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. True or false: the first step of creating an animation is to load the images into GreenfootImage objects.
  2. If images will be reused we should ________ the images.
  3. What are the image files loaded with the following code?
    private static final int COUNT = 3;
    private static GreenfootImage[] fooImages;
    //...
    for (int i = 0; i < COUNT; i++)
    {
        String fileName = "foo" + (i + 1) + ".gif";
        fooImages[i] = new GreenfootImage(fileName);
    }
    

12.2.4: Flipping Images

  • We loaded images for the player to display while moving to the right
  • We also need images for moving left
  • Rather than preparing and loading more images, we will use a method of GreenfootImage to create the left-facing images

    mirrorHorizontally(): flip the image around the x-axis

  • We can copy our right-facing images and then flip the copies to make left-facing images
  • For example, in the initializeImages() method we code:
    // Load the right-facing image
    faceRight = new GreenfootImage("standing.gif");
    // Make the left-facing image
    faceLeft = new GreenfootImage(faceRight);
    faceLeft.mirrorHorizontally();
    
  • As well as being easier to prepare, this approach runs faster because we do not load the left-facing images from a file
  • As a rule of thumb, loading from a file is about 1000 times slower than copying from main memory

Writing a Method to Flip Images

  • Similar to our method to load images, we can write a method to flip an array of images
  • The method signature has a single parameter, which is an array of unflipped images
    private static GreenfootImage[] flipImages(GreenfootImage[] imgs)
    
  • The method returns an array of flipped images
  • Inside the method, we first create an array to store the flipped images:
    GreenfootImage[] flippedImages = new GreenfootImage[images.length];
    
  • The size of the flipped array is the same size as the original array
  • After constructing the array, we write a counting loop to access each array element
    for (int i = 0; i < images.length; i++)
    
  • Inside the loop we put construct a new images from the old and then flip the new image
    flippedImages[i] = new GreenfootImage(images[i]);
    flippedImages[i].mirrorHorizontally();
    
  • Finally, we return the array of flipped images
    return flippedImages;
    
  • We call the method from initializeImages() like this:
    runLeftImages = flipImages(runRightImages);
    
  • We can see the complete flipImages() method below

Method to Flip Images

private static GreenfootImage[] flipImages(GreenfootImage[] imgs)
{
    GreenfootImage[] flipped = new GreenfootImage[imgs.length];
    for (int i = 0; i < imgs.length; i++)
    {
        flipped[i] = new GreenfootImage(imgs[i]);
        flipped[i].mirrorHorizontally();
    }
    return flipped;
}

Try It: Flip Images (4m)

  1. Start Greenfoot and open the scenario from the last Try It! exercise.

    If you did not keep the scenario, then complete Try It: Cache Images now and then continue with these instructions.

  2. Open the editor for the Player class and add an array to cache flipped images like:
    private static GreenfootImage[] runLeftImages;
    
  3. In the Player class, add the flipImages() method listed above.
  4. In the initializeImages() method, add the following method call just after the for-loop:
    runLeftImages = flipImages(runRightImages);
    
  5. Update the moveLeft() method as follows:
    public void moveLeft()
    {
        setVelocityX(-MOVE_SPEED);
        runCount++;
        if (runCount >= runLeftImages.length * DURATION)
        {
            runCount = 0; // reset the count
        }
        setImage(runLeftImages[runCount / DURATION]);
    }
    
  6. Compile the code to verify the syntax is correct.
  7. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. To flip an image around the x-axis, call the GreenfootImage method ________.
  2. Calling mirrorHorizontally() to create left-facing images is generally better than loading from a file because ________.
    1. the program requires less memory
    2. the program runs faster
    3. you do not need to prepare additional image files
    4. both b and c
  3. If each of seven image files takes 0.01 seconds to load, your program runs about ________ faster by copying and flipping images rather than by loading all images.

12.2.5: Adding Direction

  • We need images to display when our player stops moving
    private static GreenfootImage faceRight;
    private static GreenfootImage faceLeft;
    
  • We create the images in the initializeImages() method:
    // Load the right-facing image
    faceRight = new GreenfootImage("standing.gif");
    // Make the left-facing image
    faceLeft = new GreenfootImage(faceRight);
    faceLeft.mirrorHorizontally();
    
  • To make our player stand facing the direction it was moving, we need to track the direction of movement
  • To track direction, we add the following variables to Player:
    private static final int RIGHT = 0;
    private static final int LEFT = 1;
    private int direction = RIGHT;
    
  • Then in moveRight() we add:
    direction = RIGHT;
    
  • Similarly, in moveLeft() we add:
    direction = LEFT;
    
  • Now our stopMoving() method becomes:
    public void stopMoving()
    {
        setVelocityX(0.0);
        if (direction == LEFT)
        {
            setImage(faceLeft);
        }
        else
        {
            setImage(faceRight);
        }
    }
    

Exercise 12.2: Track the Player Direction (10m)

In this exercise, we track the direction of our player character and add standing images.

Specifications

  1. If you have not already completed Try It: Cache Images, do so now.
  2. In addition, if you have not already completed Try It: Flip Images, do so now.
  3. Add instance variables for the images to display when our player stops moving
    private static GreenfootImage faceRight;
    private static GreenfootImage faceLeft;
    
  4. Inside the if-statement of the initializeImages() method, add the following code to create the images:
    // Load the right-facing image
    faceRight = new GreenfootImage("standing.gif");
    // Make the left-facing image
    faceLeft = new GreenfootImage(faceRight);
    faceLeft.mirrorHorizontally();
    
  5. To track direction, add the following instance variables to Player:
    private static final int RIGHT = 0;
    private static final int LEFT = 1;
    private int direction = RIGHT;
    
  6. In moveRight() add the following line of code:
    direction = RIGHT;
    
  7. Similarly, in moveLeft() add the following line of code:
    direction = LEFT;
    
  8. Update the stopMoving() method as follows:
    public void stopMoving()
    {
        setVelocityX(0.0);
        if (direction == LEFT)
        {
            setImage(faceLeft);
        }
        else
        {
            setImage(faceRight);
        }
    }
    
  9. Compile and run your scenario to verify all the changes work well.

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

  10. Save your latest Player.java file to submit to Canvas as part of assignment 10.

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

12.2.6: Review

  • To provide animation, we need to save images in variables
    private GreenfootImage image1, image2, image3;
    
  • Rather than saving images in multiple variables, we can save them in an array
    private GreenfootImage[] images;
    
  • Storing images in an array provides several benefits
  • For example, if we name our images cleverly, we can load them using a for-loop and string concatenation like:
    for (int i = 0; i < COUNT; i++)
    {
        String fileName = "run" + (i + 1) + ".gif";
        runRightImages[i] = new GreenfootImage(fileName);
    }
    
  • Another benefit of arrays is that we can make use of the array index to select which image to display
    count++;
    if (count >= image.length * 3)
    {
        count = 0; // reset the count
    }
    setImage(image[count / 3]);
    
  • To save drawing and processing time, we were able to use the a method of GreenfootImage to create the left-facing images

    mirrorHorizontally(): flip the image around the x-axis

  • For example, we added code to create two images from one as follows:
    // Load the right-facing image
    faceRight = new GreenfootImage("standing.gif");
    // Make the left-facing image
    faceLeft = new GreenfootImage(faceRight);
    faceLeft.mirrorHorizontally();
    
  • To track direction, so we knew when to display some images, we added the following variables to Player:
    private static final int RIGHT = 0;
    private static final int LEFT = 1;
    private int direction = RIGHT;
    
  • Then we used an if-statement to select the image to draw

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

  1. To show animations we need to display images. (12.2.1)
  2. True or false: As the number of images to display increases, the code to chose which image to display becomes more complex to write. (12.2.1)
  3. . (12.2.3)
  4. Calling mirrorHorizontally() to create left-facing images is generally better than loading from a file because: (12.3.3)
    1. The program requires less memory
    2. The program runs faster
    3. You do not need to prepare additional image files
    4. Both b and c
  5. If each of seven image takes 0.1 seconds to load from a file, about how much faster does your program run by copying and flipping images than by loading the images? (12.3.3)
  6. The direction of a player is tracked using an integer variable. What is another way to track the state of the player's direction? (12.3.4)

12.3: Creating New Worlds

Learner Outcomes

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

  • Describe the advantages of using tile maps
  • Discuss the important characteristics of tiles
  • Construct tile maps

12.3.1: Tiles and Tile Maps

  • A common approach for developing game worlds is to use tile maps
  • A tile map is a technique for generating large graphics from a number of smaller graphics
  • The technique is still popular today because:
    1. Tile maps save main computer memory
    2. Tile maps increase real-time rendering performance

Laying Out Tile Maps

  • A tile map breaks down a game world into a grid as shown in the diagram below
  • Each cell in the grid contains either a small tile object or nothing

  • Tile-based maps are like creating a game world with building blocks
  • We use only a few different blocks but we can use as many of each block as we want
  • Each tile object in the map contains a reference to the image that belongs in a cell of the grid
  • When Greenfoot draws the tile at a location, it takes the referenced image and draws it on the screen
  • The program thus uses the same image at many places on the screen
  • By reusing the image many times, the program needs to store fewer images
  • Thus we only need a few small images to draw an entire scenario

Check Yourself

  1. For the above image, and ignoring the grid lines, you need ________ image(s) to draw the tile map.
  2. In the above tile map, the same image is displayed ________ times.
  3. Of the following statements, "________" correctly describes how a tile maps saves memory space.
    1. tiles are arranged in a two-dimensional grid to create a larger image.
    2. not all grids locations are filled with tile objects.
    3. the program draws the same small image at multiple places in the world
    4. each tile object is smaller than the entire world.

More Information

12.3.2: Creating Tiles

  • We can draw our own tiles or use a tile set (or sprite sheet)
  • A tile set is a collection of tiles arranged in a single larger image
  • Many artists have created tile sets and a few are available royalty free
  • The following tile set was provided for free by Ari Feldman (see: SpriteLib)

Example Tile Set from SpriteLib GPL

Example tile set

Creating Your Tiles

  • Notice that all the tiles are the same size
  • Having all tiles the same size makes drawing a tile map easier
  • To extract tiles from a tile map (or draw our own) we need an image processing program like:
  • To get us started, I cut out a few tiles from the above tile sheet
  • We will use these tiles to develop a tile map in the next sections

Tiles for Example Tile Map

Rock tile
rock-block.png
Left tile
rock-left.png
Middle tile
rock-mid.png
Right tile
rock-right.png
Pole
rock-pole.png

Check Yourself

  1. True or false: tiles in a tile set should all be the same size.
  2. To draw your own tiles, you need an ________ processing program.
  3. To extract tiles from a tile set, you need an ________ processing program.

More Information

12.3.3: Making a Tile Map

  • Let us look at how to create a tile map
  • We start by downloading and installing the Platformer 3 scenario:
  • Scenario file: platformer3.gfar or platformer3.zip.

  • Looking at the classes we have:
    • GameManager: constructs the world and controls the game
    • Platform (and subclasses): something a character can stand on
    • Sprite: smooth moving superclass for sprites
    • Player: the player controlled sprite
    • Sign: a background image (not a platform)
  • The tiles for the map are subclasses of Platform
  • The tile "engine" is in the GameManager class

Constructing the Example Tile Map

  • Lets open the editor for the GameManager class and look at the source code
  • What has changed compared to the last scenario? Click to show answer
  • Most of the changes have to do with drawing the tile map
  • The tile map is specified in the array MAP:
    private static final String[] MAP =
    {
        "B                       B",
        "B                       B",
        "B                       B",
        "B    LMMMMR    LMR      B",
        "B       P       P      LB",
        "B       P       P       B",
        "B       P     LMMMR     B",
        "BMMR    P       P       B",
        "B       PMR     P     LMB",
        "B       P       P       B",
        "B       P       P       B",
        "B     LMMMMMMMMMMMR     B",
        "B           P           B",
        "B           P           B",
        "BMMMR       P        LMMB",
        "B           P           B",
        "B           P            ",
        "B           P            ",
        "MMMMMMMMMMMMMMMR   LMMMMM"
    };
    
  • Each of the letters represents a type of tile
  • For example, "B" is a block and "L" is a left platform
  • By rearranging the letters in the MAP, we change the tile map
  • The createPlatform() method uses a loop to iterate through each string in the array MAP and call the method makeMapRow()
  • We discussed arrays of strings in lesson 7.3.3 and how to use loops with arrays in lesson 7.3.4
  • In the method makeMapRow() each character of the string is converted into a tile and added to the world
  • To understand how the makeMapRow() method works, we need to discuss characters and string methods

Check Yourself

  1. The tile "engine" for the example map is located in the ________ class.
  2. The tile map is contained in the ________ variable.
  3. The two methods that convert the map into tiles are ________ and ________.

12.3.4: Characters and String Methods

  • Recall from lesson 3.5.1 that a string is a sequence of characters
  • We specify string variables using the String data type
  • We can work with character data as well as strings
  • A character is a single letter, digit or special symbol (like: !@#$%)
  • We specify a character by enclosing it in single quotes (')
    • The quote marks are not part of the data
  • For example:
    'a'   'b'   'Z'   '3'   'q'   '$'   '*'
    
  • To declare a character variable, use the char data type like:
    char letter;
    letter = 'A';
    char letterB = 'B';
    
  • As shown in the example above, we can assign single character to a char variable

String Methods

Some Methods We Need

  • length(): Returns the number of characters in a string
    String str = "Hello";
    int numChars = str.length(); // returns 5
    
  • charAt(index): Returns a single character at index
    String str = "Hello, World!";
    char letterH = str.charAt(0); // returns 'H'
    char letterE = str.charAt(1); // returns 'e'
    char letterL = str.charAt(2); // returns 'l'
    
  • The index numbers in a string start at 0; thus the last character is always: str.length() - 1
    H e l l o , W o r l d !
    0 1 2 3 4 5 6 7 8 9 10 11 12

Check Yourself

  1. The type of delimiter used to enclose single characters of type char is the ________.
  2. True or false: "A" and 'A' are the same type.
  3. Write the code to declare a char variable named myChar and assign it a value of 'A'.

    answer

  4. Write the code to get the first character from the string named "test" and store it in a previously declared variable named myChar.

    answer

  5. The method used to determine the length of a string is ________.

12.3.5: Constructing a World

  • As we noted before, the method createPlatforms() iterates through each row of the MAP array
    private void createPlatforms()
    {
        for (int y = 0; y < MAP.length; y++) {
            makeMapRow(y);
        }
    }
    
  • The method makeMapRow() in turn extracts each character of the row
    private void makeMapRow(int y)
    {
        int tileY = topY + TILE_HEIGHT * y;
        String row = MAP[y];
        for (int x = 0; x < row.length(); x++)
        {
            int tileX = leftX + TILE_WIDTH * x;
            char tileType = row.charAt(x);
            // do something with each tile type
        }
    }
    
  • Once we know the tile type, we construct the appropriate tile and place it in the world
  • For example, if the tile type is a 'B':
    if (tileType == 'B')
    {
        addObject(new Platform(), tileX, tileY);
    }
    

Unbounded Worlds

  • Notice that the player character "sticks" to the top of the world when jumping
  • The "sticking" happens because Greenfoot restricts the y-coordinate of the actor to a value greater than or equal to zero
  • We can change this behavior in our call to super() of our World subclass
  • For instance, in our GameManager we can write
    super(800, 600, 1, false);
    
  • The fourth variable, false, is added to let actors move outside the boundaries of the world

Check Yourself

  1. True or false: the 'B' tile is a block of type Platform.
  2. True or false: no platform tile is constructed for a ' ' (space).
  3. If you want an unbounded world, set the fourth parameter of the World constructor, which is called using super(), to ________.
  4. The pixel coordinates of a tile are given by the formulas ________.
    1. (TILE_WIDTH * x, TILE_HEIGHT * y)
    2. (leftX + TILE_WIDTH * x, topY + TILE_HEIGHT * y)
    3. (x + TILE_WIDTH * leftX, y + TILE_HEIGHT * topY)
    4. (leftX + TILE_HEIGHT * x, topY + TILE_WIDTH * y)

More Information

  • World: Greenfoot API documentation

Exercise 12.3: Construct a Game World (10m)

In this exercise, we construct a new game world.

Specifications

  1. Download the Platformer3 scenario file and save it in a convenient location like the Desktop
  2. Scenario file: platformer3.gfar or platformer3.zip.

  3. Start Greenfoot and open the scenario.

    Greenfoot unzips the file for us.

  4. Open the GameManager class, locate the command that specifies the size of the Greenfoot world and try changing the size of the world.
    super(800, 600, 1);
    
  5. Change the size of the world and recompile the GameManager to see the effects.
  6. Restore the GameManager class to its original condition.

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

  7. In the GameManager class, locate the MAP array.
  8. Take about five minutes to rearrange the tile map and create a different world.
  9. Save your GameManager.java file to submit to Canvas as part of assignment 10.

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

12.3.6: Review

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

  1. What are three core game mechanics of a platform game? (12.3.1)
  2. True or false: A for-loop is often used to iterate a fixed number of times. (12.3.1)
  3. True or false: A class can be of multiple types. (12.3.1)
  4. True or false: Casting is a way to specify a more precise type than one the compiler knows about. (12.3.1)
  5. Generating a large graphics from a number of smaller graphics is known as a _______ map. (12.3.2)
  6. True or false: Tile map images are arranged in a grid to make a larger image. (12.3.2)
  7. Which of the following statements correctly describes how a tile maps saves memory space: (12.3.2)
    1. Tiles are arranged in a two-dimensional grid to create a larger image.
    2. Not all grids locations are filled with tile objects.
    3. The program draws the same small image at multiple places in the world.
    4. Each tile object is smaller than the entire world.
  8. True or false: Tiles in a tile map should all be the same size. (12.3.3)
  9. True or false: The example tile map is implemented as an array of strings. (12.3.4)
  10. What type of delimiter is used to enclose single characters? (12.3.5)
  11. What is the data type of variables that store a single character? (12.3.5)
  12. Are "A" and 'A' the same? Why or why not? (12.3.5)
  13. Given a string variable named test, what code do you write to determine its length? (12.3.5)
  14. Given a string variable named test, what code do you write to get the first character? (12.3.5)
  15. To add a new tile to the tile map engine, what changes must be made? (12.3.6)

12.4: Scrolling Worlds

Learner Outcomes

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

  • Discuss how to scroll a scenario
  • Write code to horizontally scroll a scenario

12.4.1: Scrolling the Background

  • In a 2D platform game, the map of an entire level is usually several screens wide
  • Here is a diagram of the concept:

  • The player stays centered on the screen
  • Even though the main character appears to move, the player is actually stationary
  • The other actors, including the tiles, move together (scroll) in the background
  • This scrolling background gives the illusion that the player is moving

Scrolling Backgrounds

  • We will create a scrolling background from the Platformer 3 scenario
  • The first step is to allow the actors to move outside the screen area
  • To allow actors outside the world, we change the GameManager
    super(800, 600, 1, false);
    
  • To scroll the background we need to move all the actors of the scenario every scenario cycle except the player
  • To move all the actors, we first call the method:

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

  • The method returns a List of Actor objects currently in the world
  • Since we will work with a List, we need to import the List interface
    import java.util.List;
    
  • After getting all the actors in the world, we use a loop to access each one in the list
  • After accessing an actor, we move it in the opposite direction the player is moving
  • We calculate the amount to move from the velocity of the player character
    double dx = getVelocityX();
    ...
    scrollHorizontal(dx);
    
  • Then in scrollHorizontal() we calculate the distance to move each object with:
    int moveX = (int) Math.round(a.getX() - dx);
    a.setLocation(moveX, a.getY());
    
  • The player stays stationary because the actor first moves forward by calling move()
  • After moving forward, the player then moves backwards the same amount because of the scrolling code
  • Since the world moves around player, we will put the scrolling code in the Player class
  • We can see the complete method listed below

World Method to Horizontally Scroll All Actors

public void scrollHorizontal(double dx)
{
    List<Actor> actors = getObjects(null);
    for (Actor a : actors)
    {
        int moveX = (int) Math.round(a.getX() - dx);
        a.setLocation(moveX, a.getY());
    }
}

Try It: Scrolling the World (4m)

  1. Download the following scenario file, save it to a convenient location like the Desktop and unzip the file.

    Scenario file: platformer3.gfar or platformer3.zip.

  2. Double-click the project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
  3. Open the editor for the GameManager class and add an import statement for List:
    import java.util.List;
    
  4. In addition, change the call to super to allow actors to move outside the world.
    super(800, 600, 1, false);
    
  5. Add the scrollHorizontal() method described above to the GameManager class.
  6. Compile the program to verify you implemented the new method correctly.
  7. To test the method, right-click on the world background and select void scrollHorizontal(double dx), entering an argument on the amount to scroll.

    You should see the background scroll by the number of pixels you enter. Negative numbers scroll right and positive numbers scroll left.

  8. Save your updated scenario as we will be adding to it as the lesson continues.
  9. When finished, please help those around you.
  10. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. True or false: Most platformer games have maps that are several screen's wide.
  2. True or false: a scrolling background makes the player appear to move even though the actor stays in the center of the screen.
  3. The methods of the World class called to get a list of all the objects in the world is ________.
    1. findObjects()
    2. allObjects()
    3. getObjects()
    4. listObjects()
  4. To access all the objects in a list we code a ________ statement.
  5. The number of pixels to move the background is calculated from the velocity of the ________.
  6. If there are 42 actors in the world, the following code will move how many of them? (answer)
    List<Actor> actors = getObjects(null);
    for (Actor a : actors)
    {
        int moveX = (int) Math.round(a.getX() - getVelocityX());
        a.setLocation(moveX, a.getY());
    }
    
  7. True or false: In the above code, to scroll vertically we need to change the y-argument, currently "a.getY()", of the setLocation() method call.

12.4.2: Overriding Methods

  • In our scenario, we have a move() method in the Sprite class
    public void move()
    {
        setLocation(x + velocityX, y + velocityY);
    }
    
  • We have been calling this move() method from the Sprite subclasses including Player
    public void act()
    {
        // ... other method calls omitted
        move();
    }
    
  • However, the move() method in Sprite is not correct for scrolling
  • To implement scrolling, we need to change the way the move() method works in the Player class
  • We can change the way a superclass method works by overriding the method
  • We change the behavior of the Player by writing a new move() method in Player

Method move() in the Sprite Superclass

public void move()
{
    setLocation(x + velocityX, y + velocityY);
}

Method move() in the Player Subclass

@Override public void move()
{
    super.move();
    double dx = getVelocityX();
    GameManager w = (GameManager) getWorld();
    if (w == null || dx == 0)
    {
        return;
    }
    w.scrollHorizontal(dx);
    setLocation(w.getWidth() / 2, getY()); // stay in horizontal center
}

Comparing the Superclass and Subclass Methods

  • Notice that both the superclass and subclass have a move() method
  • Also both methods have the same name and the same parameters (that is, none)
  • Thus, they have the same signature
  • The method from a subclass overrides the superclass method if it has the same signature
  • Note that a subclass method will NOT override a superclass method if the parameters are different

Using @Override

  • The compiler will verify that a method is overridden if the @Override annotation is included
  • This prevents a common mistake of misspelling a method name or not matching the parameters
  • Without the @Override annotation the compiler will not report a problem
  • Another benefit is the annotation makes our code easier to understand since it is more obvious which methods are overridden

Calling the Superclass Method

  • Note that we can call an overridden method of the superclass from a subclass
  • To do so, we use the keyword super like:
    super.move();
  • Sometimes people confuse overriding with overloading
  • Here is a table showing the similarities and differences

Overriding Verses Overloading

Overriding Overloading
  • Same method name
  • Same method name
  • Same signature
  • One method in superclass, one in subclass
  • Different signature
  • Both methods can be in same class

Try It: Overriding move() (3m)

  1. Start Greenfoot and open the scenario from the last Try It! exercise.

    If you did not keep the scenario, then complete Try It: Scrolling the World now and then continue with these instructions.

  2. Open the editor for the Player class and add the following move() method.
    @Override public void move()
    {
        super.move();
        double dx = getVelocityX();
        GameManager w = (GameManager) getWorld();
        if (w == null || dx == 0)
        {
            return;
        }
        w.scrollHorizontal(dx);
        setLocation(w.getWidth() / 2, getY()); // stay in horizontal center
    }
    
  3. Compile and run the program to verify you implemented the changes correctly.

    When the player moves, the character is supposed to stay horizontally centered on the screen while the background tiles scroll. However, the player gets off center. We will address this problem in the following sections. If you have problems ask a guild member or the instructor for help as needed.

  4. Save your updated scenario as we will be adding to it as the lesson continues.
  5. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. True or false: Overriding is when a method in the superclass and subclass have the same name.
  2. To have the compiler warn us when we do not override a method correctly add an ________ annotation before the method signature.
  3. To call a method of the superclass that has been overridden, use the keyword ________ and a dot before the method name.
    1. super
    2. this
    3. sub
    4. that

12.4.3: The instanceof Operator

  • As we test our scrolling code we notice that the background does not stay synchronized as we move the player back and forth
  • The background shifts unless we set the player velocity to an integer amount
  • This occurs because we must convert a double x position to int pixels
  • To correct the problem, we need to apply different code for a Player versus other actors
  • One way to make these changes is to test using the instanceof operator

Using the instanceof Operator

  • The instanceof operator checks if a test object is in the inheritance hierarchy of a specified class type
  • Syntax:
    testObject instanceof ClassName
    
  • Where:
    • testObject: the object to test
    • ClassName: the class to compare again
  • instanceof returns true if the testObject is a descendant of any ClassName
  • For example:
    List<Actor> actors = getWorld().getObjects(null);
    for (Actor a : actors)
    {
        if (a instanceof Player)
        {
            Player p = (Player) a;
            // do something with s
        }
        // other code here
    }
    
  • We can use the instanceof operator to test for Player objects in our loop and use exact movement on those objects
  • See the complete code of the move() method listed below, with changes in bold

Method scrollHorizontal() with instanceof Operator

public void scrollHorizontal(double dx)
{
    List<Actor> actors = getObjects(null);
    for (Actor a : actors)
    {
        if (a instanceof Player)
        {
            // Allow smooth moving
            Player p = (Player) a;
            double moveX = p.getExactX() - dx;
            p.setLocation(moveX, p.getExactY());
        }
        else
        {
            int moveX = (int) Math.round(a.getX() - dx);
            a.setLocation(moveX, a.getY());
        }
    }
}

Try It: Smooth Moving Player (3m)

  1. Start Greenfoot and open the scenario from the last Try It! exercise.

    If you did not keep the scenario, then complete Try It: Overriding move() now and then continue with these instructions.

  2. Open the editor for the GameManager class and update the scrollHorizontal() method as follows:
    public void scrollHorizontal(double dx)
    {
        List<Actor> actors = getObjects(null);
        for (Actor a : actors)
        {
            if (a instanceof Player)
            {
                // Allow smooth moving of Players
                Player p = (Player) a;
                double moveX = p.getExactX() - dx;
                p.setLocation(moveX, p.getExactY());
            }
            else
            {
                int moveX = (int) Math.round(a.getX() - dx);
                a.setLocation(moveX, a.getY());
            }
        }
    }
    
  3. Compile and run the program to verify you implemented the changes correctly.

    When the player moves, the character should stay horizontally centered on the screen while the background tiles scroll. However, the player still gets off center when bumping into walls. We will address this problem in the next section.

  4. Save your updated scenario as we will be adding to it as the lesson continues.
  5. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. True or false: we can test to see if a certain object is part of an inheritance hierarchy.
  2. To test if an object is part of an inheritance hierarchy we can use the ________ operator.
  3. Given the following code, ________ will be printed when the test() method is called.
    public class CodeTester {
        public static void test() {
            B b = new C();
            A a = b;
            if (a instanceof A) System.out.print("A");
            if (a instanceof B) System.out.print("B");
            if (a instanceof C) System.out.print("C");
            if (a instanceof D) System.out.print("D");
        }
    }
    
    class A {}
    class B extends A {}
    class C extends B {}
    class D extends C {}
    
    1. A will be printed
    2. AB will be printed
    3. ABC will be printed
    4. ABCD will be printed

Exercise 12.4: Scrolling the World Exactly (5m)

In this exercise, we add vertical scrolling code to our platformer scenario.

Specifications

  1. Complete the Try It activities we explored throughout the section including:
    1. Try It: Scrolling the World
    2. Try It: Overriding move()
    3. Try It: Smooth Moving Player

    If you have not already completed the exercises, do so now.

  2. Add a new method to the GameManager class named scrollVertical().
    public void scrollVertical(double dy)
    {
        // Add code here
    }
    
  3. Copy the following scrollHorizontal() code into scrollVertical() and adapt the code to scroll vertically.
    List<Actor> actors = getObjects(null);
    for (Actor a : actors)
    {
        if (a instanceof Player)
        {
            // Allow smooth moving
            Player p = (Player) a;
            double moveX = p.getExactX() - dx;
            p.setLocation(moveX, s.getExactY());
        }
        else
        {
            int moveX = (int) Math.round(a.getX() - dx);
            a.setLocation(moveX, a.getY());
        }
    }
    
  4. To test your new method, right-click on the world background and select void scrollVertical(double dy), entering an argument on the amount to scroll.

    You should see the background scroll up or down by the number of pixels you enter.

  5. Save your scenario files to submit to Canvas as part of lab 10.
  6. When finished please help those around you.

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

12.4.4: Review

  • In a 2D platform game, the map of an entire level is usually several screens wide
  • The player stays centered on the screen while the background moves
  • To scroll the background we move all the actors of the scenario at once while the player stays stationary
  • To scroll the background we first get a list of all the actors by calling the getObjects() method of World
  • Then we move all the actors by the amount of the player's velocity
  • A convenient way to implement the scrolling code in Player is to override the move() method from the Sprite class
  • To override a method we must have the same method signature in the superclass and the subclass
  • To have the compiler verify we have override a method correctly, we add the @Override annotation before the signature
  • Sometimes we want to call a method of the superclass that was overridden in a subclass
  • To do so, we use the keyword super like:
    super.move();
  • At times we may want to test the type of an object
  • To do so we can use the instanceof operator like:
    List<Actor> actors = getWorld().getObjects(null);
    for (Actor a : actors)
    {
        if (a instanceof Player)
        {
            Player p = (Player) a;
            // do something with p
        }
        // other code here
    }
    
  • The instanceof operator returns true is the object is in the inheritance hierarchy of the class

Check Yourself

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

  1. True or false: Most platformer games have maps that are several screen's wide. (12.4.1)
  2. True or false: a scrolling background makes the player appear to move even though the actor stays in the center of the screen. (12.4.1)
  3. The methods of the World class called to get a list of all the objects in the world is ________. (12.4.1)
    1. findObjects()
    2. allObjects()
    3. getObjects()
    4. listObjects()
  4. To access all the objects in a list we code a ________ statement. (12.4.1)
  5. The number of pixels to move the background is calculated from the ________ character's velocity. (12.4.1)
  6. True or false: Overriding is when a method in the superclass and subclass have the same name. (12.4.2)
  7. To have the compiler warn us when we do not override a method correctly add an ________ annotation before the method signature. (12.4.2)
  8. To call a method of the superclass that has been overridden, use the keyword ________ and a dot before the method name. (12.4.2)
    1. super
    2. this
    3. sub
    4. that
  9. True or false: sometimes we need to test if a certain class is part of a list. (12.4.3)
  10. To test if an object is a certain class type we can use the ________ operator. (12.4.3)
  11. Given the following code, ________ will be printed when the test() method is called. (12.4.3)
    public class CodeTester {
        public static void test() {
            B b = new C();
            A a = b;
            if (a instanceof A) System.out.print("A");
            if (a instanceof B) System.out.print("B");
            if (a instanceof C) System.out.print("C");
            if (a instanceof D) System.out.print("D");
        }
    }
    
    class A {}
    class B extends A {}
    class C extends B {}
    class D extends C {}
    
    1. A will be printed
    2. AB will be printed
    3. ABC will be printed
    4. ABCD will be printed

Wrap Up

Due Next:
Q11: Project Proposal (11/16/17)
Lab 12: Initial Prototype (11/21/17)
  • When class is over, please shut down your computer
  • You may complete unfinished lesson exercises at any time before the due date.
Home | Canvas | Schedule | Syllabus | Room Policies
Help | FAQ's | HowTo's | Links
Last Updated: November 20 2017 @20:01:29