8: Object Interaction

What We Will Cover


Illuminations

Questions from last class or the Reading?

Homework Questions?

8.1: Communicating with Other Objects

Learner Outcomes

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

  • Discuss references
  • Write code to interact with the world
  • Write code to interact with the actor objects
  • Use Lava Lists
  • Write code using the for-each loop

8.1.1: Object References

  • In this lesson we review the new concepts from the Autumn scenario
  • Towards the end of the review we will apply these concept in a new way

Reference Variables

  • In section 4.3.5 we introduced the concept of reference variables
  • An object is an instance of a class that is stored somewhere in memory
  • A reference variable "refers to" ("points to") the object located in memory

Bug Object With Variables Referring to Image Objects

Bug object with reference variables

Talking to an Object

  • For one object to "talk to" another object, it must have a reference to the other object
  • Usually, references are stored in a variable of a class data type like:
    GreenfootImage image = new GreenfootImage(size, size);
    
  • For an object to talk to another object, it calls methods of the object using the reference:
    image.setColor(Color.CYAN);
    
  • In the above we "talk to" the image object by calling its setColor() method

Check yourself

  1. True or false: a reference variable is a variable with a class name as the data type.
  2. Of the following variable declarations, the reference variable is ________.
    1. int x;
    2. double trouble;
    3. float away;
    4. String aling;
  3. True or false: with a reference variable, we can "talk to" another object by calling the object's methods.

8.1.2: Interacting with the World

  • In lesson 6.2.2 we discussed how to get a world reference from within an actor
    World w = getWorld(); // called from an actor
    
  • After we save the world in a reference variable, we can call methods of the world like:
    w.showText("Game Over", 300, 200);
    
  • We can chain the method calls to get the same effect, like:
    getWorld().showText("Game Over", 300, 200);
    
  • In the above, getWorld() returns a reference from which we can call methods of the World object
  • Another use of getWorld() was in the Autumn Leaf scenario to add Leaf objects to the world:
    getWorld().addObject(new Leaf(), getX(), getY());
    

Calling a Method in a World Subclass

  • When we add a method to a world subclass, we cannot call the method directly
  • For example, if we add a method named addScore() to a world subclass we cannot use:
    World w = getWorld();
    w.addScore(20);
    
  • If we tried we would get an error:

    cannot find symbol - method addScore(int)

  • In lesson 6.3.3, we learned that we need to use casting to solve this problem, like:
    ScrollWorld w = (ScrollWorld) getWorld();
    w.addScore(20);
    
  • The above casts the reference from getWorld() to a ScrollWorld object

Check Yourself

  1. True or false: getWorld() returns the current world that the actor lives in.
  2. Calling multiple methods in one statement with dots between them is known as ________
  3. 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.
  4. Changing the data type of a value is known as a ________.
  5. We write a cast by writing the correct type inside ________.
    1. curly braces: { }
    2. square brackets: [ ]
    3. parenthesis: ( )
    4. slashes: / /

8.1.3: Interacting with Actors

  • For one actor to interact with another, it must have a reference to the other actor
  • One way to get a reference is to save the reference in a variable like we did in lesson 6.3.6
  • In lesson 6.3.6 we kept a reference to a player character in a variable of the world

Collision Detection Methods

  • Another way to get a reference is to call one of Greenfoot's collision detection methods
  • The following methods check if one actor intersects another
  • If an intersection occurs, then the methods return a reference to that other actor
  • For example in the Autumn Leaves scenario:
    Leaf leaf = (Leaf) getOneIntersectingObject(Leaf.class);
    
  • If we want to call a method of the Leaf class we must cast the returned reference to a Leaf
  • If we only want to call methods of the Actor class then casting is not required
  • In the later case we would write code like:
    Actor a = getOneIntersectingObject(Leaf.class)
    

Actor Collision Detection Methods Returning a Single Reference

Method Description
getOneIntersectingObject(Class cls) Returns an object that intersects this object, or null if no objects intersect.
getOneObjectAtOffset(int dx, int dy, Class cls) Returns an object that is located at the specified cell relative to this objects location, otherwise returns null.
  • Both methods return at most one object, even if more than one intersects
  • If no objects are found, both methods return the keyword null
  • Passing null to the cls parameter returns any object found by the method
  • Passing a class literal to the cls parameter restricts the type of object returned

The null Value

  • Notice that for the above methods null is a special value
  • The value null is returned when the methods do not detect a collision
  • Any reference variable may be assigned the value null
  • If a variable stores the value null, it means the variable is not currently storing any reference
  • Essentially, null means the variable is empty
  • To test if a variable is storing null we use an if-statement
    Leaf leaf = (Leaf) getOneIntersectingObject(Leaf.class);
    if (leaf != null)
    {
        // if we are here we are touching a leaf
    }
    

Activity: Declaring Reference Variables (5m)

  1. In the following box, declare a reference variable for the data types and assign each variable the value null.
    1. Leaf
    2. Bubble
  2. After the variable declarations, write an assignment statement that calls getOneIntersectingObject() and assigns the returned value to each variable.
  3. In addition, write an if-statement for each reference variable that tests if the value of each variable is NOT equal to null.
  4. Verify your statements with another student in the class.
  5. Press the Record button to submit your work.

Activity Check Click to check answer

  1. Checking when one object intersects another is known as ________ ________.
  2. The special value that the collision detection methods return when no collision is detected is ________.
  3. If we have a reference from method, but want to call a method of an Actor subclass, we must tell the compiler to change the type using a ________.
  4. The special value that reference variables store that means the variable is empty is ________.
  5. Of the following, a valid test if a reference variable "obj" is empty is ________.
    1. if (obj == null)
    2. if (obj.isEmpty())
    3. if (obj.empty())
    4. if (obj.null)

8.1.4: Interacting with Lists of Actors

  • In the world class are two methods that return a list of objects
  • The most commonly used method is getObjects(), so we will focus on that method
  • To make use of the returned list, we need to learn how to use the List type in Java

