FootnotesTopSwitch statementPerforming Animations in Java

Performing Animations in Java

Java includes a construct called threads that allow several different activities to occur concurrently on your computer. While Grace will eventually support threads, our current web implementation does not support threads. (The reason is that we compile to the language javascript - essentially the native language of the web - and it does not support threads.). In this section I will explain how to create threads in Java and use them like we used the Animation library in Grace. You will be happy to know that threads are actually a bit easier to control in Java than they were in Grace, though of course the set up is longer, as always.

Note: In CS 51, we teach students how to use a class ActiveObject of the objectdraw library to avoid some complexities that arise in using the Thread class. We are going to introduce the standard Java Thread class.

When your Java program is started by the startController(...,...) method request, a special thread called the event thread pays begins execution. It executes the code in your begin method and then waits for events to happen in the window. If the user creates a mouse event, interacts with a GUI pattern, or even obscures or uncovers part of the window, the event thread does whatever is appropriate to respond to that event, e.g., repaint the screen, execute the code to respond to the mouse or other event generated, etc. Typically we want the event thread to only do things that it can accomplish very quickly because when it is busy, your program cannot respond to other events or redraw the screen.

As a result if there are long-running tasks that we need the computer to accomplish (like performing animations), we put them in separate threads. To define a class that allows you to run animations you must

  1. Define a class that extends Thread
  2. Write a constructor to initialize any instance variables
  3. Write a run method with signature public void run(). It is very important that it not take any parameters. Any parameters needed for the animation should be passed in to the constructor rather than the run method.
  4. Include as sleep statement wherever you need a delay for the animation (usually inside a loop of some kind). Because the sleep may throw an InterruptedException you must wrap it in a try-catch construct that will catch that exception.

Let's go through each of these with an example. In Grace we created a program to play a rather pathetic game of pong (the ball just fell straight down and it did not interact with the paddle). Here is the code for animating the ball/

type FallingBall = {
   start -> Done
}

class ball.size (ballSize) inside (boundary) moveRange (min,max) 
                       hitBy (paddle) on (canvas)  -> FallingBall {

    def xShift = 0
    def yShift = 8
    
    def pauseTime = 30  // time gap between moves of ball

    method start {
        def theBall = filledOval.at(boundary.location+(1@1)) 
                             size (ballSize,ballSize) on (canvas)
                             
      //  print("ball: {theBall.y}, canvas: {canvas.y}")
        animator.while{theBall.y < canvas.height} pausing (pauseTime) do {
            theBall.moveBy(xShift,yShift)
        } finally {
            theBall.removeFromCanvas
        }

    }
}

We can write the same program in Java by defining a class that extends Thread.

import objectdraw.*;

public class FallingBall extends Thread {
    // ball size and starting location
    private static final int BALLSIZE = 30;
    private static final int TOP = 50;
    private static final int BOTTOM = 600;
    
    // width of the screen
    private static final int SCREENWIDTH = 400;

    // how far the ball should fall in each iteration in loop
    private static final double Y_DISTANCE = 8;

    // delay between moves of the ball
    private static final int DELAY_TIME = 33;

    // image of the ball that will fall
    private FilledOval ball;

    /**
     * Create ball image and start falling
     * @param canvas - canvas where ball image is placed
     */
    public FallingBall(DrawingCanvas canvas) {
        ball = new FilledOval(SCREENWIDTH / 2, TOP, BALLSIZE, BALLSIZE, canvas);
    }

    /**
     *  Slowly move ball down until it is off the screen
     */
    public void run() {
        while (ball.getY() < BOTTOM) {
            ball.move(0, Y_DISTANCE);
            try {
                sleep(DELAY_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        ball.removeFromCanvas();
    }
}

Let's see how we match up with the list of instructions.

  1. Class FallingBall is declared to extend Thread.
  2. The constructor initialized the only instance variable ball.
  3. The run method contains a while loop that drops the ball by Y_DISTANCE and then sleeps for DELAY_TIME. The sleep method does have the (annoying) try-catch wrapped around it to catch the possible InterruptedException. If it does get interrupted we just print out a stack trace that shows where the error occurred.

Because we are just using a regular while loop, there is not finally clause needed. Anything placed after the while loop will be executed when the while loop is done.

Thus we can see that we replaced an animator.whilepausing()do loop in a start method with a regular while loop in a run method, where we get to put the sleep wherever we like in the loop. Don't forget to put a sleep command inside each loop if your want them to slowly move. Otherwise the loop will be complete before the screen gets a chance to refresh!

Finally we need to create and start up the thread. This is essentially the same as in Grace. In Grace we wrote:

      def pongBall: pb.FallingBall = pb.ball.at (point) size (ballSize) inside(boundary) 
                        hitBy (paddle) on (canvas)
      pongBall.start

In Java we write

      FallingBall pongBall = new FallingBall(canvas);
      pongBall.start();

or even just:

       new FallingBall(canvas).start();

if we don't need a name for the ball.

But where did the start() method come from? The only method in the class we defined was run. This is a bit of Java magic created by inheritance. The built-in class Thread has a method start() in it. When your program requests that method, it builds a new executable thread that is different from all of those currently executing in your computer. When it is ready, it will call the run method that you wrote.

This is a mechanism similar to that used by your main program. When you construct an object from a class that extends WindowController, you send it a startController method request. Method startController is defined in WindowController1 and inherited by your program. The method startController sets up the window, places the canvas in the middle, and then calls your begin method.

Aside from these few changes in syntax, the threads in Java are used in ways similar to the animator.while loops in Grace. Because threads are simpler than using timers, we have no need for the finally clause with threads. Anything code after the loop is delayed until after the loop completes, because these are just normal while loops.


FootnotesTopSwitch statementPerforming Animations in Java