
11 October 2005
Extended Graphical Templates
for Sprite Management
by Ernest Pazera
What are graphical templates?
Although you may be unfamiliar with the
term, chances are you've seen a graphical template; especially if you’ve
read some of the more popular game programming books.
This is what one looks like:

What’s wrong with this?
At one time, I uses these to manage tilesets.
However, in almost all cases, there wasn’t enough information in the
image, and I’d have to either hardcode the width of the tiles, or save
cell information in another file. Needless to say, it was an unwieldy
solution. What I wanted to do was to be able to provide all of
the information about the sprites in the template itself, such as the
size of the cell, which more often than not did not take up the entire
cell, and centering or reference points so that I could supply my sprite
blitting function with just a (x,y) pair, and have the sprite blit
properly in relation to that point. Also, I wanted to supply
transparency information, and cell size so that I could just load the
tileset, and go.
What I did to fix it …
So, I came up with a way to keep track of all of
this information graphically. The first thing I did was to make the
pixels where the white lines cross black. Actually, it can be any color.
This color is also used as the transparent color key (magenta
(RGB(255,0,255)) is a favorite of mine). What these black(or whatever)
dots allow us to do is determine the width and height of the cells. In
fact, if we really wanted, we could have more than one width or height
within the same tileset… I’d like to see you do that without
a lot of pain in a standard template.
The next thing I added was a reference point (I
call it an "anchor") on the cell walls (the x goes on the top,
the y on the left). This can be used for a center, or to reference where
the foot of a character is, or the base line of a font. With this, you
can just pass an (x,y) pair to your blitting function, and the (x,y)
point will correspond to the anchoring point in the image.
Still, this wasn’t enough for me. Most of the
time, my sprites didn’t take up the entire cell. Depending on your
blitting function, and whether or not you make heavy use of transparent
color keys, many times you either have to blit twice to the same pixel,
or you have to test all of the pixels in the source against the color
key. This isn’t terribly inefficient by any means, but why should we
force ourselves to do this if we don’t have to? So, what I did was I
put a different color to specify the extent of the sprite on the
template walls (x range on the top, y range on the left). This way, my
loader could determine the size, and store it in a RECT, and then use
some code to only blit that RECT.
Of course, this lead to a problem. Sometimes the
reference point is within the width or height, sometimes not. To fix
this, I added one more control color to differentiate between anchors
within the image rect, and outside the image rect.
Here’s an example of an extended template, with
a few sprites from spritelib:

And here’s the same image, blown up to 400%

