130 lines
7.5 KiB
Markdown
130 lines
7.5 KiB
Markdown
#### Masked Images {#Heading4}
|
|
|
|
Masked images are rendered by drawing an object's pixels through a mask;
|
|
pixels are actually drawn only where the mask specifies that drawing is
|
|
allowed. This makes it possible to draw nonrectangular objects that
|
|
don't improperly interfere with one another when they overlap. Masked
|
|
images also make it possible to have transparent areas (windows) within
|
|
objects. Masked images produce far more realistic animation than do
|
|
rectangular images, and therefore are more desirable. Unfortunately,
|
|
masked images are also considerably slower to draw—however, a good
|
|
assembly language implementation can go a long way toward making masked
|
|
images draw rapidly enough, as illustrated by this chapter's code.
|
|
(Masked images are also known as *sprites*; some video hardware supports
|
|
sprites directly, but on the PC it's necessary to handle sprites in
|
|
software.)
|
|
|
|
Masked images make it possible to render scenes so that a given image
|
|
convincingly appears to be in front of or behind other images; that is,
|
|
so images are displayed in *z-order* (by distance). By consistently
|
|
drawing images that are supposed to be farther away before drawing
|
|
nearer images, the nearer images will appear in front of the other
|
|
images, and because masked images draw only precisely the correct pixels
|
|
(as opposed to blank pixels in the bounding rectangle), there's no
|
|
interference between overlapping images to destroy the illusion.
|
|
|
|
In this chapter, I've used the approach of having separate, paired masks
|
|
and images. Another, quite different approach to masking is to specify a
|
|
transparent color for copying, and copy only those pixels that are not
|
|
the transparent color. This has the advantage of not requiring separate
|
|
mask data, so it's more compact, and the code to implement this is a
|
|
little less complex than the full masking I've implemented. On the other
|
|
hand, the transparent color approach is less flexible because it makes
|
|
one color undrawable. Also, with a transparent color, it's not possible
|
|
to keep the same base image but use different masks, because the mask
|
|
information is embedded in the image data.
|
|
|
|
#### Internal Animation {#Heading5}
|
|
|
|
I've added another feature essential to producing convincing animation:
|
|
*internal animation*, which is the process of changing the appearance of
|
|
a given object over time, as distinguished from changing only the
|
|
*location* of a given object. Internal animation makes images look
|
|
active and alive. I've implemented the simplest possible form of
|
|
internal animation in Listing 46.1—alternation between two images—but
|
|
even this level of internal animation greatly improves the feel of the
|
|
overall animation. You could easily increase the number of images cycled
|
|
through, simply by increasing the value of **InternalAnimateMax** for a
|
|
given entity. You could also implement more complex image-selection
|
|
logic to produce more interesting and less predictable
|
|
internal-animation effects, such as jumping, ducking, running, and the
|
|
like.
|
|
|
|
#### Dirty-Rectangle Management {#Heading6 align="center"}
|
|
|
|
As mentioned above, dirty-rectangle animation makes it possible to
|
|
access display memory a minimum number of times. The previous chapter's
|
|
code didn't do any of that; instead, it copied all portions of every
|
|
dirty rectangle to the screen, regardless of overlap between rectangles.
|
|
The code I've presented in this chapter goes to the other extreme,
|
|
taking great pains never to draw overlapped portions of rectangles more
|
|
than once. This is accomplished by checking for overlap whenever a
|
|
rectangle is to be added to the dirty list. When overlap with an
|
|
existing rectangle is detected, the new rectangle is reduced to between
|
|
zero and four nonoverlapping rectangles. Those rectangles are then again
|
|
considered for addition to the dirty list, and may again be reduced, if
|
|
additional overlap is detected.
|
|
|
|
A good deal of code is required to generate a fully nonoverlapped dirty
|
|
list. Is it worth it? It certainly can be, but in the case of Listing
|
|
46.1, probably not. For one thing, you'd need larger, heavily overlapped
|
|
objects for this approach to pay off big. Besides, this program is
|
|
mostly in C, and spends a lot of time doing things other than actually
|
|
accessing display memory. It also takes a fair amount of time just to
|
|
generate the nonoverlapped list; the overhead of all the looping,
|
|
intersecting, and calling required to generate the list eats up a lot of
|
|
the benefits of accessing display memory less often. Nonetheless, fully
|
|
nonoverlapped drawing can be useful under the right circumstances, and
|
|
I've implemented it in Listing 46.1 so you'll have something to refer to
|
|
should you decide to go this route.
|
|
|
|
There are a couple of additional techniques you might try if you want to
|
|
wring maximum performance out of dirty-rectangle animation. You could
|
|
try coalescing rectangles as you generate the dirty-rectangle list. That
|
|
is, you could detect pairs of rectangles that can be joined together
|
|
into larger rectangles, so that fewer, larger rectangles would have to
|
|
be copied. This would boost the efficiency of the low-level copying
|
|
code, albeit at the cost of some cycles in the dirty-list management
|
|
code.
|
|
|
|
You might also try taking advantage of the natural coherence of animated
|
|
graphics screens. In particular, because the rectangle used to erase an
|
|
image at its old location often overlaps the rectangle within which the
|
|
image resides at its new location, you could just directly generate the
|
|
two or three nonoverlapped rectangles required to copy both the erase
|
|
rectangle and the new-image rectangle for any single moving image. The
|
|
calculation of these rectangles could be very efficient, given that you
|
|
know in advance the direction of motion of your images. Handling this
|
|
particular overlap case would eliminate most overlapped drawing, at a
|
|
minimal cost. You might then decide to ignore overlapped drawing between
|
|
different images, which tends to be both less common and more expensive
|
|
to identify and handle.
|
|
|
|
#### Drawing Order and Visual Quality {#Heading7}
|
|
|
|
A final note on dirty-rectangle animation concerns the quality of the
|
|
displayed screen image. In the last chapter, we simply stuffed dirty
|
|
rectangles into a list in the order they became dirty, and then copied
|
|
all of the rectangles in that same order. Unfortunately, this caused all
|
|
of the erase rectangles to be copied first, followed by all of the
|
|
rectangles of the images at their new locations. Consequently, there was
|
|
a significant delay between the appearance of the erase rectangle for a
|
|
given image and the appearance of the new rectangle. A byproduct was the
|
|
fact that a partially complete—part old, part new—image was visible long
|
|
enough to be noticed. In short, although the pixels ended up correct,
|
|
they were in an intermediate, incorrect state for a sufficient period of
|
|
time to make the animation look wrong.
|
|
|
|
This violated a fundamental rule of animation: *No pixel should ever be
|
|
displayed in a perceptibly incorrect state*. To correct the problem,
|
|
I've sorted the dirty rectangles first by Y coordinate, and secondly by
|
|
X coordinate. This means the screen updates from to draw a given image
|
|
should be drawn nearly simultaneously. Run the code from the last
|
|
chapter and then this chapter; you'll see quite a difference in
|
|
appearance.
|
|
|
|
Avoid the trap of thinking animation is merely a matter of drawing the
|
|
right pixels, one after another. Animation is the art of drawing *the
|
|
right pixels at the right times* so that the eye and brain see what you
|
|
want them to see. Animation is a lot more challenging than merely
|
|
cranking out pixels, and it sure as heck isn't a purely linear process.
|