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
^ top
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
|
^ top
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:
- Decide on the purpose of the NPC in the game
- Decide on the actions of the NPC and how it will interact with the player
- Make and load the animations
- Create a subclass for the NPC
- Code the actions of the NPC into its class
- We will develop an example NPC in the following sections
Adding NPCs to a Scenario
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)
- Download the following scenario file, save it to a convenient location like the Desktop and unzip the file.
Scenario file: platformer4.gfar
- Double-click the
project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
- 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.
- Add the following method to the
GameManager class.
public Player getPlayer()
{
return george;
}
- 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.
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- Of the following, ________ is an NPC.
- a player character
- a sprite controlled by the game player
- a sprite controlled by the computer
- a background image like a sign
- True or false: sometimes an actor needs to call methods of another actor.
- True or false: one convenient way to access an actor is to keep track of the actor in the scenario world.
- True or false:
getWorld() returns the current subclass of world as a World object.
- The following code causes a compiler error:
GameManager world = getWorld();
Enter the code to correct the problem.
^ top
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
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)
- 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.
- Open the editor for the
Sprite class and add the state variable.
private int state;
- In addition, add the following two methods to the
Sprite class:
public void setState(int newState)
{
state = newState;
}
public int getState()
{
return state;
}
- Compile the scenario to verify you added the code correctly.
- 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;
- 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
}
- Finally, update the
act() method as follows:
public void act()
{
if (getState() == STATE_DYING)
{
updateDyingState();
}
else
{
checkKeys();
checkHorizontal();
checkVertical();
}
move();
}
- Compile the scenario to verify you implemented the changes correctly.
- Save your updated scenario as we will be adding to it as the lesson continues.
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- What is meant by state? Give an example. (an answer)
- The two methods used to keep track of a sprite's state are ________ and ________.
- What are examples of states that may be appropriate for a sprite in a game?
- True or false: we add method stubs to our code as a placeholder until we complete the method body.
^ top
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

- 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)
- 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.
- Open the
Player class and verify the GRAVITY variable has public access.
public static final double GRAVITY = 0.7;
- 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.
- Close and restart Greenfoot and then open and inspect the new
Creature class.
- Compile the Creature class to verify the installation.
In you have problems compiling, ask a guild mate or the instructor for help as needed.
- Save your updated scenario as we will be adding to it as the lesson continues.
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- The two public methods of the
Creature class that support dying are ________ and ________.
- By default, an NPC will move back and forth between two endpoints. To set the endpoints, call the method ________.
- The method called in
act() to check for a collision with the player is named ________.
- To change the default behavior of an NPC, override the ________ method.
^ top
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
|
Dead
 |
|
Setting Up the Animation
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)
- 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.
- Create a new subclass of
Creature named Gorilla with "Gorilla1.png" as the image.
- 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;
- Delete the
act() method as the Gorilla uses the act() method in Creature .
- 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();
}
}
- 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);
}
- Compile the scenario to verify you added the code correctly.
- 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.
- 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.
- 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.
- 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"
};
- Compile and run the scenario and verify the gorillas appear and are walking correctly.
- 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());
}
}
}
- Compile and run the scenario and verify the gorillas are walking correctly even while scrolling.
- Save your updated scenario as we will be adding to it as the lesson continues.
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- True or false: the purpose of having a
Creature superclass for Gorilla is to make it easier to add similar NPCs to the scenario.
- Why is
Creature a subclass of Sprite rather than Actor ? (answer)
- 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.
- What is the order of files loaded by the above
initializeImages() code? (answer)
- To add falling and landing capabilities to an NPC, what methods would we need to place in
Creature or Sprite ?
All from Player :
applyGravity()
checkVertical()
moveToContactVertical()
^ top
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
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)
- 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.
- 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;
- 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);
}
- Verify the player's
act() looks like the following code:
public void act()
{
if (getState() == STATE_DYING)
{
updateDyingState();
}
else
{
checkKeys();
checkCollisionHorizontal();
checkVertical();
move();
}
}
- 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]);
}
}
- Compile the scenario to verify you added the code correctly.
- Test the dying updates by running into a gorilla.
When the player runs into a gorilla, the player should fade away.
- Test that the player can kill the gorilla by jumping on it.
- Save your updated scenario as we will be adding to it as the lesson continues.
- When finished please help those around you.
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- Why does our player have to (potentially) die?
- The method of
Creature that checks for collisions with the player is ________.
- What condition must be true for the player to win a collision? (answer)
- Of the following steps, "________" does NOT occur when the player wins a collision with a creature.
- the player backs off in the up direction
- the player bounces off the top of the creature
- the creature starts dying
- the player automatically adds one to his "kill" count
- What happens when the creature wins a collision with the player? (answer)
^ top
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.
|
Dead
 |
