13: NPCs and Files

What We Will Cover


Illuminations

Questions from last class or the Reading?

Homework Questions?

What to take next?

Census Bureau Employment Opportunities

  • Planning to hire half a million temporary workers
  • Purpose is to ensure the census gets completed in a timely and fair manner
  • Pay is $13 to $30 per hour
  • See 2020Census.gov/jobs

13.1: Creating Non-Player Characters

Learner Outcomes

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

  • Add non-player characters to a scenario
  • Resolve collisions between the player and NPCs
  • Discuss how to override a method
  • Describe the state of a sprite
  • Describe the purpose of a copy constructor

13.1.1: Adding Non-Player Characters

  • The hero of our platform scenario is all alone in his world
  • To make the scenario more interesting, we add non-player characters (NPCs)
  • A non-player character is any character not controlled by a human player
  • In a video game, the NPC is usually controlled by the computer
  • To develop an NPC, we generally follow these steps:
    1. Decide on the purpose of the NPC in the game
    2. Decide on the actions of the NPC and how it will interact with the player
    3. Make and load the animations
    4. Create a subclass for the NPC
    5. Code the actions of the NPC into its class
  • We will develop an example NPC in the following sections

Adding NPCs to a Scenario

  • We continue working with the example scenario to add NPCs Click for link
  • Compared to platformer3, our new version has the following changes:
    • New MAP layout
    • New images
    • New sound file: squish.wav
  • We will add or change these classes:
    • Creature: a new superclass for NPCs
    • Gorilla: an new class showing an example NPC
    • GameManager: add NPCs to the scenario
    • Player: add support for dying
    • Sprite: add support for tracking state

Finding the Player

  • One common problem in a scenario is obtaining a reference to a particular object
  • For example, when an NPC runs into a player, the player dies
  • To start the player dying, the NPC needs to call a method of the Player class
    player.startDying();
    
  • Our NPC needs a reference to the player to call this method

Getting a Reference to the Player

  • One easy way to get a reference to the player is to track the player in GameManager
    private Player george = new Player();
    
  • In the above, the GameManager adds a player to the scenario
  • The createPlatforms() method then calls adds a player to the scenario
    addObject(george, getWidth() / 2, 0);
    
  • To get a reference, we will add a getPlayer() method to the GameManager
    public Player getPlayer()
    {
        return george;
    }
    
  • Now when an NPC needs a reference to the player, it can get it from the GameManager
    GameManager world = (GameManager) getWorld();
    Player p = world.getPlayer();
    

Exercise: Getting the Player (4m)

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

    Scenario file: platformer4.gfar

  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 verify the following instance variable exists:
    private Player george = new Player();
    

    Hint: use the search function in the Tools menu of the editor: Tools > Find.... Then type in a word or phrase in the box at the bottom of the page.

  4. Add the following method to the GameManager class.
    public Player getPlayer()
    {
        return george;
    }
    
  5. Test the code by right-clicking on the world background sky and selecting the getPlayer() method.

    You will see a popup box explaining what was returned.

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

Check Yourself

  1. Of the following, ________ is an NPC.
    1. a player character
    2. a sprite controlled by the game player
    3. a sprite controlled by the computer
    4. a background image like a sign
  2. True or false: sometimes an actor needs to call methods of another actor.
  3. True or false: one convenient way to access an actor is to keep track of the actor in the scenario world.
  4. True or false: getWorld() returns the current subclass of world as a World object.
  5. The following code causes a compiler error:
    GameManager world = getWorld();
    Enter the code to correct the problem.

    answer

13.1.2: The State of a Sprite

  • When we have characters interact in a scenario, things happen to them (like getting blown up)
  • To track what is happening, we add the ability to track the state of a sprite

    State: a mode or condition of being

  • For example, a sprite like our player may be alive, dying or dead
  • To keep track of the ever changing state of a sprite, we add a variable and two methods to the Sprite class
    private int state;
    
    public void setState(int newState)
    {
        state = newState;
    }
    
    public int getState()
    {
        return state;
    }
    
  • In subclasses of Sprite we define methods work with the state methods like:
    public void startDying()
    {
        stopMoving();
        setVelocityY(0);
        setState(STATE_DYING);
    }
    
  • In addition, we define the meaning of the state integer in the subclasses

The Player's State

  • To track the state of the player, we add the following state variables in the Player class:
    public static final int STATE_NORMAL = 0;
    public static final int STATE_DYING = 1;
    public static final int STATE_DEAD = 2;
    
  • When the Player changes state, we set a new value like:
    setState(STATE_DYING);
    
  • To test the state of the Player, we write code like:
    if (getState() == STATE_DYING)
    {
        updateDyingState();
    }
    
  • For example, we add the following method using state

Player Methods Using State

/**
 * Returns true if this player is alive, false otherwise.
 *
 * @return true if this player is alive, false otherwise.
 */
public boolean isAlive()
{
    return (getState() == STATE_NORMAL);
}

/**
 * Start the dying sequence.
 */
public void startDying()
{
    stopMoving();
    setVelocityY(0);
    setState(STATE_DYING);
}

Exercise: Adding State (3m)

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

    If you did not keep the scenario, then complete Exercise: Getting the Player now and then continue with these instructions.

  2. Open the editor for the Sprite class and add the state variable.
    private int state;
    
  3. In addition, add the following two methods to the Sprite class:
    public void setState(int newState)
    {
        state = newState;
    }
    
    public int getState()
    {
        return state;
    }
    
  4. Compile the scenario to verify you added the code correctly.
  5. Now open the editor for the Player class and add the following constants:
    public static final int STATE_NORMAL = 0;
    public static final int STATE_DYING = 1;
    public static final int STATE_DEAD = 2;
    
  6. In addition, add the following methods to the Player class:
    public boolean isAlive()
    {
        return (getState() == STATE_NORMAL);
    }
    
    public void startDying()
    {
        stopMoving();
        setVelocityY(0);
        setState(STATE_DYING);
    }
    
    private void updateDyingState()
    {
        // Method stub to be completed later
    }
    
  7. Finally, update the act() method as follows:
    public void act()
    {
        if (getState() == STATE_DYING)
        {
            updateDyingState();
        }
        else
        {
            checkKeys();
            checkHorizontal();
            checkVertical();
        }
        move();
    }
    
  8. Compile the scenario to verify you implemented the changes correctly.
  9. Save your updated scenario as we will be adding to it as the lesson continues.
  10. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. What is meant by state? Give an example. (an answer)
  2. The two methods used to keep track of a sprite's state are ________ and ________.
  3. What are examples of states that may be appropriate for a sprite in a game?
  4. True or false: we add method stubs to our code as a placeholder until we complete the method body.

13.1.3: Creature Features

  • All NPCs are sprites and we want to add several NPCs to our game
  • To make it easy to add multiple NPCs, we add a general superclass named Creature

    Creature inheritance hierarchy

  • Then we subclass Creature to create individual NPCs like Gorilla

Creating the Creature Superclass

  • The Creature superclass provides a common set of code, making it easier to create multiple NPCs
  • Since Creature is a subclass of Sprite, it supports state and has a move() method
  • In addition, Creature implements the methods described below to support moving and dying
  • Also, Creature has a predefined set of actions taken in the act() method as shown below
  • To code an NPC, simply write two constructors as shown in the Gorilla class

Constructors and Methods of the Creature Superclass

Constructor/Method Description
Creature() Constructs a creature with no animations.
setAnimations(animLeft, animRight, dyingLeft, dyingRight) Sets the animations and default values for moving, collisions with the player and dying.
act() Move this sprite within its specified range and track the process if it dies.
moveLeft() Move to the left.
moveRight() Move to the right.
processCollision() Decide on the effects of a collision with the player and process accordingly.
setDyingTime(intNewTime) Set the number of act() method calls the dying animation is displayed.
startDying() Start the dying sequence.
setRangeX(leftX, rightX) Set the movement range from the leftmost to the rightmost x-coordinate.

Default act() Method

public void act()
{
    if (getX() <= leftX)
    {
        moveRight();
    }
    else if (getX() >= rightX)
    {
        moveLeft();
    }
    GameManager world = (GameManager) getWorld();
    Player p = world.getPlayer();
    if (intersects(p))
    {
        processCollision();
    }
    if (getState() == STATE_DYING)
    {
        updateDyingState();
    }
    move();
}

Overriding the act() Method

  • We may write a method in a subclass that has the same signature as a superclass
  • This is referred to as overriding a method
  • To override a method, both the superclass and subclass method must have the same method signature
  • If we want to change the action of an NPC, we override the act() method of the Creature superclass
  • We will look at an example of overriding in the next section when we create an NPC

Exercise: Adding Creature Features (3m)

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

    If you did not keep the scenario, then complete Exercise: Adding State now and then continue with these instructions.

  2. Open the Player class and verify the GRAVITY variable has public access.
    public static final double GRAVITY = 0.7;
    
  3. Download the Creature class and save it in the main scenario folder.

    If you have problems downloading or installing, ask a guild member or the instructor for help as needed.

  4. Close and restart Greenfoot and then open and inspect the new Creature class.
  5. Compile the Creature class to verify the installation.

    In you have problems compiling, ask a guild mate or the instructor for help as needed.

  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. The two public methods of the Creature class that support dying are ________ and ________.
  2. By default, an NPC will move back and forth between two endpoints. To set the endpoints, call the method ________.
  3. The method called in act() to check for a collision with the player is named ________.
  4. To change the default behavior of an NPC, override the ________ method.

