CU_MDX_BitmapFont.zip (79.3 KB, 1,264 hits)
CUnitFramework.zip (101.1 KB, 11,475 hits)
inside the project (not solution) directory.
In this tutorial we will create_ a bitmap font system:
Previously, we rendered text using the DirectX Font class. While the Font class is an easy way to render text, it doesn’t have quite the performance that I would like and it is quite limited in the aesthetic quality of the fonts. As a result, I decided to implement a bitmap font system in the C-Unit Framework.
A letter printed with a bitmap font is simply a quad (two triangles) with the texture of a letter slapped onto it. To create_ our text, we will need a texture that contains all the characters that we will want to render. We will then assign each letter on the texture its own set of texture coordinates in order to access each letter and place them on their corresponding quads. Bitmap fonts usually come in two flavors: single-width fonts and multi-width fonts. Single-width fonts have each character evenly spaced on the texture. This allows us to calculate the texture coordinates in our program. While this is easier than multi-width fonts, it does not look as nice, since fonts that are not meant to be monospace, will be rendered in monospace. Multi-width fonts, on the other hand, are spaced differently according to letter. For example, an ‘i’ will take up less space than a ‘W’. In order to use multi-width fonts, we will need to create_ a texture that stores all the characters, and then create_ some sort of data sheet that provides all the dimensions of each character on the texture. Luckily, there are free programs to do this for us.
The C-Unit Framework creates bitmap fonts with the help of the Angelcode Bitmap Font Generator. The Angelcode Bitmap Font Generator is a nice tool that will arrange the characters of a font onto a texture and then create_ a data sheet that provides all the information we need to render the characters onto quads. The output looks like the following:
To store all this information, we’ll create_ a few small classes.
class BitmapCharacterSet
{
public int Base;
public int RenderedSize;
public int Width;
public int Height;
public BitmapCharacter[] Characters;
/// <summary>Creates a new BitmapCharacterSet</summary>
public BitmapCharacterSet()
for ( int i = 0; i < 256; i++ )
{
}
}
}
/// <summary>Represents a single bitmap character.</summary>
public class BitmapCharacter : ICloneable
{
public int Y;
public int Width;
public int Height;
public int XOffset;
public int YOffset;
public int XAdvance;
public List<Kerning> KerningList = new List<Kerning>();
/// <summary>Clones the BitmapCharacter</summary>
/// <returns>Cloned BitmapCharacter</returns>
public object Clone()
result.X = X;
result.Y = Y;
result.Width = Width;
result.Height = Height;
result.XOffset = XOffset;
result.YOffset = YOffset;
result.XAdvance = XAdvance;
result.KerningList.AddRange( KerningList );
return result;
}
}
/// <summary>Represents kerning information for a character.</summary>
public class Kerning
{
public int Amount;
}
/// <summary>Individual string to load into vertex buffer.</summary>
struct StringBlock
{
public RectangleF TextBox;
public BitmapFont.Align Alignment;
public float Size;
public ColorValue Color;
public bool Kerning;
/// <summary>Creates a new StringBlock</summary>
/// <param name=”text”>Text to render</param>
/// <param name=”textBox”>Text box to constrain text</param>
/// <param name=”alignment”>Font alignment</param>
/// <param name=”size”>Font size</param>
/// <param name=”color”>Color</param>
/// <param name=”kerning”>true to use kerning, false otherwise.</param>
public StringBlock( string text, RectangleF textBox, BitmapFont.Align alignment,
{
TextBox = textBox;
Alignment = alignment;
Size = size;
Color = color;
Kerning = kerning;
}
}
The BitmapCharacterSet class represents the entire BitmapCharacter alphabet. It contains the dimensions of the texture, the size the characters were created with, as well the line height, which we will use to determine the space between lines.
The BitmapCharacter class represents a single character. It contains all the information found in the generated .fnt file. To see what all these values represent, check out Angelcode’s page. They’re all just used to place and align the character in a rendered string.
The Kerning class stores any kerning information about a character. Kerning adjusts the distance between characters to make the text look more evenly spaced. For example, the letters “ll” would usually have a different kern amount than the letters “oo”. Not all fonts have kerning information but the C-Unit Framework supports kerning when that information is available.
The StringBlock class represents a single piece of text. Each of the colored sections of text in the screenshot above are separate StringBlocks. Each StringBlock can have its own color, size, bounding rectangle, alignment, and whether to use kerning.
To start implementing the bitmap font system, we’ll create_ a simple Quad class that will represent a polygon in screen space.
{
{
public Quad()
}
/// <summary>Creates a new Quad</summary>
/// <param name=”topLeft”>Top left vertex.</param>
/// <param name=”topRight”>Top right vertex.</param>
/// <param name=”bottomLeft”>Bottom left vertex.</param>
/// <param name=”bottomRight”>Bottom right vertex.</param>
public Quad( TransformedColoredTextured topLeft, TransformedColoredTextured topRight, TransformedColoredTextured bottomLeft, TransformedColoredTextured bottomRight )
m_vertices[0] = topLeft;
m_vertices[1] = bottomRight;
m_vertices[2] = bottomLeft;
m_vertices[3] = topLeft;
m_vertices[4] = topRight;
m_vertices[5] = bottomRight;
}
/// <summary>Gets and sets the vertices.</summary>
public TransformedColoredTextured[] Vertices
set { value.CopyTo( m_vertices, 0 ); }
}
/// <summary>Gets the top left vertex.</summary>
public TransformedColoredTextured TopLeft
}
/// <summary>Gets the top right vertex.</summary>
public TransformedColoredTextured TopRight
}
/// <summary>Gets the bottom left vertex.</summary>
public TransformedColoredTextured BottomLeft
}
/// <summary>Gets the bottom right vertex.</summary>
public TransformedColoredTextured BottomRight
}
/// <summary>Gets and sets the X coordinate.</summary>
public float X
set
{
m_vertices[0].X = value;
m_vertices[1].X = value + width;
m_vertices[2].X = value;
m_vertices[3].X = value;
m_vertices[4].X = value + width;
m_vertices[5].X = value + width;
}
}
/// <summary>Gets and sets the Y coordinate.</summary>
public float Y
set
{
m_vertices[0].Y = value;
m_vertices[1].Y = value + height;
m_vertices[2].Y = value + height;
m_vertices[3].Y = value;
m_vertices[4].Y = value;
m_vertices[5].Y = value + height;
}
}
/// <summary>Gets and sets the width.</summary>
public float Width
set
{
m_vertices[4].X = m_vertices[0].X + value;
m_vertices[5].X = m_vertices[0].X + value;
}
}
/// <summary>Gets and sets the height.</summary>
public float Height
set
{
m_vertices[2].Y = m_vertices[0].Y + value;
m_vertices[5].Y = m_vertices[0].Y + value;
}
}
/// <summary>Gets the X coordinate of the right.</summary>
public float Right
}
/// <summary>Gets the Y coordinate of the bottom.</summary>
public float Bottom
}
/// <summary>Gets and sets the Quad’s color.</summary>
public int Color
set
{
{
}
}
}
/// <summary>Writes the Quad to a string</summary>
/// <returns>String</returns>
public override string ToString()
result += “nY = “ + Y.ToString();
result += “nWidth = “ + Width.ToString();
result += “nHeight = “ + Height.ToString();
return result;
}
/// <summary>Clones the Quad.</summary>
/// <returns>Cloned Quad</returns>
public object Clone()
}
}
}
The Quad class is simple a wrapper for an array of TransformedColoredTextured vertices. It provides an easy way to create_ and manipulate a quad in screen space. The Quad class is a generic screen space quad that can be used to display 2D graphics (like my gui) or bitmapped text. The C-Unit bitmap font system requires a little more information with its Quads, so I created another class that inherits from Quad, called FontQuad:
{
public class FontQuad : Quad
{
private int m_wordNumber;
private float m_sizeScale;
private BitmapCharacter m_bitmapChar = null;
private char m_character;
private float m_wordWidth;
/// <summary>Creates a new FontQuad</summary>
/// <param name=”topLeft”>Top left vertex</param>
/// <param name=”topRight”>Top right vertex</param>
/// <param name=”bottomLeft”>Bottom left vertex</param>
/// <param name=”bottomRight”>Bottom right vertex</param>
public FontQuad( TransformedColoredTextured topLeft, TransformedColoredTextured topRight, TransformedColoredTextured bottomLeft, TransformedColoredTextured bottomRight )
m_vertices[0] = topLeft;
m_vertices[1] = bottomRight;
m_vertices[2] = bottomLeft;
m_vertices[3] = topLeft;
m_vertices[4] = topRight;
m_vertices[5] = bottomRight;
}
/// <summary>Gets and sets the line number.</summary>
public int LineNumber
set { m_lineNumber = value; }
}
/// <summary>Gets and sets the word number.</summary>
public int WordNumber
set { m_wordNumber = value; }
}
/// <summary>Gets and sets the word width.</summary>
public float WordWidth
set { m_wordWidth = value; }
}
/// <summary>Gets and sets the BitmapCharacter.</summary>
public BitmapCharacter BitmapCharacter
set { m_bitmapChar = (BitmapCharacter)value.Clone(); }
}
/// <summary>Gets and sets the character displayed in the quad.</summary>
public char Character
set { m_character = value; }
}
/// <summary>Gets and sets the size scale.</summary>
public float SizeScale
set { m_sizeScale = value; }
}
}
}
The FontQuad class stores a few more pieces of information that we’ll use when we create_ the the bitmap font system. For example, we’ll use the LineNumber and WordWidth properties when justifying text. Also, since the texture will print fonts at a preset size, the SizeScale property will allow us to scale the character to achieve any size we want. The class that performs all these calculations is BitmapFont:
public class BitmapFont
{
private BitmapCharacterSet m_charSet;
private List<FontQuad> m_quads;
private List<StringBlock> m_strings;
private string m_fntFile;
private string m_textureFile;
private Texture m_texture = null;
private VertexBuffer m_vb = null;
private const int MaxVertices = 4096;
private int m_nextChar;
/// <summary>Creates a new bitmap font.</summary>
/// <param name=”faceName”>Font face name.</param>
public BitmapFont( string fntFile, string textureFile )
m_strings = new List<StringBlock>();
m_fntFile = fntFile;
m_textureFile = textureFile;
m_charSet = new BitmapCharacterSet();
ParseFNTFile();
}
/// <summary>Parses the FNT file.</summary>
private void ParseFNTFile()
StreamReader stream = new StreamReader( fntFile );
string line;
char[] separators = new char[] { ‘ ‘, ‘=’ };
while ( ( line = stream.ReadLine() ) != null )
{
if ( tokens[0] == “info” )
{
for ( int i = 1; i < tokens.Length; i++ )
{
{
}
}
}
else if ( tokens[0] == “common” )
{
for ( int i = 1; i < tokens.Length; i++ )
{
{
}
else if ( tokens[i] == “base” )
{
}
else if ( tokens[i] == “scaleW” )
{
}
else if ( tokens[i] == “scaleH” )
{
}
}
}
else if ( tokens[0] == “char” )
{
int index = 0;
for ( int i = 1; i < tokens.Length; i++ )
{
{
}
else if ( tokens[i] == “x” )
{
}
else if ( tokens[i] == “y” )
{
}
else if ( tokens[i] == “width” )
{
}
else if ( tokens[i] == “height” )
{
}
else if ( tokens[i] == “xoffset” )
{
}
else if ( tokens[i] == “yoffset” )
{
}
else if ( tokens[i] == “xadvance” )
{
}
}
}
else if ( tokens[0] == “kerning” )
{
int index = 0;
Kerning k = new Kerning();
for ( int i = 1; i < tokens.Length; i++ )
{
{
}
else if ( tokens[i] == “second” )
{
}
else if ( tokens[i] == “amount” )
{
}
}
m_charSet.Characters[index].KerningList.Add( k );
}
}
stream.Close();
}
/// <summary>Call when the device is created.</summary>
/// <param name=”device”>D3D device.</param>
public void OnCreateDevice( Device device )
m_charSet.Width, m_charSet.Height, 0, Usage.None, Format.Dxt3, Pool.Managed,
Filter.Linear, Filter.Linear, 0, false, null );
}
/// <summary>Call when the device is destroyed.</summary>
public void OnDestroyDevice()
{
m_texture = null;
}
}
/// <summary>Call when the device is reset.</summary>
/// <param name=”device”>D3D device.</param>
public void OnResetDevice( Device device )
Usage.Dynamic | Usage.WriteOnly, TransformedColoredTextured.Format,
Pool.Default, null );
}
/// <summary>Call when the device is lost.</summary>
public void OnLostDevice()
{
m_vb = null;
}
}
/// <summary>Adds a new string to the list to render.</summary>
/// <param name=”text”>Text to render</param>
/// <param name=”textBox”>Rectangle to constrain text</param>
/// <param name=”alignment”>Font alignment</param>
/// <param name=”size”>Font size</param>
/// <param name=”color”>Color</param>
/// <param name=”kerning”>true to use kerning, false otherwise.</param>
/// <returns>The index of the added StringBlock</returns>
public int AddString( string text, RectangleF textBox, Align alignment, float size,
{
m_strings.Add( b );
int index = m_strings.Count - 1;
m_quads.AddRange( GetProcessedQuads( index ) );
return index;
}
/// <summary>Removes a string from the list of strings.</summary>
/// <param name=”i”>Index to remove</param>
public void ClearString( int i )
}
/// <summary>Clears the list of strings</summary>
public void ClearStrings()
m_quads.Clear();
}
/// <summary>Renders the strings.</summary>
/// <param name=”device”>D3D Device</param>
public void Render( Device device )
{
}
// Add vertices to the buffer
GraphicsBuffer<TransformedColoredTextured> gb =
m_vb.Lock<TransformedColoredTextured>( 0, 6 * m_quads.Count, LockFlags.Discard );
foreach ( FontQuad q in m_quads )
{
}
m_vb.Unlock();
// Set render states
device.SetRenderState( RenderStates.ZEnable, false );
device.SetRenderState( RenderStates.FillMode, (int)FillMode.Solid );
device.SetRenderState( RenderStates.ZBufferWriteEnable, false );
device.SetRenderState( RenderStates.FogEnable, false );
device.SetRenderState( RenderStates.AlphaTestEnable, false );
device.SetRenderState( RenderStates.AlphaBlendEnable, true );
device.SetRenderState( RenderStates.SourceBlend, (int)Blend.SourceAlpha );
device.SetRenderState( RenderStates.DestinationBlend, (int)Blend.InvSourceAlpha );
// Blend Texture and Vertex alphas
device.SetTextureState( 0, TextureStates.ColorArgument1, (int)TextureArgument.Current );
device.SetTextureState( 0, TextureStates.AlphaArgument1, (int)TextureArgument.Texture );
device.SetTextureState( 0, TextureStates.AlphaArgument2, (int)TextureArgument.Diffuse );
device.SetTextureState( 0, TextureStates.AlphaOperation, (int)TextureOperation.Modulate );
// Set sampler states
device.SetSamplerState( 0, SamplerStates.MinFilter, (int)Filter.Linear );
device.SetSamplerState( 0, SamplerStates.MagFilter, (int)Filter.Linear );
device.SetSamplerState( 0, SamplerStates.MipFilter, (int)Filter.Linear );
// Render
device.VertexFormat = TransformedColoredTextured.Format;
device.SetTexture( 0, m_texture );
device.SetStreamSource( 0, m_vb, 0, TransformedColoredTextured.StrideSize );
device.DrawPrimitives( PrimitiveType.TriangleList, 0, 2 * m_quads.Count );
}
/// <summary>Gets the list of Quads from a StringBlock all ready to render.</summary>
/// <param name=”index”>Index into StringBlock List</param>
/// <returns>List of Quads</returns>
public List<FontQuad> GetProcessedQuads( int index )
{
}
List<FontQuad> quads = new List<FontQuad>();
StringBlock b = m_strings[index];
string text = b.Text;
float x = b.TextBox.X;
float y = b.TextBox.Y;
float maxWidth = b.TextBox.Width;
Align alignment = b.Alignment;
float lineWidth = 0f;
float sizeScale = b.Size / (float)m_charSet.RenderedSize;
char lastChar = new char();
int lineNumber = 1;
int wordNumber = 1;
float wordWidth = 0f;
bool firstCharOfLine = true;
float z = 0f;
float rhw = 1f;
for ( int i = 0; i < text.Length; i++ )
{
float xOffset = c.XOffset * sizeScale;
float yOffset = c.YOffset * sizeScale;
float xAdvance = c.XAdvance * sizeScale;
float width = c.Width * sizeScale;
float height = c.Height * sizeScale;
// Check vertical bounds
if ( y + yOffset + height > b.TextBox.Bottom )
{
}
// Newline
if ( text[i] == ‘n’ || text[i] == ‘r’ || ( lineWidth + xAdvance >= maxWidth ) )
{
{
x = b.TextBox.X;
}
if ( alignment == Align.Center )
{
x = b.TextBox.X + ( maxWidth / 2f );
}
else if ( alignment == Align.Right )
{
x = b.TextBox.Right;
}
y += m_charSet.LineHeight * sizeScale;
float offset = 0f;
if ( ( lineWidth + xAdvance >= maxWidth ) && ( wordNumber != 1 ) )
{
// We have to move the last word down one line
char newLineLastChar = new char();
lineWidth = 0f;
for ( int j = 0; j < quads.Count; j++ )
{
{
if ( ( quads[j].LineNumber == lineNumber ) &&
( quads[j].WordNumber == wordNumber ) )
{
quads[j].WordNumber = 1;
quads[j].X = x + ( quads[j].BitmapCharacter.XOffset * sizeScale );
quads[j].Y = y + ( quads[j].BitmapCharacter.YOffset * sizeScale );
x += quads[j].BitmapCharacter.XAdvance * sizeScale;
lineWidth += quads[j].BitmapCharacter.XAdvance * sizeScale;
if ( b.Kerning )
{
Kerning kern = m_charSet.Characters[newLineLastChar].KerningList.Find( FindKerningNode );
if ( kern != null )
{
lineWidth += kern.Amount * sizeScale;
}
}
}
}
else if ( alignment == Align.Center )
{
( quads[j].WordNumber == wordNumber ) )
{
quads[j].LineNumber++;
quads[j].WordNumber = 1;
quads[j].X = x + ( quads[j].BitmapCharacter.XOffset * sizeScale );
quads[j].Y = y + ( quads[j].BitmapCharacter.YOffset * sizeScale );
x += quads[j].BitmapCharacter.XAdvance * sizeScale;
lineWidth += quads[j].BitmapCharacter.XAdvance * sizeScale;
offset += quads[j].BitmapCharacter.XAdvance * sizeScale / 2f;
float kerning = 0f;
if ( b.Kerning )
{
Kerning kern = m_charSet.Characters[newLineLastChar].KerningList.Find( FindKerningNode );
if ( kern != null )
{
x += kerning;
lineWidth += kerning;
offset += kerning / 2f;
}
}
}
}
else if ( alignment == Align.Right )
{
( quads[j].WordNumber == wordNumber ) )
{
quads[j].LineNumber++;
quads[j].WordNumber = 1;
quads[j].X = x + ( quads[j].BitmapCharacter.XOffset * sizeScale );
quads[j].Y = y + ( quads[j].BitmapCharacter.YOffset * sizeScale );
lineWidth += quads[j].BitmapCharacter.XAdvance * sizeScale;
x += quads[j].BitmapCharacter.XAdvance * sizeScale;
offset += quads[j].BitmapCharacter.XAdvance * sizeScale;
float kerning = 0f;
if ( b.Kerning )
{
Kerning kern = m_charSet.Characters[newLineLastChar].KerningList.Find( FindKerningNode );
if ( kern != null )
{
x += kerning;
lineWidth += kerning;
offset += kerning;
}
}
}
}
newLineLastChar = quads[j].Character;
}
// Make post-newline justifications
if ( alignment == Align.Center || alignment == Align.Right )
{
for ( int k = 0; k < quads.Count; k++ )
{
{
}
}
x -= offset;
// Rejustify the line it was moved from
for ( int k = 0; k < quads.Count; k++ )
{
{
}
}
}
}
else
{
firstCharOfLine = true;
lineWidth = 0f;
}
wordNumber = 1;
lineNumber++;
} // End new line check
// Don’t print these
if ( text[i] == ‘n’ || text[i] == ‘r’ || text[i] == ‘t’ )
{
}
// Set starting cursor for alignment
if ( firstCharOfLine )
{
{
x = b.TextBox.Left;
}
if ( alignment == Align.Center )
{
x = b.TextBox.Left + ( maxWidth / 2f );
}
else if ( alignment == Align.Right )
{
x = b.TextBox.Right;
}
}
// Adjust for kerning
float kernAmount = 0f;
if ( b.Kerning && !firstCharOfLine )
{
Kerning kern = m_charSet.Characters[lastChar].KerningList.Find( FindKerningNode );
if ( kern != null )
{
x += kernAmount;
lineWidth += kernAmount;
wordWidth += kernAmount;
}
}
firstCharOfLine = false;
// Create the vertices
TransformedColoredTextured topLeft = new TransformedColoredTextured(
x + xOffset, y + yOffset, z, rhw, b.Color.ToArgb(),
(float)c.X / (float)m_charSet.Width,
(float)c.Y / (float)m_charSet.Height );
TransformedColoredTextured topRight = new TransformedColoredTextured(
topLeft.X + width, y + yOffset, z, rhw, b.Color.ToArgb(),
(float)( c.X + c.Width ) / (float)m_charSet.Width,
(float)c.Y / (float)m_charSet.Height );
TransformedColoredTextured bottomRight = new TransformedColoredTextured(
topLeft.X + width, topLeft.Y + height, z, rhw, b.Color.ToArgb(),
(float)( c.X + c.Width ) / (float)m_charSet.Width,
(float)( c.Y + c.Height ) / (float)m_charSet.Height );
TransformedColoredTextured bottomLeft = new TransformedColoredTextured(
x + xOffset, topLeft.Y + height, z, rhw, b.Color.ToArgb(),
(float)c.X / (float)m_charSet.Width,
(float)( c.Y + c.Height ) / (float)m_charSet.Height );
// Create the quad
FontQuad q = new FontQuad( topLeft, topRight, bottomLeft, bottomRight );
q.LineNumber = lineNumber;
if ( text[i] == ‘ ‘ && alignment == Align.Right )
{
wordWidth = 0f;
}
q.WordNumber = wordNumber;
wordWidth += xAdvance;
q.WordWidth = wordWidth;
q.BitmapCharacter = c;
q.SizeScale = sizeScale;
q.Character = text[i];
quads.Add( q );
if ( text[i] == ‘ ‘ && alignment == Align.Left )
{
wordWidth = 0f;
}
x += xAdvance;
lineWidth += xAdvance;
lastChar = text[i];
// Rejustify text
if ( alignment == Align.Center )
{
// new character
float offset = xAdvance / 2f;
if ( b.Kerning )
{
}
for ( int j = 0; j < quads.Count; j++ )
{
{
}
}
x -= offset;
}
else if ( alignment == Align.Right )
{
// new character
float offset = 0f;
if ( b.Kerning )
{
}
for ( int j = 0; j < quads.Count; j++ )
{
{
quads[j].X -= xAdvance;
}
}
x -= offset;
}
}
return quads;
}
/// <summary>Gets the line height of a StringBlock.</summary>
public float GetLineHeight( int index )
{
}
return m_charSet.LineHeight * ( m_strings[index].Size / m_charSet.RenderedSize );
}
/// <summary>Search predicate used to find nodes in m_kerningList</summary>
/// <param name=”node”>Current node.</param>
/// <returns>true if the node’s name matches the desired node name, false otherwise.</returns>
private bool FindKerningNode( Kerning node )
}
/// <summary>Gets the font texture.</summary>
public Texture Texture
}
}
The BitmapFont class performs all the necessary initializations and calculations to print bitmap fonts. Since BitmapFont contains both a dynamic VertexBuffer and a Texture, we can see the normal resource On*Device methods. Initializing BitmapFont is as easy as calling the constructor with the name of the fnt file and the image generated by the Angelcode Bitmap Font Generator. The constructor calls ParseFNTFile which performs some basic text file I/O to store all the information into Lists of the data storage class described earlier.
The AddString, ClearString, and ClearStrings methods simply add or clear strings from the BitmapFont’s list of StringBlocks to render. The AddString method returns the index of the generated StringBlock, which you can use to either delete the StringBlock later with the ClearString method or get the List of raw FontQuads generated by GetProcessedQuads for use in a different Vertex Buffer.
The GetProcessedQuads method is the main method of BitmapFont. It performs all the calculations required to create_ and align each character of a StringBlock. The calcaulations are a bit long and took some pen and paper work so it’s probably best to look through the code yourself to understand it. Here is a list of items that we have to consider in the calculations:
- Text is generated character by character. When a character extends past the left or right edge of the bounding rectangle, we have to move all the characters of that same word to the next line.
- When text is center-aligned, whenever we add a new character, we have to recenter the text on the current line. When this line extends past the width of the bounding rectangle, we have to move the last word onto a new line, center the new line, and recenter the previous line from which the word was removed.
- When text is right-aligned, we add new characters on the right edge of the current line and then shift all the characters of this line to the left. When we have to create_ a new line, we move the last word to the new line, right-justify the new line, and rejustify the previous line from which the word was removed.
- Whenever a letter extends passed the bottom of the bounding rectangle, stop building the quads. If you see text being cut off, this is probably the reason why. Just extend the height of the bounding rectangle.
- In order to support arbitrary sizes, we create_ a size scale by dividing the desired rendering size by the size at which the text was generated in the texture. We then multiply all the placement values of BitmapCharacter by this size scale.
- When kerning should be used, look up the kerning information for the current character and adjust the cursor position.
When we render all the text, we first have to set a few renderstates in order for the text to appear correctly. Since we don’t want the text to have any Z-depth information, we disable RenderStates.ZEnable and RenderStates.ZBufferWriteEnable. We also want to make sure we render the text in FillMode.Solid. The alpha blending renderstates are used to render only the textured letter and not its background. The SamplerStates just improve the visual quality of the text when we scale it to new sizes. Note that since we are changing all these RenderStates, we have to change them back in our program to make sure any geometry that we render there is rendered correctly. We’ll usually want to reenable RenderStates.ZEnable and RenderStates.ZBufferWriteEnable and if we’re not using alpha transparency, disable RenderStates.AlphaBlendEnable.
That’s pretty much all there is to using BitmapFont. Some notes in using BitmapFont: Whenever you want to update_ text, clear all the strings with ClearStrings before adding new text or else the new text will just be appended to the list of text to render. Also, you may have noticed the following line of code in the tutorials:
// Only need to rebuild the text when the FPS updates
if ( m_fps != m_framework.FPS )
{
BuildText();
}
Since the fps only updates every half second, there’s no point in building all the text every frame. We increase performance by only building the text when it changes.
Some notes on the Angelcode Bitmap Font Generator: I noticed I got better results by adding about 2 pixels of padding and 1 pixel of spacing around each character. Without these space buffers, I was seeing some distortion in scaled text. Also, the BitmapFont class only supports fonts where all the characters are on 1 page, so make sure you keep the text on one texture by either reducing the font size or increasing the dimensions of the texture. And finally, make sure you select “Bit depth 32″ in order to have an alpha channel, which is necessary to render the fonts properly.