|
Specifications
- Verify you have completed the Exercise activities we explored throughout the section including:
- Exercise: Getting the Player
- Exercise: Adding State
- Exercise: Adding Creature Features
- Exercise: Adding a Gorilla NPC
- 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.
Scenario file: platformer5.gfar
This scenario includes all the updates from prior sections.
- Create a new subclass of
Creature named Fly using the "fly1.png" image.
For more information see lesson: 2.1.5: Creating an Actor.
- 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;
- 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).
- 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.
- In
Fly , remove the act() method so that the Creature superclass act() method will operate instead.
- 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.
- 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"
};
- 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;
- 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++;
}
- 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.
- Save your scenario files to submit to Canvas as part of lab 13.
- 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.
^ top
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
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

- 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.
- Which of the following is a non-player character? (13.1.1)
- A player character
- A sprite controlled by the game player
- A sprite controlled by the computer
- A background image like a sign
- True or false: To remember the state of a player we need to stoe a value in a variable. (13.1.2)
- What is the purpose of having a
Creature superclass for Gorilla ? (13.1.3)
- Which of the following superclass methods can be called from Gorilla? (13.1.3)
- The methods from
Creature only.
- The methods of
Creature and Player .
- The methods of
Actor , Creature and Sprite.
- All of the methods of all classes can be called directly.
- True or false: The images of an animation should all be the same size to make animating the images easier. (13.1.3)
- To change the behavior of an NPC, override the __________ method of
Creature . (13.1.3)
- 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)
- Which of the following steps does NOT occur when the player wins a collision with a creature? (13.1.5)
- The player backs off in the up direction
- The player bounces off the top of the creature
- The creature starts dying
- The player automatically adds one to his "kill" count
- What is the purpose of the following code in the
processCollision() method? (13.1.5)
player.setVelocityY(-player.getVelocityY());
^ top
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
|
^ top
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
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
- To store information, the computer saves the data in a __________.
- True or false: folders can contain files or other folders.
- The building block that creates the hierarchy of a file system is a __________.
- Document
- File
- Folder
- Record
- What use can you think of for reading and writing files in a Greenfoot scenario?
More Information
^ top
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