World Methods Returning a List of Objects

Method Description
getObjects(Class cls) Returns a list of all the objects in the world, or all the objects of a particular class.
getObjectsAt(int x, int y, Class cls) Returns a list of all objects at a given cell, or all the objects of a particular class at a given cell.
  • Both methods return a List of objects
  • Passing null to the cls parameter returns all types of objects
  • Passing a class literal to the cls parameter restricts the type of object returned

Java Library Classes

  • Java comes with a large collection of useful classes such as the Color class we discussed previously
  • We can see the documentation for all the classes at the Java API
  • The bottom left pane lists all of the classes in the Java libraries
  • There are literally thousands of classes and so the classes are organized into logically related packages
  • The upper left pane allows you to select classes within a single package
  • When we select a class, the main window displays the documentation for this class
  • We select a class using an import statement
    import java.packageName.ClassName
    
  • The documentation we want is for the List type which is in the util package
    import java.util.List;
    

List Types

  • When we work with collections of items, a natural approach is to use a list
  • We have used lists before when working with arrays in lesson 7.3.3
  • Java has several other ways for working with lists of data including:
    • ArrayList: resizable arrays
    • LinkedList: nodes grouped to make a sequence
    • Stack: last-in-first-out (LIFO) sequence of items
  • Different list types have characteristics that make them better choices in certain situations
  • The list types shown above have a common supertype called List
  • Technically, List is an interface which is a structure like a class
  • The List interface is a way for Java to specify a common set of methods for many different list classes
  • Which underlying list class the getObjects() method returns is not important
  • We just use the methods of the List interface as a common abstraction of the list classes
  • The following are some commonly used methods of List

Some Commonly Used Methods of List

Method Description
add(item) Adds the specified item to the end of the list.
add(index, item) Adds the specified item at the specified index of the list.
get(index) Retrieves the item at the specified index in the list.
remove(index) Removes the item at the specified index in the list.
set(int index, item) Replaces the item at the specified index in the list.
size() Returns the number of items in the list.

Generic Types

  • Let us look at the description of the List interface again:
    Interface List<E>
  • Notice the <E> after the word List
  • This is known as a generic type
  • When we use a generic type, we must include a data type inside the angle brackets: <E>
  • The "E" is a place holder name and is like a parameter for the data type
  • The actual type we want to store in the list is substituted for the "E"
  • For example, if we had a list of strings we would declare a list like:
    List<String> names;
    
  • Notice that the class name "String" replaced the "E" in the type name
  • For Autumn Leaves we had a list of Leaf objects and declare a list like:
    List<Leaf> bodies;
    
  • Since we are getting a list from the getObjects() method we write:

    List<Leaf> leaves = getObjects(Leaf.class);

Activity: Declaring Lists (3m)

  1. In the following box, declare a List variable appropriate for storing the data types:
    1. String
    2. Leaf
    3. Bubble
  2. Verify your statements with another student in the class.
  3. Press the Record button to submit your work.

Check Yourself Click to show answer

  1. To add an item to a List, call the method ________.
  2. To remove an item to a List, call the method ________.
  3. To find out how many items a List contains, call the method ________.
  4. True or false: a generic type is a parameter for a data type, meaning we must specify a specific data type when declaring variables.
  5. For the List type, a correct declaration of a variable is ________ statement.
    1. List<String names>;
    2. List<names> String;
    3. List<String> names;
    4. List<String> names();

8.1.5: Working with Lists

  • Let us look at how to work with a List returned from the method getObjects()
  • Method getObjects() of the World class gets a list of all the objects in the world
  • To store the list obtained from calling getObjects(), we need to declare a List variable
  • We can see the variable declared in the following code from the Block Class of Autumn Leaves

Method checkMouseClick() from the Block Class of Autumn Leaves

private void checkMouseClick()
{
    if (Greenfoot.mouseClicked(null))
    {
        World world = getWorld();
        List<Leaf> leaves = world.getObjects(Leaf.class);

        for (Leaf leaf : leaves)
        {
            leaf.changeImage();
        }
    }
}

Enhanced for Loop

  • Notice the for-loop in the above code
  • Officially this loop is known as the enhanced for loop
  • However, most people call this loop the for-each loop
  • The for-each loop is specially designed to work with collections of data such as lists and arrays
  • It reads each item in a list and provides a variable of the list item type to work with
  • Syntax:
    for (itemType variableName : listName) {
        // statements to execute
    }
    
  • Where:
    • itemType: the data type of all the list items
    • variableName: the name to use for each variable
    • listName: the name of the list
  • Note that we do not code initialization, test and increment statements
  • Instead, we declare a variable that refers to each array element
  • Within the loop, we use this variable to access each array element
  • For comparison we rewrite the for-each loop using a standard for loop

Comparison of a Standard for-Loop and a for-each Loop

World world = getWorld();
List<Leaf> leaves = world.getObjects(Leaf.class);

for (int i = 0; i < leaves.size(); i++)
{
    Leaf leaf = leaves.get(i);
    leaf.changeImage();
}
World world = getWorld();
List<Leaf> leaves = world.getObjects(Leaf.class);

for (Leaf leaf : leaves)
{
    leaf.changeImage();
}

Check Yourself

  1. For the above comparison, what are the similarities and differences between the two loops?
  2. True or false: the for-each loop is a special version of the for-loop designed for collections like lists or arrays.
  3. Of the following two items, the for-each loop is ________.
    1. for (int i = 0; i < leaves.size(); i++)
    2. for (Leaf leaf : leaves)
  4. True or false: the variable after the colon (:) in a for-each loop must be a collection such as an array or list.
  5. True or false: in the for-each loop, the variable declared before the colon (:) must be the same type as an item in the list.

Exercise 8.1: Herding Bubbles (6m)

In this exercise, we enhance the bubbles scenario to add a way to herd the bubbles towards a destination.

