6. Scoring and Boss Practice

What We Will Cover


Illuminations

Questions from last class or the Reading?

  • Chance for Challenges Boss 4 and makeups after class Tuesday.
  • Guilds published in Canvas, People > Project Goups
  • For those who missed guild sign-up day, I assigned them to a guild

Event Questions?

6.1: Moving the Player

Learner Outcomes

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

  • Add animation to the player character
  • Make use of logical operators
  • Code if-statements with multiple conditions

6.1.1: Animating the Player

Scenario file: scroller1.gfar.

  • We want to add animation to our player as we discussed in lesson 4.3
  • For the example, the instructor will be using these two images:
    Rocket with longer flame Rocket with shorter flame
    rocket.png rocket2.png
  • The flame of the second rocket is shorter than the flame of the first

Adding the Animation Images

  • To add the animation we first declare instance variables for the images in the Player class
    private GreenfootImage img1, img2;
    
    • Remember to ALWAYS declare instance variables as private
    • Otherwise it becomes hard to change code as projects grow larger
  • Second we add a constructor that initializes the image variables
    public Player()
    {
        img1 = new GreenfootImage("rocket.png");
        img2 = new GreenfootImage("rocket2.png");
    }
    
  • Recall that the constructor is called automatically whenever an object is constructed
  • Thus when the ScrollWorld constructs a new Player, the constructor method is called
    Player player = new Player(); // calls Player constructor
    

Coding the Animation

  • An animation requires us to alternate images
  • To alternate the images we use an if-else statement
  • The if-else statement allows us to choose between two actions
  • If a condition is true
    • then do this
  • Otherwise it is false
    • so do something else
  • Recall the syntax of the if-else statement:
    if (test) {
       statements1
    } else {
       statements2
    }
    
  • Where:
    • test: the test condition to evaluate
    • statementsX: the statements to execute depending on the test
  • Our example animation alternates two images so we code:
    if (getImage() == img1) {
        setImage(img2);
    }
    else
    {
        setImage(img1);
    }
    
  • We package the animation in a method and call the method from act()

Example Animation Method

public void updateImage()
{
    if (getImage() == img1) {
        setImage(img2);
    }
    else
    {
        setImage(img1);
    }
}

Check Yourself

  1. True or false: an if-else statement allows the programmer to select between two alternatives.
  2. What is wrong with the following if-else statement? (answer)
    if (7 == guess) {
        msg = "*** Correct! ***";
    } else (7 != guess) {
        msg = "Sorry, that is not correct.";
    }
    
  3. If the current image displayed is image1, the image displayed after the following code fragment runs is ________.
    if (getImage() == image1)
    {
        setImage(image2);
    }
    else
    {
        setImage(image1);
    }
    
  4. What is the value of x after the following code segment? (answer)
    int x = 5;
    if (x > 3) {
        x = x - 2;
    } else {
        x = x + 2;
    }
    

6.1.2: Logical Operators

  • Our player character can move four different directions in the scenario
    • right
    • left
    • forwards
    • backwards
  • By using multiple if statements we can select between multiple alternatives
  • However, our movement could go outside the world
  • If we are close to the edge, we no longer want to allow movement
  • Thus we will need to check for two conditions with each if statement:
    • if the correct key is pressed
    • if we are far enough away from the edge to move
  • To make multiple decisions with each if-statement, we will use logical operators

Logical Operators

  • Recall that we test is a key is currently being pressed using Greenfoot.isKeyDown()
  • Sometimes we want to test if a keyboard key is pressed but only if another condition is true as well
  • A logical operator, or Boolean operator, is an operator that connects two Boolean test conditions
  • We use logical operators to combine multiple Boolean expressions into one Boolean result
  • For example, we want to test if the "up" key is pressed and our y-coordinate is greater than 25
  • We write this in Java code like:
    if (Greenfoot.isKeyDown("up") && getY() > 25)
    
  • The && is how we spell "and" in Java
  • Java has several logical operators, but we only need to use three to create any possible test condition
  • These three operators are:
    • && (and)
    • || (or)
    • ! (not)
  • We discuss all three operators below

Truth Tables

  • When we discuss logic we often use truth tables
  • A truth table is a list of input values and the resulting output
  • We can see truth tables below starting with the truth table for logical "and"

