Animation

advertisement
Animation
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
• 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
• In the HTML file,
– <applet code="MyApplet.class" width="250" height="60">
<param name="delay" value="50">
<param name="version" value="Version 1.0">
</applet>
• In the Applet code:
– String version = getParameter("version");
– int delay =
Integer.valueOf(getParameter("delay")).intValue();
• Bounce Applet
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
• 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
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()
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()
• 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
•
•
•
Remember, you write paint but you call repaint
Applets inherit an update(Graphics g) method
When you call repaint(), Java really calls
•
What the inherited version of update does is:
update(Graphics g)
1. Erases the Applet (set it to the background color)
2. 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)
public void update(Graphics g) {
// Erase anything that needs erasing, then...
paint(g);
}
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)
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 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
• 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
• 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
• 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
void loadImages() {
// Create a MediaTracker
MediaTracker tracker =
new MediaTracker(this);
// Declare variables for the URLs
URL appletLocation = getCodeBase();
URL imageURL = null;
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
•
// 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
• 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
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
• 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
• 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
• 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
Download