Specifications

  1. Start Greenfoot and open the bubbles scenario from Exercise 7.4: Finishing our Bubble Scenario.

    If you do not have the scenario, then use: bubbles1.gfar.

  2. Open the editor for the BubbleWorld class and add an import statement for the List class.
    import java.util.List;
    
  3. In the BubbleWorld class, add an act() method.
    /**
     * Turn the bubbles toward a destination.
     */
    public void act()
    {
        // add code here
    }
    
  4. Compile your BubbleWorld class after every step to verify you added the code correctly.

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

  5. Within the act() method, add code to declare a reference variable named bubbles for a list of bubbles like:
    List<Bubble> bubbles = getObjects(Bubble.class);
    
  6. After the bubbles declaration, add code to test for a mouse click
    if (Greenfoot.mouseClicked(null))
    {
        // if we are here the mouse was clicked
    }
    
  7. Add the following code inside the above if-statement that retrieves the coordinates of a mouse click.
    MouseInfo info = Greenfoot.getMouseInfo();
    int x = info.getX();
    int y = info.getY();
    

    For more information, see Class MouseInfo in the Greenfoot API.

  8. Inside the if-block, add a statement that gets all the Bubble objects from the world and stores them in a List named bubbles.

    For more information see section 8.1.4: Interacting with Groups of Actors.

  9. At the end of the if-block, add an enhanced for-loop like the following:
    for (Bubble b : bubbles)
    {
        b.turnTowards(x, y);
    }
    

    For more information see section 8.1.5: Working with Lists. If you have problems, ask a classmate or the instructor for help as needed.

  10. After compiling, test your changes by clicking in the world and checking if the bubbles move towards the point of your mouse click.

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

  11. Save a copy of your completed lesson scenario to submit with the next lab.

When finished please help those around you.

8.1.6: Review

  • In this lesson we looked at how to communicate with other objects in a scenario
  • To "talk to" another object we must have a reference to the other object
  • Usually, references are stored in a variable:
    GreenfootImage image = new GreenfootImage(size, size);
    
  • For an object to "talk to" another, it calls methods of the object using the reference like:
    image.setColor(Color.CYAN);
    
  • In the above we "talk to" the image object by calling its setColor() method
  • One way to store a reference is to save it in the world as we discussed in lesson 6.2.2
  • If we want to call a method of the world subclass, then we must cast the reference like we did in lesson 6.3.3
    ScrollWorld w = (ScrollWorld) getWorld();
    w.addScore(20);
    

Collision Detection Methods

  • Another way to get a reference to an actor is to call one of Greenfoot's collision detection methods
  • We saw an example from the Autumn Leaves scenario:
    Leaf leaf = (Leaf) getOneIntersectingObject(Leaf.class);
    
  • In the above, getOneIntersectingObject() is a collision detection method that returns an actor reference
  • When we work with reference variables, we often come across the special value null
  • The value null is returned when the methods do not detect a collision
  • Any reference variable may be assigned the value null
  • If a variable stores the value null, it means the variable is not currently storing any reference
  • Essentially, null means the variable is empty
  • To test if a variable is storing null we use an if-statement
    Leaf leaf = (Leaf) getOneIntersectingObject(Leaf.class);
    if (leaf != null)
    {
        // if we are here we are touching a leaf
    }
    
  • The World class has methods like getObjects() that returns a list of objects
  • We store the list in a List variable like:
    List<Leaf> leaves = getWorld().getObjects(Leaf.class);
    
  • Once we have stored the list, we often work with it using the for-each loop like:
    List<Leaf> leaves = world.getObjects(Leaf.class);
    for (Leaf leaf : leaves)
    {
        leaf.changeImage();
    }
    

8.2: Adding a Visualizer to the Piano

Learner Outcomes

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

  • Add a music visualizer screen to the piano project
  • Explain the reasons for using a constant variable
  • Make use of reference variables to communicate between objects

8.2.1: Music Visualizers

  • We discuss how to add a simple music visualizer to our piano project in the following sections

8.2.2: Creating a Piano Visualizer

  • To create a music visualizer, we start with the completed Piano scenario:

    Scenario file: piano-complete.gfar.

  • Download the file to a convenient location like the Desktop and unzip the file
  • Double-click the project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario

Creating the Visualizer Screen

  • We want to add a large rectangular screen above the piano to display the visualization

  • The large area we will call our visualization screen
  • We make the screen by creating a subclass of Actor named Visualizer
  • For the "Scenario images" choose no image by selecting "Unspecified"

Creating an Image

  • Once created, we open the Visualizer source code
  • The Greenfoot Color class lets us choose colors to display
  • We will review how to use colors later in the lesson
  • We want to create our own image for the visualization so we add an instance variable:
    private GreenfootImage image;
  • Then we add a constructor to set up the image for the screen
    public Visualizer()
    {
        image = new GreenfootImage(800, 340);
        image.setColor(Color.DARK_GRAY);
        image.fill();
        setImage(image);
    }
    
  • The first command in the constructor creates a new GreenfootImage object
  • The second command sets the current drawing color for the new image
  • On the next line, the entire image is filled with the drawing color
  • The final command sets the newly created image as the one to be displayed

Activity: Creating a Piano Visualizer (5m)

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

    Scenario file: piano-complete.gfar.

  2. Double-click the project.greenfoot file in the unzipped folder to start Greenfoot and open the scenario.
  3. Make the visualizer screen by creating a subclass of Actor named Visualizer

    For "Scenario images" choose no image by selecting "Unspecified".

  4. Open the editor for the Visualizer class.
  5. Next, add an instance variable for the Visualizer image:
    private GreenfootImage image;
    
  6. In addition, add a constructor to set up the image for the screen:
    public Visualizer()
    {
        image = new GreenfootImage(800, 340);
        image.setColor(Color.DARK_GRAY);
        image.fill();
        setImage(image);
    }
    
  7. Test the Visualizer by right-clicking Visualizer under "Actor classes" and selecting new Visualizer().

    You should see a large dark gray rectangle appear under your mouse when you click in the world.

  8. Save your updated scenario as we will be adding to it in the next Activity.