Truth Table for && (and) Operator

If cond1 is... And cond2 is... Then cond1 && cond2 is... Example Result
false false false 5 > 10 && 5 < 2 false
false true false 5 > 10 && 5 > 2 false
true false false 5 < 10 && 5 < 2 false
true true true 5 < 10 && 5 > 2 true
  • The && operator returns true if both operands are true and returns false otherwise
  • Activity: construct a truth table for the following test conditions
    if (Greenfoot.isKeyDown("up") && getY() > 25)
    

Truth Table for || (or) Operator

If cond1 is... || cond2 is... Then cond1 || cond2 is... Example Result
false false false 5 > 10 || 5 < 2 false
false true true 5 > 10 || 5 > 2 true
true false true 5 < 10 || 5 < 2 true
true true true 5 < 10 || 5 > 2 true
  • The || operator is true when either or both operands are true
  • Only when both operands are false is the entire || statement false
  • Activity: construct a truth table for the following test conditions
    if (Greenfoot.isKeyDown("up") || getY() > 25)
    

Truth Table for ! (not) Operator

If cond is... Then ! cond is... Example Result
false true !(5 < 2) true
true false !true false
  • The ! operator negates the meaning of a logical expression

Another Look at Truth Tables

  • Note that most computers store true as 1 and false as 0
  • If we substitute 1 for true and 0 for false, we have these truth tables:

two value logic tables

  • With this substitution we see that the AND operation is the minimum of the operands
  • Conversely, the OR operation is the maximum of the operands
  • The NOT operator simply reverses its operand

More Information

Check Your Understanding

  1. Of the following groups ________ is larger.
    1. Students wearing denim
    2. Students wearing denim AND corrective lenses
  2. Of the following groups ________ is larger.
    1. Students wearing denim
    2. Students wearing denim OR corrective lenses
  3. Of the following groups ________ is larger.
    1. Students wearing denim
    2. Students wearing denim AND NOT corrective lenses
  4. Of the following logical expressions, the one that tests to see if x is between 1 and 10 (including 1 and 10) is ________ .
    1. (x >= 1 && x <= 10)
    2. (1 <= x and x <= 10)
    3. (x >= 1 || x <= 10)
    4. (1 <= x or x <= 10)

6.1.3: Implementing Player Movement

  • Now that we can test two conditions with each if-statement we can implement player movement
  • Remember we will need to check for two conditions with each if-statement:
    1. if the correct key is pressed
    2. if we are far enough away from the edge to move
  • When the "up" key is pressed we also make sure our y-coordinate is greater than half the height of our image
  • We write this in Java code like:
    if (Greenfoot.isKeyDown("up") && getY() > 25)
    
  • When both these conditions are true, we move our player character upwards like:
    setLocation(getX(), getY() - 4);
    

Activity: Logical Operators (4m)

Remember that Greenfoot.isKeyDown() checks if a key is currently being pressed and that our ScrollWorld has two public constants WIDTH and HEIGHT. Assuming our image is 100 pixels wide by 50 high, what test conditions do we write for the following:

Logical Tests
Keypress Logical Operator Edge Check
Greenfoot.isKeyDown("up")
Greenfoot.isKeyDown("down")
Greenfoot.isKeyDown("left")
Greenfoot.isKeyDown("right")

Writing the Code

  • Once we have the test conditions for our if statements, we can complete the code
  • The following example shows how to control player movement Click to show example
  • The code is in its own method: checkKeyPress()
  • We must call the checkKeyPress() method from the act() method

Exercise 6.1: Adding Player Movement 5m)

In this exercise, we do implement movement for our player character.

Specifications

  1. Start Greenfoot and open the scroller scenario from the last exercise.
  2. Open the editor for the player character and add the following checkKeyPress() method:
    private void checkKeyPress()
    {
        // add other code here
    }
    
  3. Add a call to the checkKeyPress() in the act() method.
  4. Complete the checkKeyPress() method, adding appropriate movement for your player character.

    For more information see lesson 6.1.3: Implementing Player Movement.

  5. Compile and run your scenario to verify all the changes work well.

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

  6. Optionally, add animation to your Player class as discussed in 6.1.1: Animating the Player
  7. Save a copy of your scenario with all the changes made to upload to Canvas as part of the next lab.

    We will be adding more code to these files in subsequent exercises, so it is not time to submit them to Canvas yet. However, it is a good idea to have a backup copy in case of problems later in development.

