|
|
Anatomy of an Animation
|
by M. Gallant 6/12/97
The twister Java applet portrays a twister meandering slowly
across a pastoral scene with random shaped and timed lightning
strikes in the background. Threatening background sky flicker adds
to the atmosphere as well as debris tossed up by the twister's tip.
The animation consists of 5 elements:
- 4 images of the twister with transparent background
- 1 background landscape image with transparent sky
- the background color of the applet (sky region)
- on-the-fly randomly generated lightning strikes
- random dirt generation at the bottom tip of the twister
The applet implements the Runnable interface for the animation
to run in a thread. Four color instance variables are defined:
- bgnd -- main background sky color
- flash -- brightened background sky color
- colorlight -- lightning color
- dirt -- debris of tornado color
Applet instance variables xtor, ytor are the
instantaneous position coordinates of the upper-left corner of
the tornado image component relative to the upper left corner of
the applet frame. Similarly, xlight, ylight are the starting coordinates
of the lightning streak.
The applet requires 5 gif images to be
loaded, 4 variations of the twister image (tor0.gif, tor1.gif...)
and the background landscape (hillst.gif) as declared in the init() method.
init() method
The init method is called as soon as the applet has loaded but
before the actual thread starts with the start() method. init()
defines several instance variables and sets up memory for double-buffering
graphics with the offIm and offGr objects. The
MediaTracker object tracker is then defined and used to "register"
all the image components needed in the animation. The 4 images of the
twister are also assigned to the array tor[i] which however
does not force the images to network load yet.
run() method
Control of the animation is contained in the run() method which
starts after the init() method completes. The images are loaded here
and then the infinite loop starts with while (true) {.
The boolean variable lightning is randomly set to true about
every 20 times through the loop, and determines if a lightning strike
is to be drawn in the paint() method. The next loop moves the twister smoothly horizontally
and vertically via a slow sine wave and runs through the 4 twister
images, with the current twister image to paint specified by curIm.
The repaint() method is then called which in turn calls the update() method
which is overridden to call the paint() method
writing the image to the screen via double-buffering. Note that lightning
is held true for the 4 twister images, providing greater image persistence
of the lightning following which lightning is set false.
paint() method
The rendering of each frame of the animation is specified in
the paint() method. The first conditional test:
if ((tracker.statusID(0, false) == MediaTracker.COMPLETE)&&(curIm!=null))
enables graphics updating only if all images have been (network) loaded as monitored
by the MediaTracker object tracker failing which only the background
color of the applet is drawn.
To prevent screen flicker, double-buffering
is used (with offIm and offGr being the memory image and graphics
context respectively). The sequence of image formation is:
- randomly cause a background sky flicker using if (Math.random() < .02)
which slightly brightens the background sky about every 50 frames
and fill the entire background of the applet with the sky color
using offGr.fillRect(0, 0, w, h)
- draw the landscape with offGr.drawImage(hills,0,h-40,this)
- draw 10 dirt particles randomly, close to the bottom of the twister
- draw a line-segmented lightning strike from 12 pieces with a jagged
shape and store the shape of the lightning
- the lightning strike is repeated for the 4 twister images with a
slight spatial dither, 4*Math.random(), on the previously
stored shape to look more realistic and for image persistence
- draw one of the 4 twister images
offGr.drawImage(curIm,xtor,ytor,this); since it is
the last item drawn, it will be in the overall image foreground.
- since all the draws above were to the offscreen image (offGr), render the
memory image to the applet screen area with g.drawImage(offIm,0,0,this)
As always, experimentation with animations is often effective. Frequently,
very surprising and pleasing visual results are obtained by trial and error.
For more complicated animations, it is better to define separate threads
for processes that are considered independent events. Otherwise, the code
becomes too complex with tests, polling etc. Above all,
GET CREATIVE !