Original Post
Copying and Pasting
The trend that we're trying to set here is to collect all of your game images into a single bitmap, load that bitmap into a DirectDraw surface, and 'copy and paste' these images onto our backbuffer using Blt() or BltFast() . We already have enough tools to accomplish this task with our game template code -- now it's just a matter of learning some techniques.
For this article I'm using BaseCode1 again, in its original form. You should do the same, as we've made various changes to the template code in the last article. Also, it doesn't really matter which resolution or color depth you use, as we'll only be concentrating on surface blitting (BLT = Bit bLock Transfer, or 'blitting' for slang). What we will be interested in this time around is the RESOURCE.BMP file that's included in the template code...
Letters And Numbers
It isn't hard to imagine what type of task we could perform with RESOURCE.BMP. What we're interested in doing is creating a function that, given a string of letters, numbers and spaces, displays the string at a requested location on the display. In other words, we'd like to write a function like this:
void DrawFont(int x, int y, const char *pszText);
So then if we wanted to display a string on the display in the top-left corner, we'd make a call like so:
DrawFont(0, 0, "Hello there");
Feeling Out RESOURCE.BMP
We definitely need to take a closer look at our bitmap image in order to start coming up with a game plan. Here are some observations:
Once again, we're totally neglecting the fact that DirectX methods return HRESULT values that indicate an error condition, and a future article will fill in such 'gaps'.
Using this function shouldn't be difficult to figure out -- just make sure that you don't call EraseBackground() after using it, or else everything will be erased. Also, note that this function only draws a single line of text, and trusts that you don't go over the edge of the screen.
A proper font blitting function would also allow for punctuation characters, clip against the edge of the screen, and perhaps even allow for multi-line text (i.e. wraparound), but we're more interested in learning some fundamentals here. While it is true that Win32 GDI functions are painfully slow, we also have to make sure that our custom-made font blitting function is both fast and efficient.
Just the Beginning...
The importance of this lesson had little to do with writing a font blitter, and much to do with retrieving images from a DirectDraw surface at specific coordinates. It is the key to sprites, animation, and copying areas of memory quickly to the backbuffer. You are going to notice that although our data manipulation is going to get more complicated, almost everything we do from here on in relies on resources in an image and their extraction during run-time. After all, when you think back to the overhead projector analogy, being able to copy and paste pieces of transparency sheets onto a single 'backbuffer' sheet is a major part of preparing a frame of animation.
Next, we'll be using real sprite images instead of font characters, and our journey will take us into the realm of sprite animation, physics and AI.
Oh boy, can't wait!
Questions? Comments? Please post a reply to this topic.
Edited by - Teej on May 24, 2001 6:08:09 PM
- Everything is organized by row. The first row contains all of the capital letters from A to Z, in order. The second row contains the small letters, and the final row contains the digits from 0 to 9.
- The font used in the image is fixed-pitch, which means that all characters are evenly spaced in the row.
- The rows of characters are not the same height. The small letters are obviously taller than the capitals or numbers.
#define ALPHA_LARGE_STARTX 0
#define ALPHA_LARGE_STARTY 0
#define ALPHA_LARGE_WIDTH 11
#define ALPHA_LARGE_HEIGHT 13
Since we need to describe this character in a RECT structure for use with Blt() or BltFast() , here's what it might look like:
rectSrc.left = ALPHA_LARGE_STARTX; rectSrc.top = ALPHA_LARGE_STARTY; rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1; rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;What about the letter B? We know intuitively that it starts 13 pixels over from the beginning of the letter A, and has the same height and width: rectSrc.left = ALPHA_LARGE_STARTX + ALPHA_LARGE_WIDTH; (the other three RECT fields are the same as with 'A') Here's the letter C: rectSrc.left = ALPHA_LARGE_STARTX + (ALPHA_LARGE_WIDTH * 2); Can you see the pattern? If X is the letter that we want (A = 0, B = 1, C = 2, etc.), something like this would work: rectSrc.left = ALPHA_LARGE_STARTX + (ALPHA_LARGE_WIDTH * X); Now that we have a mathematical expression that describes the section of the image that we want, all we need to do is take a given character and convert it into a proper X value. Since we know that 'A' = 65, wouldn't it be alright to say something like: X = C - 65; ...where C is the current character? Yessir it is! Here's a quick and dirty routine for calculating the source RECT:
RECT rectSrc;
char szText[] = {"ABCDEFG"};
for (int j = 0; j < strlen(szText); j++)
{
rectSrc.left = ALPHA_LARGE_STARTX + ((szText[j]- 'A') * ALPHA_LARGE_WIDTH);
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;
}
Of course, this routine assumes that every character in szText is a capital letter. Since we've really got three types of characters to display, we'll need to perform some decision-making in our loop like so:
if (szText[j] >= 'A' && szText[j] <= 'Z') ... Capital letters
if (szText[j] >= 'a' && szText[j] <= 'z') ... Small letters
if (szText[j] >= '0' && szText[j] <= '9') ... Numbers
if (szText[j] == ' ') ... Space
I threw the last one in (space) so that we have at least something useable.
Putting the Pieces Together
First, here's our definitions:
// Font definitions #define ALPHA_LARGE_STARTX 0 #define ALPHA_LARGE_STARTY 0 #define ALPHA_LARGE_WIDTH 11 #define ALPHA_LARGE_HEIGHT 13 #define ALPHA_SMALL_STARTX 0 #define ALPHA_SMALL_STARTY 13 #define ALPHA_SMALL_WIDTH 11 #define ALPHA_SMALL_HEIGHT 17 #define NUMERIC_STARTX 0 #define NUMERIC_STARTY 29 #define NUMERIC_WIDTH 11 #define NUMERIC_HEIGHT 13Now, here's a look at a possible algorithm for drawing a single line of text consisting of letters (large and small), numbers and spaces:
- For each character in the string:
- Is it a capital letter? If so, use ALPHA_LARGE_XXX defines to construct source rectangle
- Is it a small letter? If so, use ALPHA_SMALL_XXX defines to construct source rectangle
- Is it a number? If so, use NUMERIC_XXX defines to construct source rectangle
- Is it a space? If so, move over one character and continue with the next character
- If it is not a valid character, continue with the next character in the string
- Display the character on the backbuffer
- Increment x counter
//////////////////////////////////////////////////////////////////////////////
// DrawFont
//
void DrawFont(int x, int y, const char *pszText)
{
RECT rectSrc;
int location = 0;
int letterSpacing;
char curr;
// Go through each character in the given string
for (int i = 0; i < (int)strlen(pszText); i++)
{
// Get the current character
curr = pszText[i];
// Capital letter?
if (curr >= 'A' && curr <= 'Z')
{
rectSrc.left = ALPHA_LARGE_STARTX + ((curr - 'A') * ALPHA_LARGE_WIDTH);
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;
letterSpacing = ALPHA_LARGE_WIDTH;
}
// Small letter?
else if (curr >= 'a' && curr <= 'z')
{
rectSrc.left = ALPHA_SMALL_STARTX + ((curr - 'a') * ALPHA_SMALL_WIDTH);
rectSrc.top = ALPHA_SMALL_STARTY;
rectSrc.right = rectSrc.left + ALPHA_SMALL_WIDTH - 1;
rectSrc.bottom = rectSrc.top + ALPHA_SMALL_HEIGHT - 1;
letterSpacing = ALPHA_SMALL_WIDTH;
}
// Number?
else if (curr >= '0' && curr <= '9')
{
rectSrc.left = NUMERIC_STARTX + ((curr - '0') * NUMERIC_WIDTH);
rectSrc.top = NUMERIC_STARTY;
rectSrc.right = rectSrc.left + NUMERIC_WIDTH - 1;
rectSrc.bottom = rectSrc.top + NUMERIC_HEIGHT - 1;
letterSpacing = NUMERIC_WIDTH;
}
// Other (space or non-supported character)
else
{
// If it's a space, move over one character (screen position)
if (curr == ' ') location++;
// Skip to the next character in the string
continue;
}
// Draw this character to the backbuffer at the proper location
G.lpDDSBack->BltFast(x + (location++ * letterSpacing), y,
G.lpDDSRes,
&rectSrc,
DDBLTFAST_WAIT | DDBLTFAST_NOCOLORKEY);
}
}
|