When finished please help those around you.

6.1.4: Review

  • In this section we continued developing our side scroller
  • First we animated our player by alternating two images, which we have done before
  • Our example animation alternated two images though we could add more images:
    if (getImage() == img1) {
        setImage(img2);
    }
    else
    {
        setImage(img1);
    }
    
  • Next we looked at how to code more complex conditions by using logical operators
  • A logical operator, or Boolean operator, is an operator that connects two Boolean test conditions
  • Java has several logical operators, but we only need to use three to create any possible test condition
  • These three operators are:
    • && (and)
    • || (or)
    • ! (not)
  • The logical operators AND, OR and NOT are shown in the following table

  • We looked at Java code examples with complex conditions to control player movement like:
    if (Greenfoot.isKeyDown("up") && getY() > 25)
    

6.2: Non-Player Characters

Learner Outcomes

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

  • Discuss what is meant by an NPC
  • Reference the current world
  • React to collisions
  • Use the World act() method to create new objects

6.2.1: Adding Non-Player Characters

Scenario file: scroller2.gfar.

  • Our player characters is all alone in the scenario
  • 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
  • We will want two types of NPCs in our scenario:
    1. One to collect
    2. One to avoid
  • We will develop these NPCs in the following sections

NPCs to Collect

  • We often have objects to collect in a game
  • For example we may collect score for finding certain items such as we do in WBC
  • Other collectibles may include power-ups
  • A power-up is a game object that provides some benefit or extra ability

NPCs to Avoid

  • Many games are filled with NPCs to avoid
  • NPCs may cause damage or reduce the player score
  • For example, touching the Virus in WBC subtracts 100 from the score and may end the game

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: all NPCs should be avoided in a game.

6.2.2: Referencing the World

  • The World class has many useful methods like
    • addObject()
    • removeObject()
    • showText()
  • We often run into situations where an actor wants to call one of these methods
  • However, an actor cannot just call these methods because they are world methods and not actor methods
  • We can only directly call methods of our own class

Calling getWorld()

  • Fortunately, the Actor class has a method to get a reference to the world

    getWorld(): returns a reference to the world the actor lives in.

  • We can save the world reference in a variable like:
    World w = getWorld(); // called from an actor
    
  • We discussed reference variables in lesson 4.3.5: Constructors and Reference Variables
  • A reference has access to all the methods of an object
  • Thus when we get a reference to the world we can call its methods like:
    w.showText("Game Over", 300, 200);
    
  • The last two arguments are the (x, y) coordinates

Chaining Method Calls

  • The getWorld() method returns a reference to the world the actor lives in
  • Rather than saving the world reference in a variable, we can access the reference directly like:
    getWorld().showText("Game Over", 300, 200);
    
  • This is known as chaining method calls
  • We first call getWorld() which returns a reference
  • Then we call a method of the reference like showText()
  • Both of these methods calls occur in one statement with dots between them

Check Yourself

  1. True or false: sometimes an actor needs to call methods of the world class.
  2. True or false: getWorld() returns the current world that the actor lives in.
  3. Calling multiple methods in one statement with dots between them is known as ________

6.2.3: Objects to Collect and Avoid

  • We now add a collectible object to our scenario
  • For our example, we will add an Astronaut class
  • For the backstory, a space habitat has sprung a leak and we are collecting baby astronauts
  • The baby astronauts are temporarily surrounded by an emergency bubble but time is running out!

Moving and Turning

  • In the Astronaut class, we add moving and turning in the act() method
    setLocation(getX() - 2, getY());
    turn(1);
    
  • We use setLocation() so the astronaut moves to the left no matter the rotation

Disappearing an Astronaut

  • We need to remove any astronauts that slip past our player character so we do not overload our scenario
  • To make an astronaut disappear we add the following code to the act() method:
    if (getX() == 0)
    {
        getWorld().removeObject(this);
    }
    
  • When the x-coordinate reaches the left side of the screen, we call the removeObject() method of World
  • As an argument we use the special Java keyword this
  • The this keyword refers to the current object that is executing at this moment
  • In essence, the keyword this means "this current object"
  • Thus by using this as an argument, we are telling the astronaut to remove itself from the world