13.1.4: Creating an NPC

  • Our first NPC is a gorilla whose purpose is to act as a guardian
  • Our gorilla will move back and forth patrolling an area
  • If the gorilla hits (touches) a player, then the player dies
  • If the player jumps on the gorilla, then the gorilla dies
  • We get the images for the animations, shown below, from Ari Feldman's SpriteLib
  • Notice that the Moving set of images are all the same size and that the size for a set is as small as possible
  • Having all images in a set the same size helps align the animation sequence
  • Minimizing transparent pixels around the image reduces the appearance of separation during collisions

Gorilla Animation Images

Moving
Gorilla 1 Gorilla 2 Gorilla 3
Dead
Gorilla dead

Setting Up the Animation

  • To test the Creature class, we add a Gorilla subclass with "Gorilla1.png" as the image
  • In our Gorilla class, we add variables for the movement speed and animation images:
    // Default values
    private static final double MOVE_SPEED = 0.75;
    private static final int DYING_TIME = 60;
    private static final int MOVE_COUNT = 3;
    private static final int MOVE_COUNT_PER_FRAME = 8;
    
    // Cache images for multiple instances.
    private static GreenfootImage[] moveLeftImgs;
    private static GreenfootImage[] moveRightImgs;
    private static GreenfootImage dieLeftImg;
    private static GreenfootImage dieRightImg;
    
  • The GreenfootImage variables will cache our images for multiple Gorilla objects
  • We create our animations using the technique covered in lesson 8.2.2 and lesson 12.2.3
  • We load and cache the images using the code shown below

Method to Load the Images in Gorilla

public static void initializeImages()
{
    if (moveLeftImgs == null || moveRightImgs == null)
    {
        moveLeftImgs = new GreenfootImage[MOVE_COUNT];
        moveRightImgs = new GreenfootImage[MOVE_COUNT];
        for (int i = 0; i < moveRightImgs.length; i++)
        {
            String fileName = "gorilla" + (i + 1) + ".png";
            moveLeftImgs[i] = new GreenfootImage(fileName);
            moveRightImgs[i] =
                new GreenfootImage(moveLeftImgs[i]);
            moveRightImgs[i].mirrorHorizontally();
        }
    }
    if (dieLeftImg == null || dieRightImg == null)
    {
        dieLeftImg =
            new GreenfootImage("gorilla-dead.png");
        dieRightImg = new GreenfootImage(dieLeftImg);
        dieRightImg.mirrorHorizontally();
    }
}

Coding the Constructor

  • To finish our Gorilla class we only need to add a constructor
  • The constructor simply sets animation images, number of cycles to show each frame and number of cycles over which the NPC dies
  • Unless we decide to add more behavior, we need only two methods in an NPC class:
    • Constructor
    • initializeImages()

Gorilla Constructor

public Gorilla()
{
    initializeImages();
    setAnimations(moveLeftImgs, moveRightImgs, dieLeftImg, dieRightImg);
    setMoveFrameCount(MOVE_COUNT_PER_FRAME);
    setDyingTime(DYING_TIME);
}

Scrolling with NPCs

  • One other complication we run into when adding NPCs is scrolling
  • As the world scrolls, we need to update the range of the NPC's movement
  • We use the instanceof operator to test for a Creature
  • When we find a Creature we update its range of movement

Updated scrollHorizontal() that Changes NPC Ranges

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

Exercise: Adding a Gorilla NPC (10m)

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

    If you did not keep the scenario, then complete Exercise: Adding Creature Features now and then continue with these instructions.

  2. Create a new subclass of Creature named Gorilla with "Gorilla1.png" as the image.
  3. Open the editor for the Gorilla class and add the following constants and instance variables just inside the opening class brace {
    // Default values
    private static final double MOVE_SPEED = 0.75;
    private static final int DYING_TIME = 60;
    private static final int MOVE_COUNT = 3;
    private static final int MOVE_COUNT_PER_FRAME = 8;
    
    // Cache images for multiple instances.
    private static GreenfootImage[] moveLeftImgs;
    private static GreenfootImage[] moveRightImgs;
    private static GreenfootImage dieLeftImg;
    private static GreenfootImage dieRightImg;
    
  4. Delete the act() method as the Gorilla uses the act() method in Creature.
  5. Next add the following initializeImages() method to the Gorilla class:
    public static void initializeImages()
    {
        if (moveLeftImgs == null || moveRightImgs == null)
        {
            moveLeftImgs = new GreenfootImage[MOVE_COUNT];
            moveRightImgs = new GreenfootImage[MOVE_COUNT];
            for (int i = 0; i < moveRightImgs.length; i++)
            {
                String fileName = "gorilla" + (i + 1) + ".png";
                moveLeftImgs[i] = new GreenfootImage(fileName);
                moveRightImgs[i] =
                    new GreenfootImage(moveLeftImgs[i]);
                moveRightImgs[i].mirrorHorizontally();
            }
        }
        if (dieLeftImg == null || dieRightImg == null)
        {
            dieLeftImg =
                new GreenfootImage("gorilla-dead.png");
            dieRightImg = new GreenfootImage(dieLeftImg);
            dieRightImg.mirrorHorizontally();
        }
    }
    
  6. Now complete the Gorilla class by adding the following constructor just after the variables:
    public Gorilla()
    {
        initializeImages();
        setAnimations(moveLeftImgs, moveRightImgs, dieLeftImg, dieRightImg);
        setMoveFrameCount(MOVE_COUNT_PER_FRAME);
        setDyingTime(DYING_TIME);
    }
    
  7. Compile the scenario to verify you added the code correctly.
  8. Test the Gorilla class by adding a new Gorilla object to the world and then running the scenario.

    The gorilla should walk. If the gorilla does not walk, make sure you deleted the act() method as specified in step 4. Notice that the gorilla will pay no attention to floors or walls. To create the proper illusion we need to carefully place and set the range of the gorilla's movement.

  9. Open the GameManager and add the following variables just inside the opening class brace {
    // Gorilla setup
    private static final int[] GOR_LEFT = { 640, 384 };  // leftX
    private static final int[] GOR_RIGHT = { 800, 667 }; // rightX
    private int numGorilla;
    

    These variables allow us to set the left and right ranges of a Gorilla's walk in the world.

  10. Also in GameManager, add the following to makeMapRow() towards the end of the else-if chain.
    else if (tileType == 'G')
    {
        Gorilla g = new Gorilla();
        g.setRangeX(GOR_LEFT[numGorilla], GOR_RIGHT[numGorilla]);
        GreenfootImage img = g.getImage();
        int gorillaY = TILE_HEIGHT / 2 - img.getHeight() / 2;
        addObject(g, tileX, tileY + gorillaY);
        numGorilla++;
    }
    

    Compile to make sure the code is correct.

  11. Next in GameManager and add gorilla's to the map using a 'G' character where shown.
    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     G P       B",
        "B     LMMMMMMMMMMMR     B",
        "B           P           B",
        "B           P           B",
        "BMMMR       P        LMMB",
        "B           P           B",
        "B           P            ",
        "B         G P            ",
        "MMMMMMMMMMMMMMMR   LMMMMM"
    };
    
  12. Compile and run the scenario and verify the gorillas appear and are walking correctly.
  13. Now in GameManager 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 Player
                Player p = (Player) a;
                double moveX = p.getExactX() - dx;
                p.setLocation(moveX, p.getExactY());
            }
            else if (a instanceof Creature)
            {
                Creature c = (Creature) a;
                // Allow smooth moving
                c.setLocation(c.getExactX() - dx, c.getExactY());
                // Set endpoints
                int leftX = (int)Math.round(c.getLeftX() - dx);
                int rightX = (int)Math.round(c.getRightX() - dx);
                c.setRangeX(leftX, rightX);
            }
            else
            {
                int moveX = (int) Math.round(a.getX() - dx);
                a.setLocation(moveX, a.getY());
            }
        }
    }
    
  14. Compile and run the scenario and verify the gorillas are walking correctly even while scrolling.
  15. Save your updated scenario as we will be adding to it as the lesson continues.
  16. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. True or false: the purpose of having a Creature superclass for Gorilla is to make it easier to add similar NPCs to the scenario.
  2. Why is Creature a subclass of Sprite rather than Actor? (answer)
  3. True or false: all the images of an animation should be the same size to make it easier to align the images during the animation.
  4. What is the order of files loaded by the above initializeImages() code? (answer)
  5. To add falling and landing capabilities to an NPC, what methods would we need to place in Creature or Sprite? Click to show answer

13.1.5: Collisions with the Player

  • The act() method of Creature checks for a collision using an intersection method:
    if (intersects(player))
    {
        processCollision();
    }
    
  • To win in a collision, the player must jump on top of a creature
  • To check this condition, we first calculate the lowest point of the player and the highest point of the creature
  • However, because the code does not react to a collision until the objects intersect, the player will always move below the top of the creature
  • We must calculate the maximum possible overlap to resolve the collision
    int maxOverlap = (int)(player.getVelocityY()
        + player.GRAVITY + 0.5);
    
  • Then we can test to resolve who won the collision
    if (playerBottom - maxOverlap <= creatureTop)
    
  • If the player has not descended more than the maximum overlap, then the player wins in the collision

Method processCollision() of the Creature Class