When finished please help those around you.

Check Yourself

  1. The Greenfoot class used for constructing images is __________.
  2. Enter the code to construct an image 200 pixels wide by 100 pixels high.

    answer

  3. To set the drawing color of an image, call the GreenfootImage method __________.
  4. To fill the entire image with the current drawing color, call the GreenfootImage method __________.

8.2.3: Adding the Visualizer to the Piano

  • Now we want to add our new visualizer screen to the piano
  • We start by opening the source code for the Piano class
  • We will need to enlarge the piano world and move the piano keys to the bottom
  • As we will be making these changes in several places, it makes sense to declare a constant
    public static final int SCREEN_HEIGHT = 340;
    
  • Then if we decide to change the size of the visualizer screen, making the change is easy
  • We discussed constants and static variables in lesson 5.1.4

Making the Changes

  • We change the height of the world by adding more height in the constructor:
    super(800, 340 + SCREEN_HEIGHT, 1);
    
  • Then we move the piano parts around by adding SCREEN_HEIGHT to the y-coordinate in 3 places:
    showText("Click 'Run', then use your keyboard to play", 400, 320 + SCREEN_HEIGHT);
    addObject(key, 54 + (i*63), 140 + SCREEN_HEIGHT);
    addObject(key, 85 + (i*63), 86 + SCREEN_HEIGHT);
    
  • Now that we have space in the piano, we add the visualizer screen
  • We add a Visualizer variable to Piano and construct a new Visualizer object:
    private Visualizer screen = new Visualizer();
    
  • Then we add the Visualizer screen at the end of the Piano constructor:

    addObject(screen, getWidth() / 2, screen.getImage().getHeight() / 2);

  • We compile the class and the screen should now be visible

Activity: Adding the Visualizer to the Piano (5m)

  1. Start Greenfoot and open the Piano scenario from the last Activity.

    If you did not keep the scenario, then complete Activity: Adding the Visualizer to the Piano now and then continue with these instructions.

  2. Open the source code for the Piano class and add a constant for the screen height:
    public static final int SCREEN_HEIGHT = 340;
    
  3. Change the height of the world by changing adding the SCREEN_HEIGHT in the constructor:
    super(800, 340 + SCREEN_HEIGHT, 1);
    
  4. Add the expression " + SCREEN_HEIGHT" (without the quote marks) to the positioning statements of the Piano class shown below:
    addObject(key, 54 + (i*63), 140 + SCREEN_HEIGHT);
    addObject(key, 85 + (i*63), 86 + SCREEN_HEIGHT);
    showText("Click 'Run', then use your keyboard to play", 400, 320 + SCREEN_HEIGHT);
    
  5. Add an instance variable to the Piano class that also constructs a new Visualizer object:
    private Visualizer screen = new Visualizer();
    
  6. Add the Visualizer screen at the end of the Piano constructor:

    addObject(screen, getWidth() / 2, screen.getImage().getHeight() / 2);

  7. Compile and run your scenario to verify the visualizer screen appears above the piano.
  8. Save your updated scenario as we will be adding to it in the next exercise.

When finished please help those around you.

Check Yourself

  1. True or false: we needed to adjust the world size and key positions to add the screen visualizer.
  2. The height of the visualizer screen is ________ pixels.
  3. To center the visualizer screen horizontally, we use the formula __________.
  4. A better solution than adding the same number to several places in a class is to add a __________ variable.
  5. The keyword Java uses to make a variable constant is __________.
  6. True or false: you should use named constants rather than literal numbers in your code so you can easily tell what the number means.
  7. True or false: constants should be written in lower case letters so you cannot easily tell the variable is constant.

8.2.4: Connecting the Visualizer to the Keys

  • We need a method to activate our visualization effects in the Visualizer class
  • Because we may want different effects for different keys, we include a Key parameter:
    /**
     * Play imagery when pressing piano keys.
     *
     * @param k The key that was played.
     */
    public void visualize(Key k)
    {
        // Following is the visualization code
        if (image.getColor().equals(Color.MAGENTA))
        {
            image.setColor(Color.CYAN);
        }
        else
        {
            image.setColor(Color.MAGENTA);
        }
        image.fill();
    }
    
  • The current visualization code simply changes the background color
  • We will discuss more options for visualization code later in the lesson

Changing the Key Class

  • To call the visualize() method, we make some small changes in the Key class
  • We make the changes so we can store a reference to the visualizer screen and access the visualizer
  • First, we add an instance variable of type Visualizer to store the reference:
    private Visualizer screen;
  • Then we add a method to set the value of the instance variable:
    /**
     * Sets a new Visualizer to use.
     *
     * @param visualizer The new Visualizer to use.
     */
    public void setVisualizer(Visualizer visualizer)
    {
        screen = visualizer;
    }
    
  • This methods lets us pass the screen reference from the piano to each key
  • Finally, we add the following code to the play() method:
    if (screen != null)
    {
        screen.visualize(this);
    }
    
  • When we send this to the visualize method, we are sending a reference from this key
  • The visualizer can then "talk to" the key and find out information such as the coordinates of the key

Setting the Visualizer in Piano

  • We need to call the setVisualizer() method from the Piano class
  • In both loops of the makeKeys() method, we add the following code to the middle of the loop:
    key.setVisualizer(screen);
  • Make certain that the key variable is in scope

Drawing the Connections

Let's draw the interconnections between the Piano, Key and Visualizer classes. (diagram)

Check Yourself

  1. The method in the Visualizer class to draw on the visualizer is named __________.
  2. Whenever a key is played, the Key object calls the __________ method of the Visualizer class.
  3. True or false: the special keyword this mean "this object".
  4. True or false: reference variables allow one object to call the public methods of the "referred to" object.