Example Code for an act() Method

public void act()
{
    setLocation(getX() - 2, getY());
    turn(1);

    if (getX() == 0)
    {
        getWorld().removeObject(this);
    }
}

Objects to Avoid

  • In addition to a collectible object, we add an object to avoid
  • For our example, we will add an Asteroid class using the image:

    asteroid image

  • We add similar code to move, turn and disappear the asteroid as we did the astronaut
  • The difference is in what we do when we collide with the object

Check Yourself

  1. for an actor to move in one direction no matter the orientation, call the method ________.
  2. To make an object disappear, we call the World method ________.
  3. In the following code, this as an argument means ________.
    getWorld().removeObject(this);
    

6.2.4: Reacting to Collisions

  • Let us look at how to react when we collide with an object
  • The player character will do the reacting, so we add the code in the Player class
  • We call the method checkCollision() and call it from the act() method
  • Inside the checkCollision() method we use the commonly used collision methods shown below
  • When we collide with as astronaut, we rescue them and play a happy sound

    hooray.wav

  • When we collide with an asteroid it is disastrous and we play a crash sound

    crash.wav

Commonly Used Actor Methods for Interaction

Method Description
isTouching(Class cls) Returns true if this actor is touching another object of the given class; otherwise returns false.
removeTouching(Class cls) Removes one object of the given class that this actor is currently touching, if any exist.

Example Method checkCollision()

private void checkCollision()
{
    if (isTouching(Astronaut.class))
    {
        removeTouching(Astronaut.class);
        Greenfoot.playSound("hooray.wav");
    }
    if (isTouching(Asteroid.class))
    {
        Greenfoot.playSound("crash.wav");
        Greenfoot.stop();
    }
}

Check Yourself

  1. True or false: collision detection is generally performed in the class that responds to the collisions.
  2. The methods isTouching() and removeTouching() are defined in the class ________.
  3. True or false: the purpose of method isTouching() is to detect if our actor has touched another actor.

6.2.5: Creating New Objects Automatically

  • A world subclass may have an act() method just like an actor
  • One use of an act() method in the world is to add new objects to our scenario
  • An example act() method for the world class is shown below

Example act() Method for ScrollWorld

/**
 * Create new floating objects at random intervals.
 */
public void act()
{
    if (Greenfoot.getRandomNumber(100) < 3)
    {
        int x = WIDTH - 1;
        int y = Greenfoot.getRandomNumber(HEIGHT - 50) + 25;
        addObject(new Astronaut(), x, y);
    }
    if (Greenfoot.getRandomNumber(100) < 1)
    {
        int x = WIDTH - 1;
        int y = Greenfoot.getRandomNumber(HEIGHT - 50) + 25;
        addObject(new Asteroid(), x, y);
    }
}

Assigning the Coordinates

  • The right-hand side of the screen is always at the WIDTH - 1
  • The reason for the -1 is that we count pixels starting at 0 instead of 1
  • The y-coordinate is a random number like we discussed in lesson 3
  • However, with an unbounded world we must use care when launching an actor off-screen

Check Yourself

  1. True or false: the world may have an act() method just like an actor can.
  2. To appear at the right side of the screen with a world size of 600 x 400 pixels, we set the y-coordinate to ________.
  3. True or false: with an unbounded world we must be careful to not launch objects off screen.

Exercise 6.2: Adding NPCs (5m)

In this exercise, we do add NPCs to our scrolling world scenario.