public void processCollision(Player player) {
    if (!player.isAlive()) {
        return;
    }
    int playerBottom = player.getY() + player.getHeight() / 2;
    int creatureTop = getY() - getHeight() / 2;
    int maxOverlap = (int)(player.getVelocityY() + player.GRAVITY + 0.5);
    if (playerBottom - maxOverlap <= creatureTop
        || getState() == STATE_DYING)
    {
        // player wins
        int h2 = (getHeight() + player.getHeight()) / 2;
        int backoffY = getY() - player.getY() - h2;
        player.setLocation(player.getX(), player.getY() + backoffY);
        player.setVelocityY(-player.getVelocityY()); // bounce
        if (getState() != STATE_DYING)
        {
            Greenfoot.playSound("squish.wav");
            startDying();
        }
    } else {
        // creature wins
        player.startDying();
    }
}

Simulating Dying

  • Now that we have creatures who can potentially kill our player, we need a set of animations for dying
  • Conflict and its consequences is common in games, literature and other entertainment
  • For our dying animation, the player collapses and fades away

Player Fades Away

Player dying image 1 Player dying image 2 Player dying image 3 Player dying image 4 Player dying image 5 Player dying image 6 Player dying image 7 Player dying image 8 Player dying image 9 Player dying image 10 Player dying image 11 Player dying image 12

Coding the Dying Process

  • To start coding the dying process, we add the following variables to Player:
    private static final int DYING_TIME = 60;
    private static final int DIE_COUNT = 12;
    private static GreenfootImage[] dieRightImages;
    private static GreenfootImage[] dieLeftImages;
    private int dyingCount;
    
  • Then in initializeImages() we add code to cache the images like we did with our moving images
    if (dieRightImages == null)
    {
        dieRightImages = new GreenfootImage[DIE_COUNT];
        for (int i = 0; i < DIE_COUNT; i++)
        {
            String fileName = "dying" + (i + 1) + ".png";
            dieRightImages[i] = new GreenfootImage(fileName);
        }
        dieLeftImages = flipImages(dieRightImages);
    }
    
  • To start the dying process we update our player's act() method:
    public void act()
    {
        if (getState() == STATE_DYING)
        {
            updateDyingState();
        }
        else
        {
            checkKeys();
            checkCollisionHorizontal();
            checkVertical();
            move();
        }
    }
    
  • In addition, we update the method to handle the dying process as shown below

Method to Handle the Process of Dying

private void updateDyingState()
{
    dyingCount++;
    if (dyingCount >= DYING_TIME)
    {
        setState(STATE_DEAD);
        getWorld().removeObject(this);
        return;
    }
    int imgNumber = dyingCount / (DYING_TIME / DIE_COUNT);
    if (direction == LEFT)
    {
        setImage(dieLeftImages[imgNumber]);
    }
    else
    {
        setImage(dieRightImages[imgNumber]);
    }
}

Exercise: Adding Collisions (5m)

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

    If you did not keep the scenario, then complete Exercise: Adding a Gorilla now and then continue with these instructions.

  2. Open the editor for the Player class and add the following variables:
    private static final int DYING_TIME = 60;
    private static final int DIE_COUNT = 12;
    private static GreenfootImage[] dieRightImages;
    private static GreenfootImage[] dieLeftImages;
    private int dyingCount;
    
  3. At the end of initializeImages(), add the following code to cache the dying images
    if (dieRightImages == null)
    {
        dieRightImages = new GreenfootImage[DIE_COUNT];
        for (int i = 0; i < DIE_COUNT; i++)
        {
            String fileName = "dying" + (i + 1) + ".png";
            dieRightImages[i] = new GreenfootImage(fileName);
        }
        dieLeftImages = flipImages(dieRightImages);
    }
    
  4. Verify the player's act() looks like the following code:
    public void act()
    {
        if (getState() == STATE_DYING)
        {
            updateDyingState();
        }
        else
        {
            checkKeys();
            checkCollisionHorizontal();
            checkVertical();
            move();
        }
    }
    
  5. Update the method updateDyingState() as shown below
    private void updateDyingState()
    {
        dyingCount++;
        if (dyingCount >= DYING_TIME)
        {
            setState(STATE_DEAD);
            getWorld().removeObject(this);
            return;
        }
        int imgNumber = dyingCount / (DYING_TIME / DIE_COUNT);
        if (direction == LEFT)
        {
            setImage(dieLeftImages[imgNumber]);
        }
        else
        {
            setImage(dieRightImages[imgNumber]);
        }
    }
    
  6. Compile the scenario to verify you added the code correctly.
  7. Test the dying updates by running into a gorilla.

    When the player runs into a gorilla, the player should fade away.

  8. Test that the player can kill the gorilla by jumping on it.
  9. Save your updated scenario as we will be adding to it as the lesson continues.
  10. When finished please help those around you.
  11. Be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. Why does our player have to (potentially) die?
  2. The method of Creature that checks for collisions with the player is ________.
  3. What condition must be true for the player to win a collision? (answer)
  4. Of the following steps, "________" does NOT occur when the player wins a collision with a creature.
    1. the player backs off in the up direction
    2. the player bounces off the top of the creature
    3. the creature starts dying
    4. the player automatically adds one to his "kill" count
  5. What happens when the creature wins a collision with the player? (answer)

Exercise 13.1: Adding an NPC (10m)

In this exercise, we write a new NPC. We will use the following images for the NPC which are included with the scenario.

Moving
Fly 1 Fly 2 Fly 3 Fly 4 Fly 5 Fly 6
Dead
Fly dead

Specifications

  1. Verify you have completed the Exercise activities we explored throughout the section including:
    1. Exercise: Getting the Player
    2. Exercise: Adding State
    3. Exercise: Adding Creature Features
    4. Exercise: Adding a Gorilla NPC
    5. Exercise: Adding Collisions

    If you have not already completed the exercises, or had problems with them, download the Platformer 5 scenario file and save it to a convenient location like the desktop. Then unzip and open the scenario.

  2. Scenario file: platformer5.gfar

    This scenario includes all the updates from prior sections.

  3. Create a new subclass of Creature named Fly using the "fly1.png" image.

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

  4. In the Fly class, add constants and image caching variables as follows:
    // Default values
    private static final double MOVE_SPEED = 2.5;
    private static final int DYING_TIME = 60;
    private static final int MOVE_COUNT = 6;
    private static final int MOVE_COUNT_PER_FRAME = 4;
    
    // Cache images for multiple instances.
    private static GreenfootImage[] moveLeftImgs;
    private static GreenfootImage[] moveRightImgs;
    private static GreenfootImage dieLeftImg;
    private static GreenfootImage dieRightImg;
    
  5. Also in the Fly class, add an intializeImages() method, using class Gorilla as an example.
    public static void initializeImages()
    {
        // Add code here
    }
    

    For more information see section 13.1.4: Creating an NPC. You will need to add six (6) images to each animation rather than the three used to make gorillas. Notice that the scenario already includes the images, which start with the word, "fly" (hint).

  6. Add a default constructor to the new class, using class Gorilla as an example. Within the constructor, create left and right movement animations and dying animations.

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

  7. In Fly, remove the act() method so that the Creature superclass act() method will operate instead.
  8. Test the Fly class by adding a new Fly object to the world and then running the scenario.

    The fly should move. If the fly does not move, make sure you deleted the act() method as specified in step 6.

  9. Open the GameManager and add fly's to the map using an 'F' character where shown.
    private static final String[] MAP =
    {
        "B                       B",
        "B                     F 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     F P       P     F B",
        "B       P     G P       B",
        "B     LMMMMMMMMMMMR     B",
        "B           P           B",
        "B           P           B",
        "BMMMR       P        LMMB",
        "B           P           B",
        "B           P            ",
        "B         G P            ",
        "MMMMMMMMMMMMMMMR   LMMMMM"
    };
    
  10. Add the following variable to the top of the GameManager to allow us to set the left and right ranges of a Fly's movement.
    private static final int[] FLY_LEFT = { 377, 377, 890 };
    private static final int[] FLY_RIGHT = { 1067, 550, 1067 };
    private int numFly;
    
  11. Add the following to makeMapRow() towards the end of the else-if chain.
    else if (tileType == 'F')
    {
        Fly f = new Fly();
        f.setRangeX(FLY_LEFT[numFly], FLY_RIGHT[numFly]);
        GreenfootImage img = f.getImage();
        int adjustY = TILE_HEIGHT / 2 - img.getHeight() / 2;
        addObject(f, tileX, tileY + adjustY);
        numFly++;
    }
    
  12. Compile and run the scenario and verify the flys are moving correctly even while scrolling.

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

  13. Save your scenario files to submit to Canvas as part of lab 13.
  14. When finished please help those around you.

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

13.1.6: Review

  • In this section we discussed non-player characters (NPCs)
  • A non-player character is any character not controlled by a human player
  • In a video game, the NPC is usually controlled by the computer

Remembering State

  • To interact with NPCs, we added to the Sprite superclass a simple system for determining a character's state
  • Our system was a single integer variable and methods to set and get the state
    private int state;
    
    public void setState(int newState)
    {
        state = newState;
    }
    
    public int getState()
    {
        return state;
    }
    
  • Then in a character we define the possible states using constant variables like:
    public static final int STATE_NORMAL = 0;
    public static final int STATE_DYING = 1;
    public static final int STATE_DEAD = 2;
    
  • To change state we simply call the get and set state methods like:
    setState(STATE_DYING);
    if (getState() == STATE_DYING)
    {
        // do something
    }
    

