Drawing Text

I've seen two fundamental solutions for drawing text in computer games.

  • Pre-draw the character set into a surface, and copy each character

  • Draw text with a font

Each solution has strengths and weaknesses.

The surface method requires you to put the entire character set into a surface. This is fairly doable for a western European language. I've see the character sets generated in two ways: They can be drawn by hand or you can write a simple tool to use a font to draw each character into a grid. When you get ready to draw a text string, you simply run through the characters in the string and copy the corresponding bits from the surface.

The downside of the pre-drawn solution is that each character must exist on the grid, which means that languages like Mandarin Chinese are right out—their character set has thousands of symbols. It's just not a good idea to store a few megabytes of thousands of characters in a pre-drawn bitmap. It's too big. If we're talking about a western European language, the solution is more viable because there aren't that many symbols—less than 200. You also don't have the freedom of changing their size. Unless you stretch or squash the characters, you only get the size that was drawn originally.

The upside of this solution is its speed: character strings can easily be drawn in real time. Another upside is the high quality of the graphics, which can be custom tweaked and appear in multiple colors.

When I speak of a grid, it's important to note that the grid isn't necessarily a regular one. Most fonts have characters that are different widths. The font used in this book is a good example. The sequence "iiiiiiiiii" takes up a lot less space than "wwwwwwwwww" even though there are the same number of characters in each string. Figure 6.4 shows an example of a character set that covers the first few rows of the Unicode character set. If you want to see all of it you can open the Character Map tool in Windows, Accessories, System Tools.

click to expand
Figure 6.4: A Typical Character Set.

You can order these characters any way you want, but sticking to standards like ASCII or UNICODE is a pretty good idea. It sure makes your code look a lot better. Each character is "cut" into its own rectangular graphic. You'll have to do this by finding the rectangle coordinates for each character. It's not too hard to write a tool to do this, which is very common. Win32 has an API called GetTextExtentPoint32, which you can use to find the exact width and height of a character. Iterate through the character set, find the dimensions of each character, and use those dimensions to plan your grid. You'll also want to store the grid coordinates somewhere so you can find the exact location and size of each character. You'll learn more about how to manipulate graphic elements like this when we discuss sprites a little later in this chapter.

The second solution, using a font, has exactly opposite characteristics. Fonts are single color entities and are very slow to draw. Their strong point is their memory efficiency, ability to draw foreign languages, and amazing flexibility. A Win32 font is created by filling in a LOGFONT structure, and calling CreateFontIndirect. Win32 examples on exactly how to do this are quite terrible, and always seem to fill the LOGFONT structure by asking the ChooseFont dialog to do all the dirty work. It turns out that the LOGFONT structure is nearly impossible to fill out by hand and be 100% sure you will get the same thing on every computer. Don't believe me? Take a look at Figure 6.5 to see the values LOGFONT requires for some fonts we used in the Microsoft products.

click to expand
Figure 6.5: Values for LOGFONT to Display Different Fonts.

Best Practice

If you think the #defined constants that you see in the LOGFONT documentation would make more sense than these hard coded numbers, you are somewhat correct. They end up justifying the numbers, but you can't necessarily guess the exact values that will result in the exact font you want. Here's our solution: We wrote a little tool that used the ChooseFont() dialog in Win32 to select the font, weight, height, and effects and wrote out the resulting LOGFONT structure into a CSV file. Our game read the CSV file to initialize the fonts it used to draw all the text in the game. I suggest you do exactly the same thing. The steps involved include.

  1. Initialize an array of LOGFONT structures from a data file.

  2. Send each one of these structures into the Win32 CreateFontIndirect() function.

  3. Store the resulting HFONT for future use.