Exercise 8.2: Completing the Visualizer (8m)

In this exercise, we create a visualizer screen for our piano project. Make sure to read the directions carefully. We will be making changes in three classes.

Specifications

  1. Start Greenfoot and open the piano-complete scenario from the previous Activities:
    1. Activity: Creating a Piano Visualizer
    2. Activity: Adding the Visualizer to the Piano
  2. Visualizer Class: Open the editor for the Visualizer class and add the following method for responding to the piano keys:
    /**
     * Play imagery when pressing piano keys.
     *
     * @param k The key that was played.
     */
    public void visualize(Key k)
    {
        // Here is the visualization code
        if (image.getColor().equals(Color.MAGENTA))
        {
            image.setColor(Color.CYAN);
        }
        else
        {
            image.setColor(Color.MAGENTA);
        }
        image.fill();
    }
    
  3. Compile your Visualizer class to verify the code you added is correct.

    If you have problems, ask a guild member or the instructor for help.

  4. Key class: Open the source code editor for the Key class and add an instance variable of type Visualizer:
    private Visualizer screen;
    

    Each Key object needs to "talk to" the visualizer.

  5. Add the following method that sets the value of the instance variable:
    /**
     * Stores a reference to the visualizer.
     *
     * @param visualizer The new Visualizer to use.
     */
    public void setVisualizer(Visualizer visualizer)
    {
        screen = visualizer;
    }
    
  6. Also add the following code to the play() method:
    if (screen != null)
    {
        screen.visualize(this); // talking to the visualizer
    }
    
  7. Compile the Key class to verify your changes.

    If you have problems, ask a guild member or the instructor for help.

  8. Piano Class: Open the source code editor for the Piano class and add the following code to the middle of the loop to both loops of the makeKeys() method:
    key.setVisualizer(screen);

    In the black-key loop, the code goes inside the if-statement.

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

    The screen should change color when the first key is played. If you have problems, ask a guild member or the instructor for help.

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

When finished please help those around you.

8.2.5: Review

  • Music visualizers create imagery based on a piece of music
  • In this lesson we added a visualizer screen to the piano scenario
  • We started by creating a Visualizer class with a GreenfootImage class as the background
  • Within the constructor we made an image and set it for the actor
    public Visualizer()
    {
        image = new GreenfootImage(800, 340);
        image.setColor(Color.DARK_GRAY);
        image.fill();
        setImage(image);
    }
    
  • To add the visualizer, we created space in the Piano class to display images
    public static final int SCREEN_HEIGHT = 340;
    
  • After adjusting for the screen height in several location, we added a Visualizer component to the Piano
    private Visualizer screen = new Visualizer();
    //...
    addObject(screen, getWidth() / 2, screen.getImage().getHeight() / 2);
    
  • To enable visualizations, we created a method in the Visualizer:
    public void visualize(Key k)
    {
        // add visualization code here
    }
    
  • Then we modified the Key class to call the visualize() method:
    private Visualizer screen;
    //...
    if (screen != null)
    {
        screen.visualize(this);
    }
    
  • Step-by-step instructions for adding the visualizer to the piano scenario are listed in the Exercise 8.2

Check Yourself

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

  1. True or false: music visualizers create imagery based on the state of a person's mind. (8.2.1)
  2. The class name used by Greenfoot for constructing images is _______________. (8.2.2)
  3. What does the following code do? (8.2.2)
    image = new GreenfootImage(800, 340);
    image.setColor(Color.LIGHT_GRAY);
    image.fill();
    
  4. True or false: we needed to adjust the world size and key positions to add the screen visualizer. (8.2.3)
  5. The keyword Java uses to make a variable constant is ________. (8.2.3)
  6. Constants should be written in __________ case to make it obvious a variable cannot be changed. (8.2.3)
  7. Two reasons to use a constant: (8.2.3)
    1. ____________________________________________________________
    2. ____________________________________________________________
  8. Whenever a key is played, the Key object calls the _________________ method of the Visualizer class. (8.2.4)

8.3: Drawing Imagery

Learner Outcomes

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

  • Create colors, with and without transparency
  • Draw shapes on a GreenfootImage object

8.3.1: Reviewing Color and Transparency

  • In the last section we added a visualizer screen to the piano scenario:

    piano-with-vis.gfar

  • Now that we have the visualizer screen we need to create the visualization imagery
  • When we create images, we make use of colors as we discussed in lesson 7.4

Creating Colors

  • Remember that display screens use a color model known as RGB (Red, Green, Blue)
  • The red, green and blue values combine to create the overall color:

    RGB colors

  • RGB values are specified using int values from 0 to 255:
    Color(int r, int g, int b)  // 0 - 255
    
  • For example:
    Color chocolate = new Color(204, 102, 0);
    
  • In addition, the Color class has several static color constants that we can use in place of numbers as shown below

Some Color Constants

Color Constant Red, Green, Blue
Color.RED 255, 0, 0
Color.BLUE 0, 0, 255
Color.YELLOW 255, 255, 0
Color.BLACK 0, 0, 0
Color.PINK 255, 175, 175
Color Constant Red, Green, Blue
Color.GREEN 0, 255, 0
Color.MAGENTA 255, 0, 255
Color.CYAN 0, 255, 255
Color.WHITE 255, 255, 255
Color.GRAY 128, 128, 128

Pixel Transparency

  • Every pixel on a screen has two components: color and transparency
  • The transparency component of color is referred to as the alpha value
  • By adjusting the alpha value, background colors show through
  • We can see this effect in the following image
  • The color is less intense and the background checkerboard image becomes more visible as the alpha value decreases

    Effect of transparency