Specifications

  1. Start Greenfoot and open the scroller scenario from the last exercise.
  2. Add two new classes to the scroller scenario, one to collect and one to avoid.

    For more information see lesson: 6.2.1: Adding Non-Player Characters.

  3. Add code like the following to both of the new classes, such that the actors move from one side of the screen to the other.
    public void act()
    {
        setLocation(getX() - 2, getY());
        turn(1);
    
        if (getX() == 0)
        {
            getWorld().removeObject(this);
        }
    }
    
  4. In the player character class, add a checkCollision() method like the following example. Call the method from the act() method of your player character.
    private void checkCollision()
    {
        if (isTouching(Astronaut.class))
        {
            removeTouching(Astronaut.class);
            Greenfoot.playSound("hooray.wav");
        }
        if (isTouching(Asteroid.class))
        {
            Greenfoot.playSound("crash.wav");
            Greenfoot.stop();
        }
    }
    
  5. Add an act() method to the ScrollWorld class like the following such that both new objects are added to the world at random intervals.
    public void act()
    {
        if (Greenfoot.getRandomNumber(100) < 3)
        {
            int x = WIDTH - 1;
            int y = Greenfoot.getRandomNumber(HEIGHT - 50) + 25;
            addObject(new Astronaut(), x, y);
        }
        if (Greenfoot.getRandomNumber(100) < 1)
        {
            int x = WIDTH - 1;
            int y = Greenfoot.getRandomNumber(HEIGHT - 50) + 25;
            addObject(new Asteroid(), x, y);
        }
    }
    
  6. Compile and run your scenario to verify all the changes work well.

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

  7. Save a copy of your completed lesson scenario to upload to Canvas as part of Lab 5. Also, we will add to the scenario next week so bring it to class.

When finished, please help those around you.

6.3: Scoring the Scroller

Learner Outcomes

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

  • Add more interesting scoring rules to a scenario
  • Add parameters to methods
  • Reference user defined methods in the world subclass
  • Concatenate strings
  • Add timers to scenarios

6.3.1: More Flexible Scoring in the World

Scenario file: scroller3.gfar.

  • So far our scenarios have kept score in one of the actor classes
  • For example, in our bug scenario we kept score in the Bug class (see lessons 4.3.1 and 4.3.2)

Keeping Score in an Actor

  • We could keep score in our player class as we have done before
  • First we declare an instance variable in the player:
    private int score;
    
  • Then whenever we collect an astronaut we add a value to the score
    if (isTouching(Astronaut.class))
    {
        score = score + 20;
        // ... other code here
    }
    

More Interesting Scoring Rules

  • We want to add scores for other game events like:
    • Collecting an astronaut (see above)
    • Losing points if an astronaut reaches the left side of the world
    • Being hit by an asteroid does not immediately end the games but instead we loose points
    • If our score falls below zero, we lose and the game ends
  • The second scoring rule is problematic because an astronaut detects if they have left the world
  • An actor like astronaut cannot directly call methods of other actors like our player
  • Thus an astronaut cannot directly access to the score variable in Player to adjust the score
  • We can write code to access the Player object, but it is easier if we keep the score in the world

Check Yourself

  1. True or false: some scoring actions may happen outside the player character.
  2. True or false: an actor cannot directly access the private fields of objects from another class.
  3. True or false: for more varied scoring rules, we need to store the score in a central location like the world.

6.3.2: How to Keep Score in the World

  • We keep score in the world in a similar way to keeping the score in an actor
  • First we declare an instance variable in the world:
    private int score;
    
  • Next we add a method for an actor to call that adjusts the score
    public void addScore() // like in the textbook
    {
        score = score + 20;
    }
    
  • To access a private variable in an object of another class we need use a method call
  • However the above method does not allow us to subtract 15 points for rule 2
  • We could write multiple methods, one for each scoring rule
  • However a better approach is to add a parameter to the method that allows us to both add and subtract values

Adding Parameters

  • Recall from lesson 2.4.3 that every method has a parameter list

    Parts of a method

  • For methods we have written so far, most parameter lists have been empty
  • For addScore(), we add a parameter so we have a single method to call for all scoring rules
    public void addScore(int points)
    {
        score = score + points;
    }
    
  • Our addScore() method now receives the number of points in the points parameter
  • We add the received points to our score variable, allowing us to add or subtract any value

Activity: Scoring in the World (4m)

  1. Start Greenfoot and open the scrolling world scenario from last week.

    If you do not have the scenario then use scroller3.gfar and add to your scenario as homework.

  2. Open the editor for ScrollWorld and declare an instance variable in the world:
    private int score;
    
  3. Next add the following method to the world:
    public void addScore(int points)
    {
        score = score + points;
    }
    
  4. Compile your scenario to make sure the changes were made correctly.

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

  5. Test the changes by:
    1. Moving a tile, so you can access the world
    2. Right-clicking the exposed world and calling the method addScore()
    3. Right-clicking the exposed world and selecting Inspect

    You should see the score variable displayed with the value you entered in step b. If you have problems, ask a classmate or the instructor for help as needed.

  6. Save your scenario so you can update it in future exercises.