Developing NPCs

  • To help us develop NPCs we added a Creature superclass

    Creature inheritance hierarchy

  • The Creature class contains helper methods to make it easier to develop NPCs
  • We then worked through an example of developing an NPC in section 13.1.4: Creating an NPC

Player Interaction

  • The role of NPCs is usually to interact with the player
  • We went through an interaction system appropriate for a platformer game in section 13.1.5: Collisions with the Player

Adding NPC's to the Map

  • NPC's require some adjustment after adding them to the map
  • We save the adjustment values in arrays and count each NPC added
    private static final int[] GOR_LEFT = { 640, 384 };
    private static final int[] GOR_RIGHT = { 800, 667 };
    private int numGorilla;
    
  • We then make adjustments in the mapping like the following for a Gorilla
    else if (tileType == 'G')
    {
        Gorilla g = new Gorilla();
        g.setRangeX(GOR_LEFT[numGorilla], GOR_RIGHT[numGorilla]);
        GreenfootImage img = g.getImage();
        int adjustY = TILE_HEIGHT / 2 - img.getHeight() / 2;
        addObject(g, tileX, tileY + adjustY);
        numGorilla++;
    }
    
  • With the above modifications in place we add NPCs to the map like the following for a Gorilla
    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     G P       B",
        "B     LMMMMMMMMMMMR     B",
        "B           P           B",
        "B           P           B",
        "BMMMR       P        LMMB",
        "B           P           B",
        "B           P            ",
        "B         G P            ",
        "MMMMMMMMMMMMMMMR   LMMMMM"
    };
    

Check Yourself

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

  1. Which of the following is a non-player character? (13.1.1)
    1. A player character
    2. A sprite controlled by the game player
    3. A sprite controlled by the computer
    4. A background image like a sign
  2. True or false: To remember the state of a player we need to stoe a value in a variable. (13.1.2)
  3. What is the purpose of having a Creature superclass for Gorilla? (13.1.3)
  4. Which of the following superclass methods can be called from Gorilla? (13.1.3)
    1. The methods from Creature only.
    2. The methods of Creature and Player.
    3. The methods of Actor, Creature and Sprite.
    4. All of the methods of all classes can be called directly.
  5. True or false: The images of an animation should all be the same size to make animating the images easier. (13.1.3)
  6. To change the behavior of an NPC, override the __________ method of Creature. (13.1.3)
  7. True or false: By adding a number to the file names of the images, you can load all the images for an animation using a loop. (13.1.4)
  8. Which of the following steps does NOT occur when the player wins a collision with a creature? (13.1.5)
    1. The player backs off in the up direction
    2. The player bounces off the top of the creature
    3. The creature starts dying
    4. The player automatically adds one to his "kill" count
  9. What is the purpose of the following code in the processCollision() method? (13.1.5)
    player.setVelocityY(-player.getVelocityY());

13.2: Reading and Writing Files

Learner Outcomes

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

  • Discuss the organization of computer files
  • Describe files and streams
  • Discuss the types of files and streams
  • Layer streams for desired functionality
  • Write text files
  • Read text files

13.2.1: Files, Folders and Directories

  • The file system is how the computer organizes, stores and locates files
  • When working with computers, it is important to understand how the file system works
  • Files store things like documents, photos, videos, music and Java code
  • Computer file information is retained after an application ends or the computer is shut down

Organizing Files

  • Most computers organize files into folders (directories, catalogs, drawers)
  • Folders can contain other folders as well as files
  • Together, the folders make up a hierarchy of storage locations
  • We can think of our hard drive as one big master folder where other folders and files are organized and accessed
  • We see file and folder organization in the example folder below
  • Notice how files and folders are nested inside folders

Example Folder

Folder example

Programs and Files

  • Many computer programs read and write files
  • We can do the same in our Greenfoot scenarios
  • For instances, we could store a high score list of players
  • Also, we can read information like tile maps stored in files
  • Reading a tile map from a file would let us change maps without recompiling
  • To store and retrieve data in a file, we need two items:
    • A file
    • A file stream object
  • We will start by exploring files

Note: A scenario only can read files when it runs as an application. Reading and writing files is not allowed online.

Check Yourself

  1. To store information, the computer saves the data in a __________.
  2. True or false: folders can contain files or other folders.
  3. The building block that creates the hierarchy of a file system is a __________.
    1. Document
    2. File
    3. Folder
    4. Record
  4. What use can you think of for reading and writing files in a Greenfoot scenario?

More Information

13.2.2: Computer Files

File: a collection of data stored under a common name on a storage medium.

  • Files provide long-term storage of potentially large amounts of data
  • Usually, files are stored on non-volatile storage using:
  • Computer files are stored in binary
  • Each binary digit can have one of two values: 0 or 1
  • A bit is one binary digit
  • A byte is a group of eight bits

    8 bits to a byte

  • File are organized as a single sequence of bytes

    Byte 0 Byte 1 Byte 2 ... Byte n−1

  • The operating system keeps track of the number of bytes in a file
  • Files must have a name
    • Naming requirements depend on the underlying operating system (OS)
  • The file system organizes files into folders or directories

Understanding Binary -- or Not?

Only 10 types of people

Types of Files

  • All data in a file is ultimately just zeros and ones (bits)
  • However, we can classify the files based on their data into two broad categories: text and binary

Text Files

  • In text files, the bytes in the file represent printable characters
  • Text files usually store Latin based characters (like English) in one byte
  • The byte for each character is defined by a standard such as ASCII (ASCII table) or UTF-8
  • Every line of text is delimited by end-of-line characters:
    • Unix/OS-X: "\n"
    • Windows: "\r\n"
  • We can read text files because each byte is interpreted by a program as textual characters
  • Some of these programs, like TextPad, then display the textual data to our computer's screen
  • Since there are many programs that read and display text, text files are called human readable

Binary Files

  • Data other than text is usually referred to as binary data
  • Each bit represents some type of encoded information like program instructions or integer data
  • Binary files are easily read by the computer but not by humans
  • The following table compares binary and text values saved in a file
  • First we consider the value "1234" as ASCII codes and compare these bits to a binary value of 1234

Comparing Binary and Textual Data Formats

Description Byte 0 Byte 1 Byte 2 Byte 3
"1234" as char's '1' '2' '3' '4'
"1234" as ASCII codes (bytes) 49 50 51 52
"1234" as ASCII codes (bits) 00110001 00110010 00110011 00110100
(int) 1234 as binary bits 00000000 00000000 00000100 11010010

Exercise: What's My File Type (4m)

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

    Scenario file: fileio.gfar or fileio.zip.

  2. Double-click the project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
  3. Compile the scenario.
  4. Start a text editor like TextPad.
  5. Open the FileWorld.java source code file in the text editor.

    Right-click and select Send to TextPad.

  6. Open the FileWorld.class compiled file in the text editor

    Right-click and select Send to TextPad.

  7. Notice the difference between the two files.

    Which file is a binary file and which is a text file?

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

Check Yourself

  1. Program I/O is the term used for program I________ and O________.
  2. True or false: files can be used as both the input and output of a program.
  3. Of the following, the advantage of using files for program I/O is ________ .
    1. data still exists after the program ends
    2. input can be automated (rather than entered manually)
    3. output from one program can be input to another
    4. all of these
  4. A file resembles a(n) ________ of bytes.
  5. True or false: data in a file is restricted to characters only.
  6. Since text files can be displayed in any text editor, text files are known as ________ readable files.
  7. Files that store non-text data are known as ________ files.
  8. Of the following, the binary file is ________.
    1. Bug.class
    2. Bug.java
    3. BugWorld.java
    4. README.TXT

13.2.3: Streams

Stream: an ordered list of data delivered over time

  • To read and write files, we use a stream
  • A stream connects a program to data, such as that stored in a file
  • Input stream: an object that provides a sequence of bytes to a program over time

    Input stream

  • Output stream: an object that accepts a sequence of bytes from a program and delivers the data over time

    Output stream

File Streams

File stream: a data delivery path used to connect a program to a file.

  • File streams can be either input or output streams
  • File input streams receive data from a file
  • File output streams send data to a file
  • Each file our program uses will need a separate file stream object

Basic File Streams

  • Since there are two types of files, there are streams to support each type
  • We use character streams to read and write text files:
  • Similarly, we use binary streams to read and write binary files:
  • Our focus in this lesson is the character (text) streams

Text Streams

  • Streams are implemented using classes and objects
  • To read and write text, we use character streams
  • A character stream processes characters through a stream
  • There is a separate set of classes for writing and reading character streams
  • These classes are organized in an inheritance hierarchy as shown below

Some Classes from the Reader Hierarchy

Reader: Foundation class for reading character streams
  |
  +--BufferedReader: Add a buffer to the stream
  |
  +--InputStreamReader: Bridge character to binary streams
       |
       +--FileReader: Connects a stream to a file

Some Classes from the Writer Hierarchy

Writer: Foundation class for writing character streams
  |
  +--BufferedWriter: Add a buffer to the stream
  |
  +--PrintWriter: Convert binary data to text
  |
  +--OutputStreamWriter: Bridge character to binary streams
       |
       +--FileWriter: Connects a stream to a file

Check Yourself

  1. A ________ is an ordered list of data delivered over time.
  2. True or false: streams can be defined as input, output or bi-directional (input and output).
  3. The superclass for streams that read text is ________.
  4. The superclass for streams that write text is ________.