When you're ready to draw the font to a surface, here is the function you can use:

 static const COLORREF COLOR_TRANSPARENT( RGB( 255, 0, 255 ) ) ; HRESULT DrawText( HFONT hFont, const TCHAR* strText,                   DWORD dwOriginX, DWORD dwOriginY,                   COLORREF crBackground, COLORREF crForeground,                   COLORREF crShadow,const CPoint& shadowOffset) {         HDC     hDC = NULL;     HRESULT hr;     if( m_pdds == NULL || strText == NULL )         return E_INVALIDARG:     // Make sure this surface is restored.         if ( m_pdds->IsLost() != DD_OK )     {     if( FAILED( hr = m_pdds->Restore() ) )         return hr;     }     if( FAILED( hr = m_pdds->GetDC( &hDC ) ) )         return hr;     // Transparency     if ( COLOR_TRANSPARENT==crBackground )     {           SetBkMode(hDC, TRANSPARENT);     }     else     {           SetBkColor( hDC, crBackground );     }     void *pOld = NULL; if( hFont )     pOld = SelectObject( hDC, hFont );     if(COLOR_TRANSPARENT != crShadow)//Draw the shadow if you must"     {           //The shadow text gets drawn first, with the background color intact           SetTextColor( hDC, crShadow );                     TextOut( hDC,             dwOriginX+shadowOffset.x,             dwOriginY+shadowOffset.y, strText, l );           //The main text gets drawn with a transparent background           SetBkMode(hDC, TRANSPARENT);      }      SetTextColor( hDC, crForeground );      TextOut( hDC, dwOriginX, dwOriginY, strText, l );      if ( pOld )             SelectObject( hDC, (HFONT) pOld );    if( FAILED( hr = m_pdds->ReleaseDC( hDC ) ) )         return hr;    return S_OK; } 

There are two important things going on in this function. Right after the DC is grabbed, the cdBackground color is checked to see if it is transparent. This is important if your font is antialiased to the background pixels instead of a particular color. Figure 6.6 shows the difference.

click to expand
Figure 6.6: Displaying Fonts with Antialiasing Effects.

The antialiased font blends the edges of the letters with the background color or the pixel values that are already there. There is a processor cost to using antialiased fonts, since they must read the background pixel values as the calculated blended colors. Fonts aren't cheap to draw, aliased or not so you might as well make sure your fonts are all drawn antialiased. One thing is clear, they look much better. Another feature of this function is a colored drop shadow. A drop shadow clarifies text and sharpens its image, especially on busy or light colored backgrounds. Figure 6.7 shows an example.

click to expand
Figure 6.7: Using a Drop Shadow to Display Text.

Drop shadows are not native to Win32 fonts: You have to draw yours the hard way. You do this by drawing the text string twice, first with the drop shadow color, and then with a small (x,y) offset and second with the nominal color. Yes, it's expensive. Yes, you should do it.

Did I mention that fonts were expensive to draw? I could say this hundreds of times and it would still lack punch. On one of our games there was a bit of text that was supposed to flash red, green, yellow and back to red in a looping animation. Hey, this was a casino game, what did you expect? Taste? Anyway, my first implementation simply redrew the font every second. Even this seemingly vast amount of time1,000 ms is a near eternity to a modern CPUwas not enough. The game's framerate was brought to its knees. The solution to the problem was to pre-draw the text string in all three colors, and save off the result. A font draw every 1,000 ms was replaced with a smallish memory copy. The game's framerate was thankfully restored.

A more insidious problem has to do with an antialiased font with a transparent background colorit is supposed to antialias to the destination surface. This implies that if the destination surface changes, as it might if something were to animate underneath the text string, that the font would need to be redrawn. That's absolutely true. If you fail to redraw the font you'll get some odd pixel artifacts around the outside of the letters. Needless to say, you should avoid screen designs that have antialiased fonts overlap background animations.

One more clue about fonts: Not every font that comes default on Windows machines can be drawn antialiased. Documentation on font attributes is pretty tough to come by, including which fonts are included with all the different Windows operating systems. I suggest you perform empirical experiments, and keep good records. Whenever I need this data I just call a friend at Microsoft! I know, I cheat. So what?


Never forget that fonts are creative works and must be licensed. If you include a font file in your game you must have permission from the font company or your game will be in breach of copyright laws. Some font companies have special terms of use in multimedia content. Before you use a font that you didn't create in-house make sure you lawyer up and cover your butt. The last thing you need is a font company suing you because you didn't buy the right license, which can run as high as $15,000 for a single font family.

Game Coding Complete
Game Coding Complete
ISBN: 1932111751
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net