Check Yourself

  1. True or false: To access a private variable in an object of another class, the other class must provide access via a method.
  2. True or false: adding parameters to a method can make the method more flexible and useful.
  3. When calling a method with a parameter, you must supply the necessary ________.

6.3.3: Accessing a Method in the World

  • Recall that we discussed in lesson 6.2.2 how to reference the world by calling getWorld()
  • In our Player class we can add code to call our addScore() method like:
    if (isTouching(Astronaut.class))
    {
        World world = getWorld();
        world.addScore(20);
        // ... other code here
    }
    
  • However, we get an error if we do

    cannot find symbol - method addScore(int)

  • The error is due to a tricky problem that we must solve with casting

Casting Needed

  • The problem with the above code is that World does not have an addScore() method
  • Instead, our ScrollWorld subclass has an addScore() method
  • The Actor method getWorld() correctly returns a ScrollWorld but says it is returning a World
    public World getWorld()
    
  • The compiler simply accepts that getWorld() returns a World class instead of ScrollWorld
  • We solve the problem with a cast, which tells the compiler we are working with a different object: ScrollWorld
  • We write a cast by writing the correct type in parenthesis in front of the method call:
    if (isTouching(Astronaut.class))
    {
        ScrollWorld world = (ScrollWorld) getWorld();
        world.addScore(20);
        // ... other code here
    }
    
  • Casting is a tricky concept which many people will not understand at this stage
  • We will return to casting once we know more about typing and subtyping

Activity: Calling addScore() in the World (4m)

  1. Start Greenfoot and open the scrolling world scenario from the last Activity.
  2. Open the editor for the Player class and locate the checkCollision() code. Inside the method, add a call to addScore() method in the world like:
    if (isTouching(Astronaut.class))
    {
        ScrollWorld world = (ScrollWorld) getWorld();
        world.addScore(20);
        removeTouching(Astronaut.class);
        Greenfoot.playSound("hooray.wav");
    }
    
  3. Compile your scenario to make sure the changes were made correctly.

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

  4. Test your code by running into an object to collect and verifying the score changes.
  5. In addition, for the object to avoid, add a call to the addScore() method with a negative score like:
    if (isTouching(Asteroid.class))
    {
        ScrollWorld world = (ScrollWorld) getWorld();
        world.addScore(-100);
        Greenfoot.playSound("crash.wav");
        //getWorld().showText("Game Over", 300, 200);
        //Greenfoot.stop();
    }
    

    In addition, remove or comment out any calls to showText() or Greenfoot.stop() as shown above.

  6. Again, compile your scenario to make sure the changes were made correctly.

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

  7. Test your code by running into an object to avoid and verifying the score changes.
  8. Save your scenario so you can update it in future exercises.

Check Yourself

  1. True or false: unless we tell it differently with a cast, the compiler thinks that getWorld() returns a World object instead of a subclass of World.
  2. Changing the data type of a value is known as a ________.
  3. We write a cast by writing the correct type inside ________.
    1. curly braces: { }
    2. square brackets: [ ]
    3. parenthesis: ( )
    4. slashes: / /

6.3.4: Strings and Concatenation

  • Once we can keep the score we want to display it in the scenario
  • We can easily display the score using the World method showText()
  • The method showText() requires a String argument
  • However, our score variable has an int data type
  • Thus we will need to convert the int value from score into a String

Reviewing Strings

  • Remember from lesson 3.5.1 that a string is a sequence of characters (letters, digits, other symbols)
  • To indicate a string in a program we enclose the characters within double quotes
    "This is a string"
    "b"
    "$3.95"
    "My name is Ed"
    
  • We create variables for strings using the String data type:
    String firstName;           // declaration
    firstName = "Edward";       // assignment
    String lastName = "Parrish" // declaration + assignment
    
  • When a method parameter expects a String, we must provide either characters in double quotes or a variable of type String
    Greenfoot.isKeyDown("right")
    

Joining Strings (Concatenation)

  • We may join two strings together using the '+' operator
  • The join operation is known as concatenation
  • For example:
    String cool = "Java" + " rules!"
    System.out.println(cool);
    