As you can see, the green sections make up the
rectangles into which the sprites fit. The blue dots are the y
reference, which is one pixel below the feet of the characters, and the
cyan dots are the centering reference dots for x. The reason that the y
anchors are one pixel below the feet of the characters is so that I can
align the sprites on the topmost pixel of whatever they are standing on.
In the upper right corner, along the right side,
there are a series of 5 dots. These are for the control colors, so that
you can use whatever colors you like.
How to load in images that use the extended
template ...
Initially upon loading, you have to load in the
control colors from the rightmost pixel column. I use the order
Transparent, Border, Outside Anchor, Inside, Inside Anchor. This is just
a personal preference, order-wise.
Next, you scan the top row, and leftmost column of
pixels, and parse out the cell widths and heights.
After that, you scan the top and left wall of each
cell for the anchors and sprite rectangles. Then you’re ready to go.
The coordinates for the RECTs and anchors should be in relation to the
tileset image as a whole. You’ve got all the information you need to
be able to blit your tiles.
Now, if you’re using GDI, your work isn’t quite
done. You’ll have to scan for transparent pixels, and replace them
with black (if they aren’t already), and also make a bitmask, but
since all the transparent pixels are there in the image, you can go
ahead and do that while scanning for transparent pixels. The beautiful
thing is that you don’t have to scan the entire image, you can
just scan within the sprite rectangles.
If you’re using DirectX, then your job is
simple, just set the source color key to the transparent control color
(be sure to take into account the pixel format) and go.
Blitting a sprite from an extended template …
You’ll probably want to keep all of this
information in a class or a struct, like so:
//GDI tileset
struct GDITileset
{
HDC hdcImage;//contains the actual image
HDC hdcBitMask;//contains the bitmask
LPRECT rcRectList;//list of sprite rectangles
LPPOINT ptAnchorList;//list of anchors
DWORD dwSpriteCount;//number of sprites
};
//ddraw tileset
struct DDTileset
{
LPDIRECTDRAWSURFACE7 lpddsTileset;//surface containing tileset
LPRECT rcRectList;//list of sprite rectangles
LPPOINT ptAnchorList;//list of anchors
DWORD dwSpriteCount;//number of sprites
};
Regardless of using GDI or DDraw, the way to find
out where to blit to is the same.
Your blit function will most likely resemble one
of the following:
bool BlitSpriteGDI(HDC hdcDest, GDITileset*
pTileset, DWORD dwSpriteIndex, int xDest, int yDest);
bool BlitSpriteDD(LPDIRECTDRAWSURFACE7 lpddsDest, DDTileset* pTileset,
DWORD dwSpriteIndex, int xDest, int yDest);
Within either of these functions, you first check
to see if dwSpriteIndex is a valid sprite by comparing it to pTileset->dwSpriteCount.
If it isn’t valid, return false.
if(dwSpriteIndex>=pTileset->dwSpriteCount)
return(false);
Next, we pull out the source RECT.
RECT rcSrc;
CopyRect(&rcSrc,&pTileset->rcRectList[dwSpriteIndex]);
Now, we calculate the destination RECT:
RECT rcDest;
CopyRect(&rcDest,&rcSrc);//start by
copying the source rect
OffsetRect(&rcDest,-pTileset->ptAnchorList[dwSpriteIndex].x,-pTileset->ptAnchorList[dwSpriteIndex].y);//subtract
the anchor
OffsetRect(&rcDest,rcSrc.left,rcSrc.top);//add the upper left corner
of the source rect
Lastly, the actual blit. With GDI, first the
bitmask with SRCAND, then the image with either SRCPAINT or SRCINVERT.
With DDraw, just use Blt.
And that’s about all there is to blitting from
an extended template.
Other uses for extended templates
…
I use extended templates for almost ALL of my 2d
graphics. Sprites, animation sequences, cursors, tiles, fonts, you name
it.
The great thing about using these templates for
animation sequences is that you can move the anchor points for a sprite
without moving the image. This makes lining up cells really easy.
Bitmapped fonts are a breeze, since you have the
width of the sprite within the struct. I usually use a little VB utility
I wrote to take a font and make an extended template with it. I only
include characters 32 through 127, and then I have the program scan the
image to set up the width and height and the anchors. (for the space, I
just copy the width and x anchor from the exclamation point).
Using extended templates for cursors is really
cool. You can use the anchor point as the point that you are on. This is
especially useful in fullscreen DirectDraw applications where you are
using DirectInput, and need to do your own cursors, and want to have
them animated; extended templates make that management easy.
One more really cool thing …
One last thing, and I’ll stop trying to sell you
on extended templates.
Since you have the sprite rectangles, you can
programmatically make the entire set smaller. It’s a really simple
process.
First, you scan through each row, and determine
the largest height (has to include the anchor if there are any anchors
outside of the image itself). Next, you scan each column, and do the
same thing for width.
Now, you can create a new template, and use the
information from the scan as the cell widths and heights, and then
reposition the sprites based on this information. Voila! you have a
smaller image, thus decreasing load time and scan time.
So, what CAN’T extended template do for you?
Well, there’s one thing I’d like these
templates to do that they currently cannot, and that is store strings
naming the sprites. Sigh. Bitmaps can only be extended so far. If
anybody has any ideas, though, let me know.
Ernest
Pazera |