Setting the Transparency (Alpha) Value

  • We can work with the transparency of an image in two ways
  • One way is by using methods of GreenfootImage (see below)
  • Another way is to use the Color class directly
  • When constructing a Color object, we can specify a fourth value: alpha
    Color(int r, int g, int b, int a)  // 0 - 255
    
  • For example:
    Color myColor = new Color(146, 223, 227, 128);
    
  • Alpha values are specified using int values from 0 to 255
  • An alpha value of 0 is completely transparent (invisible) whereas 255 is completely opaque (solid)
  • Values in between 0 and 255 are translucent -- some background shows through

Some GreenfootImage Methods Used for Transparency Control

Method Description
getTransparency() Returns the current transparency setting.
setTransparency(int t) Set the transparency of the drawing color.

Check Yourself

  1. The three color components are: R__________, G__________, B__________.
  2. answer
  3. answer
  4. answer

8.3.2: Drawing Shapes

  • We can draw shapes on a GreenfootImage object
  • First we construct the image object we want to draw upon:
    GreenfootImage img = new GreenfootImage(width, height);
    
  • Then we set the drawing color:
    img.setColor(Color.MAGENTA);
    
  • Once the color is set we can draw shapes using one of the drawing methods listed below

Some GreenfootImage Methods Used for Drawing

Method Description
setColor(someColor) Change the current drawing color.
drawLine(...) Draw a line between two points.
drawOval(...) Draw the outline of an oval.
drawRect(...) Draw the outline of a rectangle.
drawPolygon(...) Draw the outline of a closed polygon defined by arrays of x and y coordinates.
fillOval(...) Draw a solid oval.
fillRect(...) Draw a solid rectangle.
fillPolygon(...) Draw a filled polygon defined by arrays of x and y coordinates.

Examples of Drawing Shapes

  • We can draw on the Visualizer screen using the image variable
  • In the visualize() method we replace the image.fill() method with shapes to draw, like:
    //image.fill();
    image.drawRect(5, 40, 90, 55);
    
  • Here is an example of drawing two lines within a 50 x 50 area
    // Draw lines on 50x50 or larger image
    image.drawLine(0, 0, 49, 49);
    image.drawLine(0, 49, 49, 0);
    
  • Here is an example of drawing rectangles within a 50 x 50 area
    // Draw rectangles on 50x50 or larger image
    image.drawRect(0, 0, 30, 30);
    image.fillRect(20, 20, 49, 49);
    
  • Here is an example of drawing ovals within a 50 x 50 area
    // Draw ovals on 50x50 or larger image
    image.drawOval(0, 0, 30, 30);
    image.fillOval(20, 20, 29, 29);
    
  • Here is an example of drawing polygons (triangles) within a 50 x 50 area
    // Draw triangles on 50x50 or larger image
    int[] x1 = {0, 24, 49};
    int[] y1 = {49, 0, 49};
    image.drawPolygon(x1, y1, x1.length);
    int[] x2 = {0, 49, 24};
    int[] y2 = {0, 0, 49};
    image.fillPolygon(x2, y2, x2.length);
    
  • For polygons like a triangle, we must plot out the (x, y) coordinates of the vertexes for the shape we want to draw

    polygon terms

  • The coordinates of the vertexes are stored in two arrays, one for x and one for y
    int[] x1 = {0, 24, 49};
    int[] y1 = {49, 0, 49};
    image.drawPolygon(x1, y1, x1.length);
    
  • We then pass these arrays, along with their length, to the drawPolygon() method

Activity: Drawing Shapes (3m)

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

    If you did not keep the scenario, then complete Exercise 8.2 now.

  2. Open the editor for the Visualizer class and in the visualize() method replace the code
    image.fill();
    
    with one or more shapes from the above examples like:
    image.drawLine(0, 0, 49, 49);
    image.drawRect(0, 0, 30, 30);
    image.fillOval(20, 20, 29, 29);
    
  3. Compile and run your Piano scenario to ensure it works correctly.

    Every time you press a keyboard key for the Piano, you should see the shapes you coded.

  4. Save your updated scenario as we will be adding to it in the next exercise.

When finished please help those around you and then complete the following Check Yourself questions.

Check Yourself

Given the following image and color, write code to draw a line, oval and rectangle.

GreenfootImage image = new GreenfootImage(50, 50);
image.setColor(Color.MAGENTA);
// Draw a line
image. // example
// Draw an oval
image. // example
// Draw a rectangle
image. // example
setImage(img);

8.3.3: Shape Objects

  • Rather than drawing on the Visualizer screen directly, we should draw on a subclass of Actor
  • Then we place the Actor subclass on top of the screen area
  • Using an Actor subclass lets us move and rotate the shapes we draw
  • As an example, we will draw filled ovals
  • We start by creating a new subclass of Actor named Oval
  • Do not assign an image to the actor as we will create and set our own
  • We create our image in the Oval class constructor as shown below
  • Note that by default a GreenfootImage has a transparent background

Constructor for Class Oval that Draws a Shape

/**
 * Oval Constructor
 *
 * @param width  The width of the oval.
 * @param height  The height of the oval.
 * @param color  The color of the oval.
 */
public Oval(int width, int height, Color color)
{
    GreenfootImage image = new GreenfootImage(width, height);
    image.setColor(color);
    image.fillOval(0, 0, width, height);
    setImage(image);
}

Testing the Shape Classes

  • We can test our shape class by right clicking the Oval class in the inheritance hierarchy

    creating an oval

  • When specifying the color we must enter the class name
  • For example, to specify magenta we type the package along with the class name and constant
    Color.MAGENTA
  • Similarly, to specify the color pink we type
    new Color(255, 175, 175)

Overloading the Constructor

  • Testing with multiple parameters is somewhat tedious
  • To make the process easier, we typically add a default constructor
  • We discussed multiple constructors and the default constructor in lesson 7.4.5
  • A default constructor assigns default values when instantiating (constructing) an object
  • We need a width, height and color when we create an Oval object
  • Thus we choose appropriate default values and assign them through constructor chaining
  • Constructor chaining is where one constructor calls another constructor of the same class
  • To call another constructor, we use the keyword this() with parenthesis
  • Inside the parenthesis we supply an argument of the type needed for another constructor like:
    public Oval()
    {
        this(50, 25, Color.BLUE);
    }
    