String Conversions

  • We can easily convert numerical data types, like int or double, to strings using the + operator
  • If an expression starts with a String type then the compiler converts other types to String automatically
  • For example:
    System.out.println("The result is: " + 7 + 2);
    
  • Produces the output:
    The result is: 72
    
  • If you want to add 7 + 2, you need to use parenthesis:
    System.out.println("The result is: " + (7 + 2));
    

Displaying the Score with showText()

  • We can update our addScore() method in ScrollWorld class to display the score
    public void addScore(int points)
    {
        score = score + points;
        showText("Score: " + score, 80, 25);
    }
    
  • The message to showText() starts with a String
  • Thus the int score is converted to a String data type by the concatenation operation (+)

Activity: Showing the Score (3m)

  1. Start Greenfoot and open the scrolling world scenario from the last Activity.
  2. Open the editor for the world subclass and locate the addScore() method. Inside the method, add a call to showText() like:
    public void addScore(int points)
    {
        score = score + points;
        showText("Score: " + score, 80, 25);
    }
    
  3. Compile your scenario to make sure the changes were made correctly.

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

  4. In addition, add a call to addScore() from the constructor to display the initial score, usually 0.
    public ScrollWorld()
    {
        super(WIDTH, HEIGHT, 1, false); // unbounded world
        prepare();
        addScore(0); // show initial score
    }
    
  5. Test your code by running the game and making sure the score is displayed.
  6. Save your scenario so you can update it in future exercises.

Check Yourself

  1. A sequence of characters is known as a(n) ________.
  2. The operator used to join two strings is ________.
  3. The contents of s3 is ________ after the following code executes.
    String s1 = "Hi ", s2 = "Mom!";
    String s3 = s1 + s2;
    

6.3.5: Keeping Time

  • One problem with our scenario is that hitting an asteroid occurs multiple times
  • This means that we will lose even for a glancing blow from an asteroid
  • What we need is some time for our player to get out of the way before another penalty is applied

Adding a Timer

  • The usual way to track time is to count game cycles
  • By counting game cycles, the timing is controlled by the speed of the game
  • To track game cycles, we first declare an instance variable in Player
    private int hitTimer;
    
  • When we are hit by an object to avoid, we will give player some time to make an escape

Planning the Time

  • We want to allow some number of game cycles to move away from an object to avoid
  • To allow time, we add a second test condition to our if-statement for the collision
    if (isTouching(Asteroid.class) && hitTimer == 0)
    {
        // other code omitted
        hitTimer = 25;
    }
    
  • Inside the if-statement we set the amount of time we want to allow
  • Since a scenario runs about 50-60 cycles per second, we are allowing about ½ second to move

Counting Down the Time

  • In addition to the above, we need to count down our timer every game cycle
  • To count down the time, we add an else-if clause to our if-statement
    else if (hitTimer > 0)
    {
        hitTimer--;
    }
    
  • After we hit an asteroid, this code keeps the timer running until it reaches zero
  • Once the timer reaches zero we can get hit again
  • The complete if-else-if code is listed below

Adding a Hit Timer to the Asteroid Collision

if (isTouching(Asteroid.class) && hitTimer == 0)
{
    ScrollWorld world = (ScrollWorld) getWorld();
    world.addScore(-100);
    Greenfoot.playSound("crash.wav");
    //getWorld().showText("Game Over", 300, 200);
    //Greenfoot.stop();
    hitTimer = 25;
}
else if (hitTimer > 0)
{
    hitTimer--;
}

Exercise 6.3: Finishing the Scoring (10m)

In this exercise, we finish off the scoring for our scroller scenario. Make sure to compile whenever you add a line of code to verify you added the code correctly.