- 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?

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)
- 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.
- Double-click the
project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
- Compile the scenario.
- Start a text editor like TextPad.
- Open the FileWorld.java source code file in the text editor.
Right-click and select Send to TextPad.
- Open the FileWorld.class compiled file in the text editor
Right-click and select Send to TextPad.
- Notice the difference between the two files.
Which file is a binary file and which is a text file?
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- Program I/O is the term used for program I________ and O________.
- True or false: files can be used as both the input and output of a program.
- Of the following, the advantage of using files for program I/O is ________ .
- data still exists after the program ends
- input can be automated (rather than entered manually)
- output from one program can be input to another
- all of these
- A file resembles a(n) ________ of bytes.
- True or false: data in a file is restricted to characters only.
- Since text files can be displayed in any text editor, text files are known as ________ readable files.
- Files that store non-text data are known as ________ files.
- Of the following, the binary file is ________.
- Bug.class
- Bug.java
- BugWorld.java
- README.TXT
^ top
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

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

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
- A ________ is an ordered list of data delivered over time.
- True or false: streams can be defined as input, output or bi-directional (input and output).
- The superclass for streams that read text is ________.
- The superclass for streams that write text is ________.
^ top
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
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

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
- Buffering improves input and output performance because ________.
- it reduces the number of file read and write operations.
- it increases the number of file read and write operations.
- it increases the speed of file read and write operations.
- it stores file data in RAM, which is faster than storing it to a magnetic disc.
- Of the following, the "layer" that is not commonly used when writing a text file is ________.
FileWriter
BufferedWriter
PrintWriter
ProgramWriter
- When we want to write data to a file using
print() and println() , we add the "layer" named ________.
^ top
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
- Import the API libraries for file output.
import java.io.*;
- 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
- 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();
- 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)
- 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.
- Double-click the
project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
- Open the
FileWorld class and verify the API libraries for file I/O are imported:
import java.io.*;
- 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.
- 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.
- 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.
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- Of the following, ________ is NOT a usual step for writing to a file.
- check if the file exists
- create an output stream
- write the data
- close the file
- True or false: there is only one way to create an output stream for writing text to a file.
- True or false: if an output stream is not closed then it is possible to lose data.
^ top
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
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
- Import the API libraries for file input.
import java.io.*;
import java.util.Scanner;
- 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
- 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();
}
- 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
- Of the following, ________ is not a usual step for reading from a file.
- scan the file for errors
- create an input stream
- read the data
- close the file
- True or false: if an input stream is not closed then it is possible to lose data.
- True or false: reading data from a JAR file is more difficult because the data is compressed.
- True or false: our program must always know the type of data being read from a file.
- 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.
^ top
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
- If you have not already completed Exercise: Writing a File, do so now.
- Open the
FileWorld class and verify the API libraries for file reading are imported:
import java.io.*;
import java.util.Scanner;
- 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.
- 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.
- 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.
- 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.
^ top
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

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

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
Answer these questions to check your understanding. You can find more information by following the links after the question.
- How does a computer store information in secondary memory? (13.2.1)
- What is the technical name for the file system organizing technique that stores files and folders? (13.2.1)
- Files
- Folders
- Documents
- Records
- True or false: a file is just a container for data. (13.2.2)
- What standards are commonly used to define text file formats? (13.2.2)
- Which of the following is a commonly used text file format? (13.2.2)
- ASCII
- Textual
- Characterizer
- YOUTF-8
- What is the difference between a text file and a binary file? (13.2.2)
- What is an input stream? (13.2.3)
- What is an output stream? (13.2.3)
- True or false: streams can be defined as input, output or bi-directional (input and output). (13.2.3)
- To input or output text, use a ________________ stream. (13.2.3)
- What are three streams we typically layer for program output and what is the purpose of each layer? (13.2.4)
- How does buffering improve I/O performance? (13.2.4)
- What is the advantage of working with the top (
PrintWriter ) layer? (13.2.4)
- What are the three steps for writing files? (13.2.5)
- Write code to define an output stream for writing data to a file. (13.2.5)
- Why is it important to close the output stream after our program is finished writing to a file? (13.2.5)
- What are the three steps for reading files? (13.2.6)
- Write code to define an input stream for reading data from a file. (13.2.6)
- True or false: our program must always know the type of data being read from a file. (13.2.6)
- 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)
^ top
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
|
^ top
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

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

- 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