13.2.4: Layering Streams

  • Usually, any one stream does have not all the functionality we want
  • Instead, it is common to "layer" two or more streams

Writing to a Text Stream

  • To write text to a file, we use a FileWriter stream:
    FileWriter fw = new FileWriter("out.txt");
    
  • However, a FileWriter does not know how to write different types of data
  • That's why we combine the FileWriter with a PrintWriter:
    PrintWriter out = new PrintWriter(fw);
    
  • Now we can use the overloaded print() methods of a PrintWriter object to write various data types

Buffering

  • Every time a byte or sequence of bytes is written or read to a file, the OS must perform a series of operations
  • Buffering improves performance of I/O by reducing the number of calls to the OS
  • A program copies each output to a block of memory called a buffer
  • The entire buffer is output to disk at once
  • One long disk access takes less time than many smaller ones

Buffer

Adding a Buffered Layer

  • We use a buffered stream classes to improve I/O performance:
  • We can add a buffering layer to our previous stream example:
    FileWriter fw = new FileWriter("myfile.txt");
    BufferedWriter buf = new BufferedWriter(fw);
    PrintWriter out = new PrintWriter(buf);
    
  • Another way to write the same functionality:
    PrintWriter out = new PrintWriter(
                      new BufferedWriter(
                      new FileWriter("myfile.txt")));
    
  • The second way shows the layers of the streams more clearly
    • Top layer: convenience methods for converting data to characters
    • Middle layer: buffering for improved performance
    • Bottom layer: character output stream
  • After the layers are built we work with the top layer only

Check Yourself

  1. Buffering improves input and output performance because ________.
    1. it reduces the number of file read and write operations.
    2. it increases the number of file read and write operations.
    3. it increases the speed of file read and write operations.
    4. it stores file data in RAM, which is faster than storing it to a magnetic disc.
  2. Of the following, the "layer" that is not commonly used when writing a text file is ________.
    1. FileWriter
    2. BufferedWriter
    3. PrintWriter
    4. ProgramWriter
  3. When we want to write data to a file using print() and println(), we add the "layer" named ________.

13.2.5: Writing a Text File

  • Let us look at the steps for writing to a text file

Creating the Writer Stream

  • Our first step is to build the layers of the output stream
  • There are many ways to connect a character output stream to a file
  • We can "mix and match" Writer subclasses to get the functionality we need
  • Note that any class ending in the word "Writer" is a Writer subclass
  • This list of constructors should give you some idea how this is possible:
    FileWriter(String pathname)
    FileWriter(String pathname, boolean append)
    
    BufferedWriter(Writer out)
    
    PrintWriter(String fileName)
    PrintWriter(Writer out)
    
  • Usually we end up with layers like the following:
    PrintWriter out = new PrintWriter(
                      new BufferedWriter(
                      new FileWriter("scores.txt")));
    
  • The PrintWriter object lets us call the overloaded print() and println() methods for writing various types of data

Writing the Data

  • After we build the output stream, we write our data
  • We write using the print() and println() methods of PrintWriter, like:
    out.println("This is a string");
    out.println('c');
    out.println(1234);
    out.println(1.234);
    for (int i = 0; i <= 10; i++) {
        out.print(i + ",");
    }
    out.println();
    
  • Notice that print() and println() are the same methods we use for printing to a terminal window
    System.out.println("this is a string");

Closing the File

  • When finished writing, we call the close() method of out to flush the buffer and close the file:
    out.close();
    
  • If we forget to close the stream, we may lose the contents of the output buffer
  • The following is a step-by-step procedure for writing files followed by an example method

Step by Step Procedure for Writing Text Files

  1. Import the API libraries for file output.
    import java.io.*;
  2. Create the output stream.
    String fileName = "data.txt";
    PrintWriter out = new PrintWriter( // encode data as characters
                      new BufferedWriter( // buffer data for speed
                      new FileWriter(fileName))); // write bytes
    
  3. Write the data like printing to a terminal window.
    out.println("This is a string");
    out.println('c');
    out.println(1234);
    out.println(1.234);
    for (int i = 0; i <= 10; i++) {
        out.print(i + ",");
    }
    out.println();
    
  4. Close the file.
    out.close(); // flush buffer and free up resources
    

Example Method that Writes Data to a File

public void writeScores(String filename) throws IOException
{
    String[] scores = {"Ed      1000", "MstrChf  500"};
    PrintWriter out = new PrintWriter(
                      new BufferedWriter(
                      new FileWriter(filename)));
    for (String line: scores)
    {
        out.println(line);
    }
    out.close();
}

What is "throws IOException"?

  • The "throws IOException" clause is a way to handle errors (exceptions)
  • We will discuss exceptions later