Activity: Shape Classes (5m)

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

    If you did not keep the scenario, then complete Activity: Drawing Shapes now.

  2. Make a shape class by creating a subclass of Actor named Oval

    For "Scenario images" choose "No image" as we will draw our own image.

  3. Open the editor for the Oval class.
  4. Add the following constructor to the Oval class.
    /**
     * Oval Constructor
     *
     * @param width  The width of the oval.
     * @param height  The height of the oval.
     * @param color  The color of the oval.
     */
    public Oval(int width, int height, Color color)
    {
        GreenfootImage image = new GreenfootImage(width, height);
        image.setColor(color);
        image.fillOval(0, 0, width, height);
        setImage(image);
    }
    
  5. Compile your Oval class to verify the code you added is correct.

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

  6. Test your Oval class by right-clicking the Oval class in the inheritance hierarchy and entering the information described above under Testing the Shape Class.
  7. Add a second default constructor to the Oval class that calls the first constructor with appropriate default values.
  8. Save your updated scenario as we will be adding to it in the next Try It exercise.

When finished please help those around you.

Check Yourself

  1. To construct an image with a size of width x height, write ________
  2. True or false: by default, a GreenfootImage has a transparent background.
  3. The fully qualified name for Color.BLUE is ________.

8.3.4: Drawing for Each Key

  • So far our shape code is the same no matter which key we press
  • We can change the visualization by utilizing the parameter of the visualize() method
    public void visualize(Key k)
  • Recall the play method from the Key class
    public void play()
    {
        Greenfoot.playSound(sound);
        if (screen != null)
        {
            screen.visualize(this);
        }
    }
    
  • Every time we press a piano key, we code an argument of this to send a reference of the key that is played
  • In the visualize() method we can examine the Key reference to make decisions on the visualization
  • If we right-click on a key, we see several get-methods inherited from Actor that may provide useful information like:
    • getImage(): returns the image of the key
    • getX(): returns the center of the key position
  • The GreenfootImage from getImage() has other methods that may be useful like:
    • getWidth(): returns the width of the key image

Example Shapes to Draw

  • To get started we will draw ovals above the keys as they are played
  • The ovals will be half as high as they are wide
  • To determine the width of the key, we use the following code:
    int width = k.getImage().getWidth();
  • Finding the x-coordinate of the key is straightforward:
    int x = k.getX();
  • To pick a random location above the key to place an oval, we get the height of the screen image and subtract the height of an oval

    int drawHeight = image.getHeight() - width / 2;
    int y = Greenfoot.getRandomNumber(drawHeight) + width / 4;

  • With this information, we set a color, create the shape and add it to the world
  • We can see the complete visualize() method below

Drawing Imagery

public void visualize(Key k)
{
    int width = k.getImage().getWidth();
    int x = k.getX();
    int drawHeight = image.getHeight() - width / 2;
    int y = Greenfoot.getRandomNumber(drawHeight) + width / 4;
    Color color = new Color(255, 0, 255, 128);
    Oval shape = new Oval(width, width / 2, color);
    getWorld().addObject(shape, x, y);
}

Activity: Positioning Shapes (3m)

  1. Start Greenfoot and open the Piano scenario from the last activity.

    If you did not keep the scenario, then complete Activity: Shape Classes now.

  2. Open the editor for the Visualizer class and change the visualize() method as follows:
    public void visualize(Key k)
    {
        int width = k.getImage().getWidth();
        int x = k.getX();
        int drawHeight = image.getHeight() - width / 2;
        int y = Greenfoot.getRandomNumber(drawHeight) + width / 4;
        Color color = new Color(255, 0, 255, 128);
        Oval shape = new Oval(width, width / 2, color);
        getWorld().addObject(shape, x, y);
    }
    
  3. Save your updated scenario as we will be adding to it in the next Try It exercise.

When finished please help those around you.

Check Yourself

  1. White and black keys have different widths, which we get by calling the key's image method __________.
  2. We can get the x-coordinate of a Key object named k by calling its method __________.
  3. In the following code, the purpose of the number 128 is to set the color's ________.
    new Color(255, 0, 255, 128)

8.3.5: Mapping Colors to Keys

  • One improvement we can make is to map a particular color to a key
  • For instance, we might want to play a blue color for one key and a green color for another
  • To map colors to keys, we add a Color instance variable to the Key class:
    private Color keyColor;
    

Assigning Colors for Every Key

  • Inside the constructor of the Key class we can assign a color to the instance variable keyColor
  • We may choose to assign random colors in the constructor like:
    int red = Greenfoot.getRandomNumber(200) + 55;
    int green = Greenfoot.getRandomNumber(200) + 55;
    int blue = Greenfoot.getRandomNumber(200) + 55;
    keyColor = new Color(red, green, blue, 100);
    
  • With a unique color for every key, we need a way to ask each key it's color
  • If we have a reference, how do we talk to an object like a key?

Accessor Methods

  • To get the value of a key's color, we add a getColor() method to the Key class
    public Color getColor()
    {
        return keyColor;
    }
    
  • The getColor() method is an accessor method - a method that returns the value of a variable of the object
  • By calling this method we can get the color for any key object
  • For example, in the visualize() method of our Visualizer class instead of:
    Color color = new Color(255, 0, 255, 128);
    
  • We could write:
    Color color = k.getColor(); // k is the Key parameter
    

Mutator Methods

  • We may want to set the value of a key's color from outside the object
  • To set the value of a shape color, we add a setColor() method to the Key class
    public void setColor(Color newColor)
    {
        keyColor = newColor;
    }
    
  • The setColor() method is known as a mutator method - a method that modifies a field of the object
  • By calling this method we can set a different color for every key
  • For example, in our Piano class we could add a value of Color.WHITE to all the white keys:
    key.setColor(Color.WHITE);
    