- 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
- Import the API libraries for file output.
import java.io.*;
- 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
- 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();
- Close the file.
out.close(); // flush buffer and free up resources
Step by Step Procedure for Reading Text Files
- Import the API libraries for file input.
import java.io.*;
import java.util.Scanner;
- 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
- 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();
}
- Close the file.
in.close(); // flush buffer and free up resources
Check Yourself
- To store information, the computer saves the data in a __________.
- True or false: data in a file is restricted to characters only.
- When we want to write binary data to a file as character, we add the "layer" named ________.
- When we want to read character data and convert it to data types like
int , double and String , we add a(n) ________.
^ top
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)
- 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
- 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;
- 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();
}
- 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.
- Save your updated scenario as we will be adding to it as the lesson continues.
- When finished, please help those around you.
- Be prepared to answer the following Check Yourself questions when called upon.
Check Yourself
- When the number of file data items is unknown use a ________ statement to read all the data.
- Of the following, the answer that is NOT a method of the Scanner class for testing the input stream is ________.
hasNext()
hasNextLine()
hasNextInt()
hasNoInput()
- When typing the file name, double-quotes are required to indicate a data type of ________.
^ top
13.3.3: Loading Files into 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
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
- Of the following statements, the one that constructs a new
ArrayList that stores String data is ________.
ArrayList<GreenfootImage> images = new ArrayList<GreenfootImage>();
ArrayList<String> strList = new ArrayList();
ArrayList<String> strList = new ArrayList<String>;
ArrayList<String> strList = new ArrayList<String>();
- To add an item to an
ArrayList , call the method ________.
- To convert an
ArrayList to an array, call the method ________.
^ top
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
- 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.
- 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;
- Next add the following instance variable inside the
GameManager class:
private String[] map;
- 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
}
- 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);
}
- 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.
- 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"
// };
- Compile the
GameManager class to verify you implemented the variable changes correctly.
- 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.
- 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");
- 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.
^ top
 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)
- Start Greenfoot and open the scenario from the last exercise: Exercise 13.3: Reading a Map File.
- Create a new
Actor subclass named Portal with "portal.gif" as the image.
- Open the editor for the
Portal class and add the following instance variable inside the Portal class:
private String world;
- 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;
}
- 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);
}
}
- 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.
- Save your updated scenario as we will be adding to it as the lesson continues.
- When finished, please help those around you.
^ top
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)
- Start Greenfoot and open the scenario from the last exercise: Exercise: Add a Portal.
- 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);
}
- 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);
}
- 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
- 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.
- Save your updated scenario as we will be adding to it as the lesson continues.
- When finished, please help those around you.
^ top
13.3.6: Changing Worlds
Creating Maps
Example Map Classes
Updating Portal
^ top
Exercise 13.3b: Changing Worlds (3m)
- Start with your existing platform code from these exercises
- Exercise: Add a Portal
- 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
- In the
GameManager , verify the createPlatforms() method has public access:
public void createPlatforms(String[] map)

Create two new World subclasses with the following code, setting "sky.jpg" as the image.
- Update the
GameManager constructor to remove any call to create or read maps
//createPlatforms(map);
//readMap("maps/map1.txt");
- 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());
}
}
}
- Right-click the
Map1 class and select new Map1() .
^ top
13.3.7: Review
Answer these questions to check your understanding. You can find more information by following the links after the question.
- What is the difference between a text file and a binary file? (13.3.1)
- What is an input stream? (13.3.1)
- What is an output stream? (13.3.1)
- What are three streams we typically layer for program output? (13.3.1)
- What are the steps for writing files? (13.3.1)
- What are the steps for reading files? (13.3.1)
- 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)
- Which of the following statements constructs a new
ArrayList that stores String data? (13.3.3)
ArrayList<GreenfootImage> images = new ArrayList<GreenfootImage>();
ArrayList<String> strList = new ArrayList();
ArrayList<String> strList = new ArrayList<String>;
ArrayList<String> strList = new ArrayList<String>();
- Which of the following statements adds a new string to the end of the list? (13.3.3)
add("foo");
strList.add("foo");
strList.addToEnd("foo");
strList.addString("foo");
- A portal is a doorway to another ________. (13.3.4)
- We must use a different symbol for each portal because the constructor ________ is different. (13.3.5)
- The
Greenfoot method to change worlds is ________. (13.3.6)
^ top
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.
^ top
Last Updated: November 25 2019 @18:14:34
|