Exercise: Writing a File (3m)

  1. If you have not done so already, download the following scenario file, save it to a convenient location like the Desktop, and unzip the file. To unzip the gfar, double-click it.

    Scenario file: fileio.gfar or fileio.zip.

  2. Double-click the project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
  3. Open the FileWorld class and verify the API libraries for file I/O are imported:
    import java.io.*;
    
  4. Add the following method to the FileWorld class:
    public void writeScores(String filename) throws IOException
    {
        String[] scores = {"Ed      1000", "MstrChf  500"};
        PrintWriter out = new PrintWriter(
                          new BufferedWriter(
                          new FileWriter(filename)));
        for (String line: scores)
        {
            out.println(line);
        }
        out.close();
    }
    

    Compile the class after copying in the code and verify there are no errors. Resolve any errors you find, getting help from a guild mate or the instructor as needed.

  5. Call the new method to verify it works by right-clicking on the background and selecting the method. Save a file with the name of "scores.txt".

    Remember to put double-quote marks (") around the name of the file.

  6. Verify the file was written to the main folder of your scenario and examine the contents of the file using a text editor like TextPad.

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

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

Check Yourself

  1. Of the following, ________ is NOT a usual step for writing to a file.
    1. check if the file exists
    2. create an output stream
    3. write the data
    4. close the file
  2. True or false: there is only one way to create an output stream for writing text to a file.
  3. True or false: if an output stream is not closed then it is possible to lose data.

13.2.6: Reading a Text File

  • Once we have a text file we can read it with our program
  • The steps for reading are similar to the steps for writing

Creating the Reader Stream

  • To read from a text file, we need to create a character input stream
  • To do this, we layer classes from the Reader hierarchy:
  • Note that any class ending in the word "Reader" is a Reader subclass
  • This list of constructors should give you some idea how this is possible:
    FileReader(String pathname)
    
    BufferedReader(Reader in) // extends Reader
    
    Scanner(Readable source) // Reader implements Readable
    
  • There is no "PrintReader" class, so we can use a Scanner object instead
  • Usually we end up with layers like the following:
    Scanner in = new Scanner(
                 new BufferedReader(
                 new FileReader("data.txt")));
    
  • The Scanner object lets us call various methods for converting text into other data types

Reading From a JAR File in Exported Scenarios

  • One way to deploy a scenario is as an application packaged in a JAR file (see lesson 4.5.2)
  • A JAR file is a ZIP-like file with some extra features
  • Greenfoot uses JAR files to package our scenario and sometimes we want to read text files included in our JAR file
  • However, a FileReader cannot connect to a file inside a JAR because of the compressed format
  • To load files from a JAR, we need to use a method named getClass()
  • Method getClass() gets the Class runtime class for the current object
  • The runtime class is the information Java keeps about each class that is running
  • We can extract information from Java about the runtime class using the class named Class
  • A Class object has two methods we can use to get resources:
  • We can use either of these methods to connect to a text file
  • So instead of creating layers like the above, we create layers with getResourceAsStream() like:
    InputStream istream = getClass().getResourceAsStream("data.txt");
    Scanner in = new Scanner(
                 new BufferedReader(
                 new InputStreamReader(istream)));
    
  • Since this technique is more generally useful, it is preferred for Greenfoot projects
  • Reading from a JAR file is the only file operation allowed on any web page, including the Greenfoot Gallery
  • Java runs on a web page in an applet, which is a small application container
  • We can automatically detect if our program is running in an applet by checking for a SecurityManager
    if (System.getSecurityManager() == null) {
        // perform file operations not allowed on a web page
    }
    
  • An applet contains a SecurityManager to reduce threats, like viruses, to the Java program
  • Thus if a SecurityManager is not present then we can write files

Reading the Data

  • After we build the input stream, we read our data
  • If our data is more than just lines of text, then we use a Scanner to convert the characters into other data types
  • Scanner is located in the java.util package, so we need to import the class using:
    import java.util.Scanner;
    
  • A Scanner reads tokens which are usually delimited by whitespace
  • A token is like a word in a sentence
  • Whitespace is a character which represents spaces between words, like space and tab characters
  • With our Scanner object we can use methods to read from the stream like:
    String str = in.nextLine();
    char ch = in.nextLine().charAt(0);
    int x = in.nextInt();
    double d = in.nextDouble();
    for (int i = 0; i <= 10; i++) {
        x = in.nextInt();
    }
    
  • Notice that we must know the type of data that is in the input stream to use these methods
  • Otherwise, the Scanner object will throw an exception

Some Commonly Used Methods of a Scanner Object for Tokenizing Data

Method Description
next() Returns the next token as a String object.
nextLine() Returns the rest of the current line as a String object.
nextDouble() Returns the next token as an double value.
nextInt() Returns the next token as an int value.

Closing the Stream

  • After our program finishes reading, it should call the close method:
    in.close();
    
  • Calling close() frees up computer system resources
  • The following is a step-by-step procedure for reading files followed by an example method

Step by Step Procedure for Reading Text Files

  1. Import the API libraries for file input.
    import java.io.*;
    import java.util.Scanner;
    
  2. Create the input stream.
    String fileName = "data.txt";
    Scanner in = new Scanner( // parse characters into data types
                 new BufferedReader( // buffer data for speed
                 new InputStreamReader( // read bytes as characters
                 getClass().getResourceAsStream(fileName)))); //JAR
    
  3. Read the data.
    String str = in.nextLine();
    char ch = in.nextLine().charAt(0);
    int x = in.nextInt();
    double d = in.nextDouble();
    for (int i = 0; i <= 10; i++) {
        x = in.nextInt();
    }
    
  4. Close the file.
    in.close(); // flush buffer and free up resources
    

Example Method Reading a Text File

public void readScores(String filename) throws IOException
{
    InputStream istr = getClass().getResourceAsStream(filename);
    Scanner in = new Scanner(
                 new BufferedReader(
                 new InputStreamReader(istr)));
    while (in.hasNext()) {
        String line = in.nextLine();
        System.out.println(line);
    }
    in.close();
}

Testing for End of File

  • Notice the while loop in the example method above
  • Sometimes we do not know how many lines of data are in a file
  • Using a loop like this is a typical way to solve the problem and let us read any amount of data
  • As an example, we could read a tile map for a game from a file
  • The Scanner object has methods like hasNext() to see if there is any more input in the stream
  • We can use these methods to test when our program reaches the end of a file

Some Test Methods of a Scanner Object

Method Description
hasNext() Returns true if this scanner has another token in its input.
hasNextLine() Returns true if there is another line in the input of this scanner.
hasNextDouble() Returns true if the next token can be interpreted as a double value.
hasNextInt() Returns true if the next token can be interpreted as an int value.

Check Yourself

  1. Of the following, ________ is not a usual step for reading from a file.
    1. scan the file for errors
    2. create an input stream
    3. read the data
    4. close the file
  2. True or false: if an input stream is not closed then it is possible to lose data.
  3. True or false: reading data from a JAR file is more difficult because the data is compressed.
  4. True or false: our program must always know the type of data being read from a file.
  5. To read from a file when we do not know how much data it contains, we can use a loop and test for the end of the file using the ________ method of the Scanner object.

Exercise 13.2: Writing and Reading Files (5m)

In this exercise, we write data to a file and then read the data back from a file.

Specifications

  1. If you have not already completed Exercise: Writing a File, do so now.
  2. Open the FileWorld class and verify the API libraries for file reading are imported:
    import java.io.*;
    import java.util.Scanner;
    
  3. Add the following method to the FileWorld class:
    public void readScores(String filename) throws IOException
    {
        InputStream istr = getClass().getResourceAsStream(filename);
        Scanner in = new Scanner(
                     new BufferedReader(
                     new InputStreamReader(istr)));
        while (in.hasNext()) {
            String line = in.nextLine();
            System.out.println(line);
        }
        in.close();
    }
    

    Compile the class after copying in the code and verify there are no errors. Resolve any errors you find, getting help from a guild mate or the instructor as needed.

  4. Call the new method to verify it works by right-clicking on the background and selecting the method.

    Remember to put double-quote marks (") around the name of the file.

  5. Verify the read operation works by verifying the Terminal window shows the contents of the file.

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

  6. Save your scenario files to submit to Canvas as part of lab 13.

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

13.2.7: Review

  • Oftentimes we want our programs to save data that still exists after ending the program
  • One example is to save the high scores in a game
  • When the game starts, our program reads existing data from a file
  • If the player reaches a new high score, our program can save the new high score
  • To read and write files, we use a stream
  • A stream connects a program to data, such as that stored in a file
  • Input stream: an object that provides a sequence of bytes to a program over time

    Input stream

  • Output stream: an object that accepts a sequence of bytes from a program and delivers the data over time

    Output stream

File Streams

  • File streams can be either input or output streams
  • File input streams receive data from a file
  • File output streams send data to a file
  • Each file our program uses will need a separate file stream object
  • We use character streams to read and write text files:

Writing a Text File

  • To write text to a file, we use a FileWriter stream:
    FileWriter fw = new FileWriter("out.txt");
    
  • However, a FileWriter does not know how to write different types of data
  • That's why we combine the FileWriter with a PrintWriter:
    PrintWriter out = new PrintWriter(fw);
    
  • Now we can use the overloaded print() methods of a PrintWriter object to write various data types
  • In addition, we often buffer our output to increase the speed of file I/O
  • We then add a buffering layer to our previous stream example:
    FileWriter fw = new FileWriter("myfile.txt");
    BufferedWriter buf = new BufferedWriter(fw);
    PrintWriter out = new PrintWriter(buf);
    
  • Another way to write the same functionality:
    PrintWriter out = new PrintWriter(
                      new BufferedWriter(
                      new FileWriter("myfile.txt")));
    
  • The PrintWriter object lets us call the overloaded print() and println() methods for writing various types of data

Reading a Text File

  • See 13.2.6 above

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

  1. How does a computer store information in secondary memory? (13.2.1)
  2. What is the technical name for the file system organizing technique that stores files and folders? (13.2.1)
    1. Files
    2. Folders
    3. Documents
    4. Records
  3. True or false: a file is just a container for data. (13.2.2)
  4. What standards are commonly used to define text file formats? (13.2.2)
  5. Which of the following is a commonly used text file format? (13.2.2)
    1. ASCII
    2. Textual
    3. Characterizer
    4. YOUTF-8
  6. What is the difference between a text file and a binary file? (13.2.2)
  7. What is an input stream? (13.2.3)
  8. What is an output stream? (13.2.3)
  9. True or false: streams can be defined as input, output or bi-directional (input and output). (13.2.3)
  10. To input or output text, use a ________________ stream. (13.2.3)
  11. What are three streams we typically layer for program output and what is the purpose of each layer? (13.2.4)
  12. How does buffering improve I/O performance? (13.2.4)
  13. What is the advantage of working with the top (PrintWriter) layer? (13.2.4)
  14. What are the three steps for writing files? (13.2.5)
  15. Write code to define an output stream for writing data to a file. (13.2.5)
  16. Why is it important to close the output stream after our program is finished writing to a file? (13.2.5)
  17. What are the three steps for reading files? (13.2.6)
  18. Write code to define an input stream for reading data from a file. (13.2.6)
  19. True or false: our program must always know the type of data being read from a file. (13.2.6)
  20. What technique can we use to read all the data from a file, if we do not know how much data is in the file? (13.2.6)

13.3: Worlds and Files

Learner Outcomes

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

  • Read text files with a loop
  • Read and display a map file in a tile world

13.3.1: Review of Files and Streams

  • Sometimes we want to save data after our scenario finishes
  • For instances, we could store a high score list of players
  • Sometimes we want to use stored data so it is easier to make changes to scenarios
  • For example, we can read information from files to create tile maps
  • Reading a tile map from a file would let us change maps without recompiling
  • In addition, multiple tile map files to allow us to more easily create multiple levels in a scenario

Types of Files

  • All data in a file is ultimately just zeros and ones (bits) grouped into 8-bit bytes
  • We classify files based on how we store data into two broad categories: text and binary
  • For text files, the bytes in the file represent printable characters
  • The byte for a character is defined by a standard such as ASCII (ASCII table) or UTF-8
  • Programs like text editors read text files and display the bytes as textual characters
  • Every line of text is delimited by end-of-line characters:
    • Linux/Unix/OS-X: "\n"
    • Windows: "\r\n"
  • Data other than text is usually referred to as binary data
  • Each bit represents some type of encoded information like program instructions or integer data
  • Binary files are easily read by the computer but not by humans

Streams

  • To read and write files, we use a stream
  • A file stream is used to connect a file to a program
  • Input stream: accepts bytes from a source and delivers them to a program

    Input stream

  • Output stream: accepts bytes from a program and delivers the data to a destination

    Output stream

  • We use character streams to read and write text files:

Layering Streams

  • Usually, any one stream does have not all the functionality we want
  • Instead, it is common to "layer" two or more streams
  • We add buffers to our streams to make reading and writing faster

Buffer

  • When writing, we add a stream to convert from binary to character data like PrintWriter
  • When reading, we add an object to convert the character data into other types like int, double and String using a Scanner
  • The following instructions include the layers normally used to write and read text files

Step by Step Procedure for Writing Text Files

  1. Import the API libraries for file output.
    import java.io.*;
  2. Create the layered output stream.
    String fileName = "data.txt";
    PrintWriter out = new PrintWriter( // encode data as characters
                      new BufferedWriter( // buffer data for speed
                      new FileWriter(fileName))); // write bytes
    
  3. Write the data like printing to a terminal window.
    out.println("This is a string");
    out.println('c');
    out.println(1234);
    out.println(1.234);
    for (int i = 0; i <= 10; i++) {
        out.print(i + ",");
    }
    out.println();
    
  4. Close the file.
    out.close(); // flush buffer and free up resources
    

Step by Step Procedure for Reading Text Files

  1. Import the API libraries for file input.
    import java.io.*;
    import java.util.Scanner;
    
  2. Create the input stream.
    String fileName = "data.txt";
    Scanner in = new Scanner( // parse characters into data types
                 new BufferedReader( // buffer data for speed
                 new InputStreamReader( // read bytes as characters
                 getClass().getResourceAsStream(fileName)))); //JAR
    
  3. Read the data.
    String str = in.nextLine();
    char ch = in.nextLine().charAt(0);
    int x = in.nextInt();
    double d = in.nextDouble();
    while (in.hasNextInt()) {
        x = in.nextInt();
    }
    
  4. Close the file.
    in.close();  // flush buffer and free up resources
    

Check Yourself

  1. To store information, the computer saves the data in a __________.
  2. True or false: data in a file is restricted to characters only.
  3. When we want to write binary data to a file as character, we add the "layer" named ________.
  4. When we want to read character data and convert it to data types like int, double and String , we add a(n) ________.

13.3.2: Using Loops to Read a File

  • Sometimes we do not know how many data items are in a file
  • To solve this, the typical approach is to use a loop to read the file data
  • Within the loop we check whether or not more data is available from the input stream
  • The Scanner object has methods like hasNext() to see if there is any more input in the stream
  • These methods signal our program when it reaches the end of a file
  • The following table shows some methods of the Scanner class for detecting if more data is available
  • After the table is an example of a method that read an indefinite number of lines from a file

Some Test Methods of a Scanner Object

Method Description
hasNext() Returns true if this scanner has another token in its input.
hasNextLine() Returns true if there is another line in the input of this scanner.
hasNextDouble() Returns true if the next token can be interpreted as a double value.
hasNextInt() Returns true if the next token can be interpreted as an int value.

Method to Read a File with a Loop

public void readMap(String fileName)
{
    Scanner in = new Scanner(
                 new BufferedReader(
                 new InputStreamReader(
                 getClass().getResourceAsStream(fileName))));
    while (in.hasNext())
    {
        String line = in.nextLine();
        System.out.println(line);
    }
    in.close();
}

Exercise: Read a File with a Loop (4m)

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

    Scenario file: platformer6.gfar

    Double click to unzip and lauch the gfar. If using an unzipped version, double-click the project.greenfoot file in the extracted folder

  2. Open the editor for the GameManager class and import the API libraries for file input by adding the following at the top of the file.
    import java.io.*;
    import java.util.Scanner;
    
  3. Next in the GameManager class, add the readMap() method below, and then compile the file.
    public void readMap(String fileName)
    {
        Scanner in = new Scanner(
                     new BufferedReader(
                     new InputStreamReader(
                     getClass().getResourceAsStream(fileName))));
        while (in.hasNext())
        {
            String line = in.nextLine();
            System.out.println(line);
        }
        in.close();
    }
    
  4. To test the method, right-click on the scenario background and select the readMap() method, entering an argument of: "maps/map1.txt" (with the quotes).

    Remember to include the double-quote marks (") because the entry is a string. You should see the map file in the terminal window. If you have problems, ask a guild mate or the instructor for help as needed.

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

Check Yourself

  1. When the number of file data items is unknown use a ________ statement to read all the data.
  2. Of the following, the answer that is NOT a method of the Scanner class for testing the input stream is ________.
    1. hasNext()
    2. hasNextLine()
    3. hasNextInt()
    4. hasNoInput()
  3. When typing the file name, double-quotes are required to indicate a data type of ________.

13.3.3: Loading Files into an ArrayList

  • We can read data from a file but we still need to store the data into the map array
  • We could load each line of the file directly into an array like:
    int i = 0;
    while (in.hasNext()) {
        String line = in.nextLine();
        map[i] = line;
        i++;
    }
    
  • However, an array must be set to the correct size before we read from the file
  • Unless we know the number of lines in the map ahead of time, we need a list type that can grow as needed
  • One list type that can grow as needed is an ArrayList

Array Lists

  • An ArrayList is a class that organizes data into a List
  • We discussed the List type in lesson 8.1.4: Interacting with Lists of Actors
  • The ArrayList class is part of the standard library classes of Java
  • To make use of an ArrayList, we must import the Java class library
    import java.util.ArrayList;
  • The general syntax for declaring an ArrayList is:
    ArrayList<elementType> objectName;
    
  • Where:
    • elementType: the type of data stored in the list
    • objectName: the name you make up for the ArrayList
  • For example, to store a list of String objects we declare:
    ArrayList<String> list;
    
  • After declaring an ArrayList, we construct one using the new operator
  • For example, to declare and construct an ArrayList to store strings:
    ArrayList<String> list = new ArrayList<String>();
    
  • The above declaration creates an ArrayList with zero elements
  • To add items to the list, we call the method add():
    list.add(element);
    
    Where element is a variable or literal of the correct type.
  • Each time we call the add() method, the ArrayList adds a new element to the end of the list

Some Commonly Used Constructors and Methods of the ArrayList<type> Class

Constructor/Method Description
ArrayList() Creates an empty list.
add(object) Adds the specified object to the end of the list.
get(index) Returns the object at the specified index in the list.
set(index, element) Returns the number of elements in the list.
size() Replaces the element at index with the new element.
toArray(array) Returns an array containing all of the elements in proper sequence.

Converting an ArrayList to an Array

  • Notice that the last method above returns an array from the ArrayList
  • After we have loaded all the lines from the file into an ArrayList, we call the toArray() method like:
    map = list.toArray(new String[0]);
    
  • By using an ArrayList our program can read any number of lines from a file
  • After reading the lines, we then convert the lines to an array
  • We will update readMap() to complete this task in the next exercise

Updated Method readMap()

public void readMap(String fileName)
{
    removeObjects(getObjects(null)); // remove all actors
    ArrayList<String> list = new ArrayList();  // construct ArrayList
    Scanner in = new Scanner(
            new BufferedReader(
                new InputStreamReader(
                    getClass().getResourceAsStream(fileName))));
    while (in.hasNext()) { // while not at end of file
        String line = in.nextLine();
        list.add(line); // add lines to list
        //System.out.println(line);
    }
    in.close();
    map = list.toArray(new String[0]); // convert to array
    createPlatforms(map); // add platforms from map
}

Check Yourself

  1. Of the following statements, the one that constructs a new ArrayList that stores String data is ________.
    1. ArrayList<GreenfootImage> images = new ArrayList<GreenfootImage>();
    2. ArrayList<String> strList = new ArrayList();
    3. ArrayList<String> strList = new ArrayList<String>;
    4. ArrayList<String> strList = new ArrayList<String>();
  2. To add an item to an ArrayList, call the method ________.
  3. To convert an ArrayList to an array, call the method ________.

Exercise 13.3: Reading a Map File (5m)

In this exercise, we load map data from a file and display the tile map in a scenario using a file chooser.

Specifications

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

    If you did not keep the scenario, then complete Exercise: Read a File with a Loop now and then continue with these instructions.

  2. Open the editor for the GameManager class and import the API library for the ArrayList at the top of the file with the other import statements.
    import java.util.ArrayList;
    
  3. Next add the following instance variable inside the GameManager class:
    private String[] map;
    
  4. In the GameManager class, replace the readMap() method with the one shown below.
    public void readMap(String fileName)
    {
        removeObjects(getObjects(null)); // remove all actors
        ArrayList<String> list = new ArrayList();  // construct ArrayList
        Scanner in = new Scanner(
                new BufferedReader(
                    new InputStreamReader(
                        getClass().getResourceAsStream(fileName))));
        while (in.hasNext()) { // while not at end of file
            String line = in.nextLine();
            list.add(line); // add lines to list
            //System.out.println(line);
        }
        in.close();
        map = list.toArray(new String[0]); // convert to array
        createPlatforms(map); // add platforms from map
        addObject(george, getWidth() / 2, 0); // add player
    }
    
  5. In the constructor of GameManager, replace the method call to createPlatforms(map) with a call to readMap().
    public GameManager()
    {
        super(800, 600, 1, false);
        leftX = TILE_WIDTH / 2 - TILE_WIDTH;
        topY = TILE_HEIGHT - getHeight() % TILE_HEIGHT;
        // createPlatforms(map);  // comment or remove
        readMap("maps/map1.txt"); // call instead of createPlatforms()
        addObject(new Sign(), TILE_WIDTH * 33, getHeight() - TILE_HEIGHT * 2);
    }
    
  6. Replace all the uses of the MAP variable with map. Note that the code will not compile until you complete step 7.

    Hint: use the search and replace function in the Tools menu: Tools : Replace.... Be sure to check the Match Case box.

  7. Now remove the entire String[] MAP variable from the scenario by commenting or deleting:
    // private static final String[] map =
        // {
            // "BBBBBBBBBBBB                       BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB                       BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB                       BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB    LMMMMR    LMR      BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB       P       P      LBBBBBBBBBBBBB",
            // "BBBBBBBBBBBB       P       P       BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB       P     LMMMR     BBBBBBBBBBBBB",
            // "BBBBBBBBBBBBMMR    P       P       BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB       PMR     P     LMBBBBBBBBBBBBB",
            // "BBBBBBBBBBBB       P       P       BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB       P       P       BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB     LMMMMMMMMMMMR     BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB           P           BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB           P           BBBBBBBBBBBBB",
            // "BBBBBBBBBBBBMMMR       P        LMMBBBBBBBBBBBBB",
            // "BBBBBBBBBBBB           P           BBBBBBBBBBBBB",
            // "BBBBBBBBBBBB           P                        ",
            // "BBBBBBBBBBBB           P                        ",
            // "MMMMMMMMMMMMMMMMMMMMMMMMMMR   LMMMMMMMMMMMMMMMMM"
        // };
    
  8. Compile the GameManager class to verify you implemented the variable changes correctly.
  9. After compiling, run your updated scenario and verify the map is still displayed.

    Move a tile and an NPC to verify there are no duplicate's behind them. If so, you probably need to remove the call to createPlatforms() in the constructor (step 5). Ask a guild mate or the instructor for help as needed.

  10. We can now load other maps by changing the following line in the constructor:
    readMap("maps/map1.txt");
    
    to something like:
    readMap("maps/map2.txt");
    
  11. Save your updated scenario as we will be adding to it as the lesson continues and eventually turning it in as part of lab 14.

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

Image of portal
portal.gif

13.3.4: Adding a Portal

  • With multiple worlds available, we need some mechanism to change between them
  • One commonly used technique is to add a portal to a game
  • A portal is a doorway or other entrance to another place, in this case another world
  • To add a portal we create a new Actor subclass named Portal

Exercise: Add a Portal (5m)

  1. Start Greenfoot and open the scenario from the last exercise: Exercise 13.3: Reading a Map File.
  2. Create a new Actor subclass named Portal with "portal.gif" as the image.
  3. Open the editor for the Portal class and add the following instance variable inside the Portal class:
    private String world;
    
  4. Add a constructor to the Portal class with a single String parameter. Assign the parameter to the world instance variable.
    public Portal(String map)
    {
        world = map;
    }
    
  5. Update the act() method to call the readMap() method as follows.
    public void act()
    {
        if (isTouching(Player.class))
        {
            GameManager w = (GameManager)getWorld();
            w.readMap(world);
        }
    }
    
  6. Test the Portal class by adding a new Portal object to the world using "maps/map2.txt" (with the quotes) as an argument to the constructor and then running the scenario.

    Remember to include the double-quote marks (") because the entry is a string. When the player touches the portal, a new map should appear. If you have problems, ask a guild mate or the instructor for help as needed.

  7. Save your updated scenario as we will be adding to it as the lesson continues.
  8. When finished, please help those around you.

13.3.5: Adding a Portal to the Map

  • With portals available we want to add them to our world map
  • However, each portal needs different arguments to specify which world to visit
  • One way to accomplish this goal is to have a different symbol for each world portal
  • For example:
    • 1: "maps/map1.txt"
    • 2: "maps/map2.txt"
  • To implement these symbols we update our makeMapRow() method in GameManager like:
    else if (tileType == '1')
    {
        Portal port = new Portal("maps/map1.txt");
        GreenfootImage img = port.getImage(); // adjust y-position
        int adjustY = TILE_HEIGHT / 2 - img.getHeight() / 2;
        addObject(port, tileX, tileY + adjustY);
    }
    

Exercise: Add Portals to the Map (5m)

  1. Start Greenfoot and open the scenario from the last exercise: Exercise: Add a Portal.
  2. Open the GameManager, locate the makeMapRow() method and add the following to makeMapRow() before the else if (tileType != ' ') option.
    else if (tileType == '1')
    {
        Portal port = new Portal("maps/map1.txt");
        GreenfootImage img = port.getImage(); // adjust y-position
        int adjustY = TILE_HEIGHT / 2 - img.getHeight() / 2;
        addObject(port, tileX, tileY + adjustY);
    }
    
  3. Similarly, add another entry to makeMapRow() to recognize a '2' as a portal for "maps/map2.txt".
    else if (tileType == '2')
    {
        Portal port = new Portal("maps/map2.txt");
        GreenfootImage img = port.getImage(); // adjust y-position
        int adjustY = TILE_HEIGHT / 2 - img.getHeight() / 2;
        addObject(port, tileX, tileY + adjustY);
    }
    
  4. Open the map1.txt and map2.txt files in a text editor and add portal entries. For example, map1.txt may look like:
    BBBBBBBBBBBB                       BBBBBBBBBBBBB
    BBBBBBBBBBBB                     F BBBBBBBBBBBBB
    BBBBBBBBBBBB                       BBBBBBBBBBBBB
    BBBBBBBBBBBB    LMMMMR    LMR      BBBBBBBBBBBBB
    BBBBBBBBBBBB       P       P      LBBBBBBBBBBBBB
    BBBBBBBBBBBB       P       P       BBBBBBBBBBBBB
    BBBBBBBBBBBB       P     LMMMR     BBBBBBBBBBBBB
    BBBBBBBBBBBBMMR    P       P       BBBBBBBBBBBBB
    BBBBBBBBBBBB       PMR     P     LMBBBBBBBBBBBBB
    BBBBBBBBBBBB     F P       P     F BBBBBBBBBBBBB
    BBBBBBBBBBBB       P     G P       BBBBBBBBBBBBB
    BBBBBBBBBBBB     LMMMMMMMMMMMR     BBBBBBBBBBBBB
    BBBBBBBBBBBB           P           BBBBBBBBBBBBB
    BBBBBBBBBBBB           P           BBBBBBBBBBBBB
    BBBBBBBBBBBBMMMR       P        LMMBBBBBBBBBBBBB
    BBBBBBBBBBBB           P           BBBBBBBBBBBBB
    BBBBBBBBBBBB           P
    BBBBBBBBBBBB         G P                     2
    MMMMMMMMMMMMMMMMMMMMMMMMMMR   LMMMMMMMMMMMMMMMMM
    
  5. Test the Portal class by by running the scenario, finding a portal, and touching the portal with the player.

    When the player touches the portal, a new map should appear. If you have problems, ask a guild mate or the instructor for help as needed.

  6. Save your updated scenario as we will be adding to it as the lesson continues.
  7. When finished, please help those around you.

13.3.6: Changing Worlds

  • Unfortunately, we can no longer read files on the Greenfoot site
  • However, we can work around this problem by creating mutiple worlds with different maps
  • Greenfoot allows a scenario to have multiple world subclasses
  • After creating multiple worlds, we may switch between them using code like:
    World world = new MyWorld();
    Greenfoot.setWorld(world);
    
  • The setWorld() method is part of the Greenfoot class

    public static void setWorld(World world)

Creating Maps

  • One way to create new world maps is as a subclass of GameManager

    map as world subclasses

  • The subclasses contain a map array and a call to createPlatforms()
  • Then in the GameManager constructor we remove the calls to creating or reading maps
    //createPlatforms(map);
    //readMap("maps/map1.txt");
    
  • In addition, we make certain theat the method createPlatforms() is public or protected instead of private

Example Map Classes

Updating Portal

  • The next step is to change the Portal class act() method like:
    public void act()
    {
        if (isTouching(Player.class))
        {
            if (world.contains("map1"))
            {
                Greenfoot.setWorld(new Map1());
            }
            else if (world.contains("map2"))
            {
                Greenfoot.setWorld(new Map2());
            }
        }
    }
    
  • Finally, right-click the Map1 class and select new Map1().
  • This last step sets the starting world of a Greenfoot scenario

Exercise 13.3b: Changing Worlds (3m)

  1. Start with your existing platform code from these exercises
    1. Exercise: Add a Portal
    2. Exercise: Add Portals to the Map

    If you had problems then download the following scenario file, save it to a convenient location like the Desktop, and double-click to open the gfar file.

    Scenario file: platformer7.gfar

  2. In the GameManager, verify the createPlatforms() method has public access:
    public void createPlatforms(String[] map)
    
  3. Maps as world subclasses

    Create two new World subclasses with the following code, setting "sky.jpg" as the image.
  4. Update the GameManager constructor to remove any call to create or read maps
    //createPlatforms(map);
    //readMap("maps/map1.txt");
    
  5. Change the Portal class act() method as follows:
    public void act()
    {
        if (isTouching(Player.class))
        {
            if (world.contains("map1"))
            {
                Greenfoot.setWorld(new Map1());
            }
            else if (world.contains("map2"))
            {
                Greenfoot.setWorld(new Map2());
            }
        }
    }
    
  6. Right-click the Map1 class and select new Map1().

13.3.7: Review

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

  1. What is the difference between a text file and a binary file? (13.3.1)
  2. What is an input stream? (13.3.1)
  3. What is an output stream? (13.3.1)
  4. What are three streams we typically layer for program output? (13.3.1)
  5. What are the steps for writing files? (13.3.1)
  6. What are the steps for reading files? (13.3.1)
  7. What technique can we use to read all the data from a file, if we do not know how much data is in the file? (13.3.2)
  8. Which of the following statements constructs a new ArrayList that stores String data? (13.3.3)
    1. ArrayList<GreenfootImage> images = new ArrayList<GreenfootImage>();
    2. ArrayList<String> strList = new ArrayList();
    3. ArrayList<String> strList = new ArrayList<String>;
    4. ArrayList<String> strList = new ArrayList<String>();
  9. Which of the following statements adds a new string to the end of the list? (13.3.3)
    1. add("foo");
    2. strList.add("foo");
    3. strList.addToEnd("foo");
    4. strList.addString("foo");
  10. A portal is a doorway to another ________. (13.3.4)
  11. We must use a different symbol for each portal because the constructor ________ is different. (13.3.5)
  12. The Greenfoot method to change worlds is ________. (13.3.6)

Wrap Up

Due Next:
Lab 13: Prototype Test (11/26/19)
Q13: User-Testable Prototype (11/26/19)
  • When class is over, please shut down your computer
  • You may complete unfinished lesson exercises at any time before the due date.
Last Updated: November 25 2019 @18:14:34