Specify Colors for Every Key

  • Usually, we will want to specify a different color for every key
  • To allow us to choose the color for every key we create an array
  • For example, we can define an array of 12 colors, one for each white key:
    private Color[] whiteKeyColors =
        {
            new Color(230, 101, 166, 128),
            // other colors omitted
            new Color(102, 255, 153, 128)
        };
    
  • Then we modify our call to the setColor() method like:
    key.setColor(whiteKeyColors[i]);
  • We can do something similar for the black keys as well
  • As another option, we can randomly choose from a list of colors
    int index = Greenfoot.getRandomNumber(12);
    key.setColor(whiteKeyColors[index]);

Check Yourself

  1. A(n) ________ method changes the value of a class variable.
  2. A(n) ________ method returns the value class variable without changing the value.
  3. To store a specific but different value for every key, we use a(n) ________.

8.3.6: Fading Away

  • So far we have not used the act() method of the Oval class
  • We can use the act() method to move and rotate the shape over several game cycles
  • Another use of the act() method is to implement a fading effect
  • We can change the intensity of an image by adjusting its transparency
  • By reducing the transparency over time, we can make our images fade away
  • The following code shows one way to implement the fading effect
  • Let us look at each line of code and describe what it does

Adding a Fading Effect

public void act()
{
    int alpha = getImage().getTransparency();
    if (alpha > 2)
    {
        getImage().setTransparency(alpha - 2);
    }
    else
    {
        getWorld().removeObject(this);
    }
}

Check Yourself

  1. True or false: you can implement a fading effect by increasing the transparency over time.
  2. True or false: in the above code the purpose of the test condition (alpha > 2) is to detect when to remove the object from the scenario.
  3. In the above code, the fading effect is produced by the ________ line of code
    1. int alpha = getImage().getTransparency();
    2. if (alpha > 2)
    3. getImage().setTransparency(alpha - 2);
    4. getWorld().removeObject(this);

Exercise 8.3: Finishing our Drawing (10m)

In this exercise, we finish our visualizer. Make sure to read the specifications carefully. We will be making changes in three classes.

Specifications

  1. Start Greenfoot and open the Piano scenario from the Activity: Positioning Shapes.

    If you have not completed the Activity, do so now.

  2. Key class: Open the editor for the Key class.
  3. Add a Color instance variable to the Key class:
    private Color keyColor = Color.CYAN;
    
  4. To set a color associated with the key, add a setColor() mutator method to the Key class:
    public void setColor(Color newColor)
    {
        keyColor = newColor;
    }
    
  5. To get the value of the associated color, add a getColor() accessor method to the Key class:
    public Color getColor()
    {
        return keyColor;
    }
    
  6. Compile your Key class to verify the code you added is correct.

    For more information on creating an Actor see lesson: 8.3.5: Mapping Colors to Keys. If you have problems, ask a classmate or the instructor for help as needed.

  7. Visualizer class: Open the editor for the Visualizer class and locate the visualize() method.
  8. In the visualize() method, change the following line from:
    Color color = new Color(255, 0, 255, 128);
    
    to:
    Color color = k.getColor();
    
  9. Piano class: Open the editor for the Piano class and add the following instance variable:
    private Color[] whiteKeyColors =
        {
            new Color(230, 101, 166, 128),
            new Color(198, 227, 171, 128),
            new Color(146, 223, 227, 128),
            new Color(229, 156, 215, 128),
            new Color(51, 255, 0, 128),
            // ... add other colors here (12 total for the array)
            new Color(102, 255, 153, 128)
        };
    

    Add additional colors until you have 12 total colors, one for each white piano key. You can use the Color Chart of RGB Triplets from the textbook to choose colors. Also, you may use the same color more than once. Once you have added the colors, compile the Piano class to verify there are no errors.

  10. In the makeKeys() method Piano class, at the end of the loop that makes white keys, add the following statement:
    key.setColor(whiteKeyColors[i]);
    
  11. Compile your Piano class and verify there are no errors.

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

  12. Inside the if-statement of the loop for making black piano keys, add random colors using the following code:
    int red = Greenfoot.getRandomNumber(200) + 55;
    int green = Greenfoot.getRandomNumber(200) + 55;
    int blue = Greenfoot.getRandomNumber(200) + 55;
    key.setColor(new Color(red, green, blue, 128));
    

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

  13. 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.

  14. Oval class: In the Oval class, replace the act() method with the following code to implement fading:
    public void act()
    {
        int alpha = getImage().getTransparency();
        if (alpha > 2)
        {
            getImage().setTransparency(alpha - 2);
        }
        else
        {
            getWorld().removeObject(this);
        }
    }
    

    In addition, experiment with adding movement and rotation to the act() method as time permits.

  15. 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.

  16. Save a copy of your completed piano scenario to add to in the next quest. Do NOT turn in for the lab, only the quest.

When finished please help those around you.

8.3.7: Review

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

  1. True or false: Most screens use a color model known as RYB (Red, Yellow, Blue). (8.3.1)
  2. Given the following code: (8.3.1)
    GreenfootImage image = new GreenfootImage(800, 340);
    image.setColor(new Color(255, 0, 255));
    
  3. True or false: Background colors show through more as the alpha value decreases. (8.3.2)
  4. True or false: A good way to draw shapes in a project is to use a subclass of Actor. (8.3.3)
  5. (8.3.4)
  6. (8.3.4)

Wrap Up

Due Next:
Q7: Extending Bubbles (10/19/17)
Lab 8: Newton's Lab (10/24/17)
Q8: Visualize this! (10/26/17)
Lab 9: Asteroids (10/31/17)
Q9: Gravitational Attraction (11/2/17)
  • When class is over, please shut down your computer
  • You may complete unfinished lesson exercises at any time before the due date.
Home | Canvas | Schedule | Syllabus | Room Policies
Help | FAQ's | HowTo's | Links
Last Updated: November 04 2017 @22:28:24