Specifications

  1. Start Greenfoot and open the scroller scenario from the previous Activities:
    1. Activity: Scoring in the World
    2. Activity: Calling addScore() in the World
    3. Activity: Showing the Score
  2. Open the Player class and add an instance variable for the hit timer, like:
    private int hitTimer;
    

    Again, compile after adding each line of code to verify you added the code correctly.

  3. Add a second test condition to your if-statement for the collision, like:
    if (isTouching(Asteroid.class) && hitTimer == 0)
    {
        // other code omitted
        hitTimer = 25;
    }
    
  4. In addition, set the amount of time to allow on the hit timer as shown above.
  5. To count down the time, add an else-if clause to your if-statement
    else if (hitTimer > 0)
    {
        hitTimer--;
    }
    

    For more information see section: 6.3.5: Keeping Time.

  6. Compile and run your code, verifying that hitting an object to avoid has a delay before another score penalty is applied.

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

  7. As another scoring step, in Astronaut add a call to ScrollWorld that reduces the score when disappearing off the edge of the world, like:
    if (getX() <= 0)
    {
        ScrollWorld world = (ScrollWorld) getWorld();
        world.addScore(-15);
        getWorld().removeObject(this);
    }
    
  8. As a "final" step, open the editor for the ScrollWorld class and update the addScore() method with code to test if the score is less than zero, like:
    if (score < 0)
    {
        Greenfoot.playSound("game-over.wav");
        showText("Game Over", 300, 200);
        Greenfoot.stop();
    }
    

    You can use the following game over sound: game-over.wav.

  9. Compile and run your scenario to verify all the changes work well.

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

  10. Save a copy of your completed lesson scenario to upload to Canvas as part of Lab 5.

When finished, please help those around you.

6.3.6: Referencing 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 some action should occur
  • By careful placement of collision detection code, we can often avoid having to call methods of other actors
  • However, sometimes an NPC actor needs a reference to the player

Saving a Reference in the World

  • One easy way to get a reference to the player is to track the player in the world subclass
  • For example, we can store a reference to the player in an instance variable
  • private Player player;
    
  • When the ScrollWorld adds a player to the scenario, the player object is assigned to the instance variable
    player = new Player();
    addObject(player, 150, getHeight() / 2);
    
  • In our example, the Player variable is assigned in the prepare() method

Accessing the Player Reference

  • To access the player instance variable, we add a getPlayer() method to ScrollWorld
    public Player getPlayer()
    {
        return player;
    }
    
  • Now when an NPC needs a reference to the player, it can get it from the world
    ScrollWorld world = (ScrollWorld) getWorld();
    Player p = world.getPlayer();
    

Check Yourself

  1. True or false: sometimes an actor needs to call methods of another actor.
  2. True or false: one convenient way to access an actor is to keep track of the actor in the scenario world.
  3. True or false: getWorld() returns the current subclass of world as a World object.

6.3.7: Review

  • In this section we looked at how to reference user defined methods in the world subclass
  • As examples, we added more insteresting scoring rules to the game
  • To make the scoring more flexible, we added the scoring to the world subclass (ScrollWorld)
  • First we declared an instance variable in the world:
    private int score;
    
  • Next we add a method for an actor to call that adjusts the score
    public void addScore(int points)
    {
        score = score + points;
        showText("Score: " + score, 80, 25);
    }
    
  • The method has a parameter that recieves the number of points to add to the score
  • During the method call, we display the current score on the screen as well
  • To access the world method from an actor, we must use a cast like:
    ScrollWorld world = (ScrollWorld) getWorld();
    world.addScore(20);
    
  • The cast tells the compiler that getWorld() actually returns a world subclass object and not a World object

Timers

  • Another technique we looked at was to add timers to our scenario
  • The usual way to track time is to count game cycles
  • By counting game cycles, the timing is the same no matter the speed of the game
  • For our example, we first declared an instance variable in Player
    private int hitTimer;
    
  • We then added a second test condition to our if-statement testing a collision
    if (isTouching(Asteroid.class) && hitTimer == 0)
    {
        // other code omitted
        hitTimer = 25;
    }
    else if (hitTimer > 0)
    {
        hitTimer--;
    }
    
  • To count down the time, we added an else-if clause to our if-statement
  • The timer code allows us a set number of game cycles to pass until the executing the if-statement again

Wrap Up

Due Next:
Q5: Basic Arcane Studies (10/5/17)
Lab 6: Piano Practice (10/10/17)
Q6: Scenario Mashup (10/12/17)
  • When class is over, please shut down your computer
  • You may complete unfinished lesson exercises at any time before the due date.
Home | Canvas | Schedule | Syllabus | Room Policies
Help | FAQ's | HowTo's | Links
Last Updated: October 05 2017 @14:01:06