Comp310 - Basic Animations. Basic Animations in Java COMP 310 Java Resources Eclipse Resources The page covers issues that arise when doing animations in Java. See also: Java GUI Programming Primer The mystery of "repaint" When the repaint() method of a Java GUI component is called the screen does NOT repaint immediately. The Java GUI system runs on its own thread, by queuing event requests and an executing them one at a time. The repaint() method of a Java GUI component simply puts a repaint request into the queue which is then processed whenever the system gets around to it. If the system is very busy doing other things, then it may be some time before the repaint request gets handled. If the repaint requests are being regularly submitted, they may even get backed up in the system, waiting to be processed. When the repaint request finally gets serviced, the Java GUI system will call the paintComponent method each visual component (e.g. panel, button, frame, etc) that is being repainted. The Java GUI system, being in charge of the screen, will hand the paintComponent method a Graphics object onto which painting is performed. This Graphics object is then rendered onto the screen by the Java GUI system. NEVER CALL paintComponent DIRECTLY FROM YOUR OWN CODE. Likewise, NEVER INSTANTIATE A Graphics OBJECT--the Java GUI system will automatically hand your code the Graphics object that it needs. Painting a Shape The Graphics class provides several method that can be used to draw various geometric shapes. One usually has to specify the upper left corner of the "bounding box" that forms the rectangular extent of the desired shape. The width and height of the bounding box are often required as well. For instance, to paint a blue circle centered at a given location, you need to write the following code:
private java.awt.Color color = java.awt.Color.BLUE; // The color of the ball
private java.awt.Point location = new java.awt.Point(23, 84); // the location of the center of the ball
private int radius = 10; // the radius of the ball
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint whatever normally gets painted.
g.setColor (color); // set the color to paint with
g.fillOval (location.x-radius, location.y-radius, 2*radius, 2*radius); // paint a circle onto the graphics object centered on location and with defined radius.
}
Note that the above is not the code that one would write to paint multiple sprites (balls) from a the repainting of a single panel (canvas). While the same operations would exist, they would be distributed to the individual sprites so that each sprite could paint itself with its own color, position and size. See the discussions below on dispatching and call-backs. For those so inclined, here's some links to discussions of the full gory details of the painting process: http://www.oracle.com/technetwork/java/painting-140037.html and http://javadevwannabe.blogspot.com/2012/02/painting-swing-components.html Setting up the timer Performing an animation is really nothing more than regularly asking the system to repaint and then having the system paint something slightly different each time. To regularly schedule a repaint event, a timer is used. Since the Model drives the View, the timer should be in the Model! IMPORTANT NOTE: Java has TWO different Timer classes! javax.swing.Timer -- This timer queues events that run on the GUI thread. THIS IS THE ONE YOU WANT FOR ANIMATIONS. java.util.Timer -- This timer queues events that run on its own background thread. This timer will not work for animation processes, but is the one you would use for non-GUI calculations, such as long mathematical computations. First, let us assume that there is an adapter that connects the Model to the View:
/**
* Interface that goes from the model to the view that enables the model to talk to the view
*/
public interface IModel2ViewAdapter {
/**
* The method that tells the view to update
*/
public void update();
/**
* No-op "null" adapter
* See the web page on the Null Object Design Pattern at http://cnx.org/content/m17227/latest/
*/
public static final IModel2ViewAdapter NULL_OBJECT = new IModel2ViewAdapter() {
public void update() {
}
};
}
The Model class, BallModel here, therefore looks something like this:
public class BallModel {
private IModel2ViewAdapter _model2ViewAdpt = IModel2ViewAdapter.NULL_OBJECT; // Insures that the adapter is always valid
public BallModel(IModel2ViewAdapter model2ViewAdpt ) {
_model2ViewAdpt = model2ViewAdpt;
}
// rest of Model code elided
}
The View class, BallGUI here, is assumed to have some sort of update method that would be called by the update method the actual, concrete IModel2ViewAdapter instance being used by the Model:
public class BallGUI extends JFrame {
// constructor and fields elided
/**
* Updates the view by repainting the canvas
*/
public void update() {
_pnlCanvas.repaint();
}
}
From the Model's perspective, the all the timer does is to tell the View to update. On the View's side, that call to update is translated into the reapinting of the canvas (see the code above). The code to use the timer to regularly update the View is thus very simple: Old style using anonymous inner class:
private int _timeSlice = 50; // update every 50 milliseconds
private Timer _timer = new Timer(_timeSlice, new ActionListener() {
/**
* The timer "ticks" by calling this method every _timeslice milliseconds
*/
public void actionPerformed (ActionEvent e) {
_model2ViewAdpt.update();
}
});
New style (Java 8+) using lambda expression (lambda function) where the compiler does all the work of making the above anonymous inner class:
private int _timeSlice = 50; // update every 50 milliseconds
private Timer _timer = new Timer(_timeSlice, (e) -> _model2ViewAdpt.update());
To start the timer: _timer.start(); To stop the timer: _timer.stop(); Question: Why is the timer in the model and not the view? Using adapters as call-backs for painting sprites Since the code for the sprites live in the model, not in the view, the code to paint a sprite therefore exists in the model side of the system. In general, the view (frame, etc) will have no idea of how many or what type of sprites are being displayed. However, since the (re)painting is an event queued on the GUI thread, there needs to be a mechanism for the view (the frame or component in the frame) to communicate back to the model to paint the sprite(s) at the appropriate time. In classical programming terms, this is accomplished by providing a call-back function (a particular usage of a lambda function) to the view that is called when the appropriate component is actually repainted. This function will paint the sprite. A special component, e.g. a JPanel, is created with an overridden paintComponent(Graphics g) method, which is the method called by the repaint event when it is executed. The supplied call-back painting function is invoked in this overridden paintComponent method. Since we are using a Model-View-Controller architecture, the painting call-back command is just one of the methods on the adapter interface that connects the view back to the model. An instance of this adapter interface is given to the GUI at construction time. Conversely, as described above, the call-back/lambda function is just an adapter interface with only one method. Example code:
public class BallGUI extends JFrame {
// The model adapter is initialized to a no-op to insure that system always has well-defined behavior
private IView2ModelAdapter _view2ModelAdpt = IView2ModelAdapter.NULL_OBJECT;
// Create a special panel with an overridden paintComponent method.
private JPanel _pnlCanvas = new JPanel() {
private static final long serialVersionUID = 2;
public void paintComponent(Graphics g) {
super.paintComponent(g); // clear the panel and redo the background
_view2ModelAdpt.paint(g); // call back to the model to paint the sprites
}
};
/**
* Constructor is supplied with an instance of the model adapter.
*/
public BallGUI(IView2ModelAdapter view2ModelAdpt ) {
_view2ModelAdpt = view2ModelAdpt ;
...
}
...
} Where the command interface is given by a method in the adapter the View uses to access the Model:
/**
* The interface of the adapter from the view to the model that enables the view to talk to the model.
*/
public interface IView2ModelAdapter {
public void paint(Graphics g);
// other methods elided...
/**
* No-op singleton implementation of IView2ModelAdapter
* See the web page on the Null Object Design Pattern at http://cnx.org/content/m17227/latest/
*/
public static final IView2ModelAdapter NULL_OBJECT = new IView2ModelAdapter () {
public void paint(Graphics g) {
}
// other methods similarly coded for no-op
};
} A concrete implementation of the IView2ModelAdapter's paint() method would call a specific method on the Model to paint whatever the Model wishes to paint, such as the update() method described below. Note: The terms “call-back” and “lambda” because these are classical terms that were used to describe the interactions we will be using to communicate between the view and the model. Since the advent of object-oriented programming and design patterns, these terms have been supplanted in this situation with the MVC terminologies. The larger notion of the lambda function will be explored in much greater depth as the course progresses. ==================================== The following code refers to the Ballworld provided code. To obtain the code, see the semester-specific instructions in Canvas. For semester-independent, general instructions on attaching external libraries to your project, see Using svn:externals to Access a Shared Code Library You may also wish to review Java generics. ==================================== Dispatching to paint sprites, etc. To tell all the sprites to do something, e.g. to move and paint, the Observer-Observable Design Pattern is used. Here, all the sprites are "observers" that are notified whenever a single, central "observable" is notified. Since the sprites are part of the Model, the dispatching takes place in the Model! The view simply calls back to the model through its adapter (see above) when the painting of sprites is needed. Java supplies classes to support the Observer-Observable pattern, but a little modification helps them be more user-friendly and the addition of generics enables us to guarantee more type-safety. We will also change the name to "dispatcher" instead of "observable" because the official design pattern "observable" does not automatically delegate to all its observers, which, for convenience, our dispatcher will do. The fundamental concepts of the observer-observable pattern still hold however.
// From the provided.util.dispatcher package
/**
* A generic Observable interface that immediately notifies its IObservers when its notifyAll() method is called. The changed state of the Dispatcher does not need to be separately set.
*/
public class IDispatcher {
/**
* Immediately updates all the IObservers held.
* @param msg An input parameter that is passed on to all the IObservers.
*/
public void updateAll(TMsg msg);
} For this beginning-level animation discussion, the generic TMsg parameter will the Graphics class. In later work, this will change. There are many ways to implement an IDispatcher. The provided library provides two different possibilities. For now, use the provided.util.dispatcher.impl.SequentialDispatcher implemention class. (Do try the parallel dispatching class at some point however!) IDispatcher myDispatcher = new SequentialDispatcher(); Question: Why is the myDispatcher variable typed to the IDispatcher interface and not to the specific implementing class? So long as all the sprites implement the provided.util.dispatcher.IObserver interface (note: the generic TMsg parameter is set to Graphics here), then they can be added to an IDispatcher instance and notified whenever the Dispatcher's notifyAll method is called. Specifically, the sprites must all implement the following method:
public class MySprite implements IObservable {
/**
* The update method that the dispatcher's updateAll method will call.
* @param disp The calling dispatcher
* @param g The input parameter given to the dispatcher's updateAll() method, here a Graphics object.
*/
public void update(IDispatcher disp, Graphics g) { ...}
} Here, the parameter passed during the updating is the Graphics object onto which the sprite will paint itself. Note also that the update method of the sprite is not limited to simply painting itself. The update method may do many other things, such as changing the position (and hence creating animated motion) or other characteristics of the sprite. To add a sprite to the IDispatcher, it's addObserver method is used: myDispatcher.addObserver(aSprite); To call the update method of every sprite that was added to the dispatcher, simply call its updateAll method with the desired input parameter, e.g. below is a method of the model that is used to update the painted positions of all the sprites:
public class BallModel {
private IDispatcher myDispatcher = new SequentialDispatcher();
// Constructor and other fields elided
// Methods to add sprites (Observers) to the dispatcher elided.
/**
* This is the method that is called by the view's adapter to the model, i.e. is called by IView2ModelAdapter.paint().
* This method will update the sprites's painted locations by painting all the sprites
* onto the given Graphics object.
* @param g The Graphics object from the view's paintComponent() call.
*/
public void update(Graphics g) {
myDispatcher.updateAll(g); // The Graphics object is being given to all the sprites (Observers)
}
} Question: If the BallModel were to hold references to sprites, what class or interface type would those sprite variables be? Why? -- Careful, this is tougher question than it may seem! Animation Overview Putting everything above together, we see the following process Food for thought: Looking at the design of the model above, what does it seem to say about themodel portion of the system and the number of decoupled pieces into which it should be separated? And as a result, are the also implications on the number of adapters needed to connect from the model to the view? Does anything about the Model-View-Controller Design Pattern dictate how many pieces comprise either the model or view sides? Is there even a notion of a "side" here? Cross-thread invocations In general, Java will not let a GUI component be modified from a thread that is not the GUI thread. Doing so will generate a "cross thread invocation" error at run-time. This means that all operations that affect the GUI must be transferred from the computation thread to the GUI thread before they are executed. This means turning the operation into an event that can be queued into the GUI event handling queue. This is not particularly difficult to accomplish but can be a bit tedious and on has to be very careful to be diligent about doing it. Most of our animation code runs on the GUI thread, so there won't be much problem here. This will become a major issue when we start working with networking code however! For more information, see the Comp310 Java Resources page on "Cross-Thread Invocations onto the GUI Thread". © 2020 by Stephen Wong