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:
- 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.
Perhaps the most important observation of them all has to do with the ASCII character set, which numbers all of the letters, numbers and punctuation symbols commonly used on computers. All of the letters (both capital and small) and numbers are listed in their natural order in the ASCII table, which means that we can take advantage of the way they are grouped mathematically. By the way, you can do an index search on 'ASCII character codes chart' in the online SDK documentation if you want to take a good look at the ASCII table.
For characters, the capital letter 'A' is character number 65, like so:
char c = 'A';
printf("Char = %d", c); // output: Char = 65
We can perform mathematical calculations directly using characters:
char c = 'A';
c += 3;
printf("Char = %c (number %d)", c, c); // output: Char = D (number 68)
There is a direct relationship between a character, its position in the ASCII table, and its position in our image, and now we're going to figure it out.
From the Known to the Unknown
So long as you have multiple images in a surface, you're always going to need to be able to find the proper coordinates that isolate any image. What is always known is that (0, 0) is the top-left of the image surface. In order to copy an area of memory from one surface to another, you need to know the starting top-left coordinate of the source image, as well as its width and height.
Let's start with the character 'A'. We know that its starting coordinates are (0,0), and by using the fact that all capital letters are evenly spaced, we can determine that a capital letter is 11 pixels wide and 13 pixels high. Simple enough:
#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 13
Now, 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
Here's my first run-through for this function. Keep in mind that we don't shoot for an ideal function our first time through, just a working one:
//////////////////////////////////////////////////////////////////////////////
// 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);
}
}
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