Animation : Animation
How animation is done : How animation is done The illusion of movement is created by displaying a series of images, each slightly different
The eye fills in the gaps
More images per second makes smoother animation
12 images per second is sometimes “good enough”
20 images per second (one every 50 ms) is quite good
The computer has to work to compute each new image
For complex images, such as most visually exciting computer games, this is an extremely difficult problem
For simple animations, modern computers generally don’t have any problems
That is, if you don’t do anything too stupid ?
Tweaking : Tweaking When developing an animation, you often have several parameters (time, x and y distances, etc.) that you need to experiment with
It’s a nuisance to recompile your program each time
Instead, pass in parameters
You can pass parameters to both applets and applications
For an application, each parameter comes in as a String
public static void main(String args[])
This is just an array of Strings; use it in the obvious way
Passing parameters to applets : Passing parameters to applets In the HTML file,
In the Applet code:
String version = getParameter("version");
int delay = Integer.valueOf(getParameter("delay")).intValue();
Bounce Applet
Model-View-Controller : Model-View-Controller The MVC Design Pattern is usually an excellent choice for doing animations
The Controller handles user input, and controls the Model
The best way to do this is to call methods in the Model
The Model does the computational work; it should be independent of the other classes
The View examines the state of the Model and displays the results on the screen
For a “pure” animation, sometimes the View is in charge, at least to the extent of telling the Model when to make a “step”
Controller.html
Example Model I: Bouncing ball : Example Model I: Bouncing ball class Model { final int BALL_SIZE = 20; int xPosition = 0; int yPosition = 0; int xLimit, yLimit; int xDelta = 4; int yDelta = 3; void makeOneStep( ) { ...}}
Example Model II: Bouncing ball : Example Model II: Bouncing ball void makeOneStep() { xPosition += xDelta; if(xPosition < 0 || xPosition >= xLimit) { xDelta = -xDelta; xPosition += xDelta; } yPosition += yDelta; if(yPosition < 0 || yPosition >= yLimit) { yDelta = -yDelta; yPosition += yDelta; }}
Example: View.run() : Example: View.run() public void run() { // we set "alive" false when applet is destroyed while(alive) { // we set okToRun true if the Applet is started if(okToRun) { model.makeOneStep(); repaint(); } // We use "delay" to control the speed of execution try { Thread.sleep(delay); } catch(InterruptedException e) {} }}
Example: View.paint() : Example: View.paint() public void paint(Graphics g) { // Erase previous ball g.setColor(Color.white); g.fillRect(oldX, oldY, model.BALL_SIZE, model.BALL_SIZE); // Draw new ball and remember its position g.setColor(Color.red); g.fillOval(model.xPosition, model.yPosition, model.BALL_SIZE, model.BALL_SIZE); oldX = model.xPosition; oldY = model.yPosition;}
How repainting is actually done : How repainting is actually done Remember, you write paint but you call repaint
Applets inherit an update(Graphics g) method
When you call repaint(), Java really calls update(Graphics g)
What the inherited version of update does is:
Erases the Applet (set it to the background color)
Call your paint(Graphics g) method
Your gets a nice, clean background to draw on, but erasing and drawing may cause flicker
You can override update(Graphics g) and erase only the part that needs erasing
Example: View.update(Graphics g) : Example: View.update(Graphics g) public void update(Graphics g) { // Erase anything that needs erasing, then... paint(g);}
Backgrounds : Backgrounds To use a background, simply paint it before you paint any objects in the foreground:
void background(Graphics g) { int width = getSize().width; int height = getSize().height; int squareSize = 2 * model.BALL_SIZE; g.setColor(Color.blue); g.fillRect(0, 0, width, height); g.setColor(Color.green); for (int x = 0; x < width; x += 2 * squareSize) { for (int y = 0; y < height; y += 2 * squareSize) { g.fillRect(x, y, squareSize, squareSize); g.fillRect(x + squareSize, y + squareSize, squareSize, squareSize); } }}
Example: View.update(Graphics g) : Example: View.update(Graphics g) public void update(Graphics g) { paintBackground(g); paint(g);}
public void paint(Graphics g) {// Draw new ball g.setColor(Color.red); g.fillOval(model.xPosition, model.yPosition model.BALL_SIZE, model.BALL_SIZE);}
Controller Applet
Flicker, again : Flicker, again Flicker occurs because you are trying to change the display at the same time your computer is trying to show you the display
The secret is to get the display ready first, then give the computer the new, completed display
Instead of painting onto the same Graphics g that the computer is using,
Create a new, “offscreen” Graphics
Do your work there
Move it to g when it's all painted and ready to be seen
This technique is called double buffering
Double buffering : Double buffering public void update(Graphics g) { Image offscreenImage = null; Graphics offscreenGraphics = null; // Create the offscreen Graphics if (offscreenImage == null) { offscreenImage = createImage(getSize().width, getSize().height); offscreenGraphics = offscreenImage.getGraphics(); } // Paint into the offscreen Graphics background(offscreenGraphics); paint(offscreenGraphics); // Copy the offscreen onto the screen g.drawImage(offscreenImage, 0, 0, null);}
Controlling flicker
Controls : Controls User controls have to be handled in a separate Thread from the animation
All the usual techniques for working with Threads apply
Adding user controls
Using images : Using images Good news: Displaying images is easy
g.drawImage(controller.skull, model.xPosition, model.yPosition, null);
Bad news: Loading images into memory is slightly complicated
Here’s how:
Create a new MediaTracker object
Get the URLs for your images
Start loading your images
Tell the MediaTracker about each image
Tell the MediaTracker to wait until all images are loaded
Example: loadImages() I : Example: loadImages() I void loadImages() { // Create a MediaTracker MediaTracker tracker = new MediaTracker(this); // Declare variables for the URLs URL appletLocation = getCodeBase(); URL imageURL = null;
Example: loadImages() II : Example: loadImages() II try { // Start loading the graveyard image imageURL = new URL(appletLocation + "/images/boothill-blue.jpg"); graveyard = getImage(imageURL); tracker.addImage(graveyard, 0); // Start loading the skull image imageURL = new URL(appletLocation + "/images/skull-1.gif"); skull = getImage(imageURL); tracker.addImage(skull, 0);}catch (MalformedURLException e) { e.printStackTrace();}
Example: loadImages() III : Example: loadImages() III // Wait for the images to be loaded // (should do error checking, but I didn’t) try { tracker.waitForAll(5000); } catch (InterruptedException e) {}} // end of method
Using Images
Animating individual elements I : Animating individual elements I To make an individual image “do something” we need a series of images
Examples: A bird that flaps its wings, a rotating skull
The more images, the smoother the motion.
There are two ways to animate an individual image:
Use an animated GIF
You only need to load and use the one image
Your program can't control the speed of the animation
Use a series of GIFs or JPGs
You have lots of separate files and images
You have full control over the animation
Animating individual elements II : Animating individual elements II for (int i = 0; i < 25; i++) { skullURL = new URL(appletLocation + "/images/t-skull-" + i + ".gif"); skull[i] = getImage(skullURL); tracker.addImage(skull[i], 0); }
skullNumber = (skullNumber + 1) % 24;g.drawImage(controller.skull[skullNumber], model.xPosition, model.yPosition, null);
Animating elements
Multiple images
Listening to the mouse : Listening to the mouse Your program can detect (and use) mouse events
This doesn’t really have anything to do with animation, but it can be used along with animation
view.addMouseListener(myMouseAdapter);
class myMouseAdapter extends MouseAdapter { public void mousePressed(MouseEvent event) {...} public void mouseReleased(MouseEvent event) {...}}
view.addMouseMotionListener(myMouseDragger);
class myMouseDragger extends MouseMotionAdapter) { public void mouseDragged(MouseEvent event) {...}}
Reacting to mouse clicks : Reacting to mouse clicks public void mousePressed(MouseEvent event) { // Get mouse position int mouseX = event.getX(); int mouseY = event.getY(); // Check whether the mouse is inside the skull if (mouseX >= model.xPosition && mouseX <= model.xPosition +model.skullWidth && mouseY >= model.yPosition && mouseY <= model.yPosition +model.skullHeight) { draggingSkull = true; }}
public void mouseReleased(MouseEvent event) { draggingSkull = false; draggingSkeleton = false;}
Responding to mouse drags : Responding to mouse drags public void mouseDragged(MouseEvent event) { // Get mouse position int mouseX = event.getX(); int mouseY = event.getY(); if (draggingSkull) { // Center skull over mouse position model.xPosition = mouseX – (model.skullWidth / 2); model.yPosition = mouseY - (model.skullHeight / 2);}
Mouse control
The End : The End