2D and 3D Fonts

  CU_MDX_Text.zip (63.8 KiB, 2,844 hits)


  CUnitFramework.zip (101.1 KiB, 20,514 hits)

Extract the C-Unit Framework
inside the project (not solution) directory.

Text is useful to show scores, game menus, and any other information you may want to share. DirectX supports both 2D and 3D fonts. 2D fonts are represented by the Font class. The Font constructor and the Font.DrawText method contain some parameters that are a bit hard to memorize, so I created my own Font class that encapsulates the Direct3D Font class:

namespace CUnit
{
/// Font wrapper
public class Font
{
Microsoft.DirectX.Direct3D.Font m_font = null;
private int m_size;

public enum Align { Left, Center, Right, TopRight, TopLeft, BottomRight, BottomLeft };

/// Default constructor
public Font() Toggle
{
// Empty
}


/// Create a new font.
/// Direct3D Device
/// Font face name
/// Size of the font
/// true for bold text, false for normal text
/// true for italic text, false for normal text
public Font( Device device, string faceName, int size, bool bold, bool italic ) Toggle
{
m_size = size;
m_font = new Microsoft.DirectX.Direct3D.Font( device, -size, 0,
( bold ) ? FontWeight.Bold : FontWeight.Normal, 1, italic,
CharacterSet.Default, Precision.Default, FontQuality.Default,
PitchAndFamily.DefaultPitch | PitchAndFamily.FamilyDoNotCare, faceName );
}


/// Print some 2D text.
/// Sprite for batch printing
/// Text to print
/// X position in window coordinates
/// Y position in window coordinates
/// Width to constrain text in
/// Height to constrain text in
/// Color of the text
/// CUnit.Font.Alignment enum
public void Print( Sprite sprite, string text, int xPosition, int yPosition, Toggle
int textBoxWidth, int textBoxHeight, int color, Align alignment )
{
if ( m_font == null )
{
return;
}
DrawStringFormat format = 0;
if ( textBoxWidth == 0 )
{
format |= DrawStringFormat.NoClip;
}
else
{
format |= DrawStringFormat.WordBreak;
switch ( alignment )
{
case Align.Left:
format |= DrawStringFormat.Left;
break;
case Align.Center:
format |= DrawStringFormat.Center;
break;
case Align.Right:
format |= DrawStringFormat.Right;
break;
case Align.TopRight:
format |= DrawStringFormat.Right | DrawStringFormat.Top;
break;
case Align.BottomRight:
format |= DrawStringFormat.Right | DrawStringFormat.Bottom;
break;
case Align.TopLeft:
format |= DrawStringFormat.Left | DrawStringFormat.Top;
break;
case Align.BottomLeft:
format |= DrawStringFormat.Left | DrawStringFormat.Bottom;
break;
}
if ( textBoxHeight == 0 )
{
// A width is specified, but not a height.
// Make it seem like height is infinite
textBoxHeight = 2000;
}
}
format |= DrawStringFormat.ExpandTabs;
System.Drawing.Rectangle rect = new System.Drawing.Rectangle( xPosition, yPosition, textBoxWidth, textBoxHeight );
m_font.DrawString( sprite, text, rect, (DrawStringFormat)format, color );
}


/// Print some 2D text.
/// Text to print
/// X position in window coordinates
/// Y position in window coordinates
/// Color of the text
public void Print( string text, int xPosition, int yPosition, int color ) Toggle
{
Print( null, text, xPosition, yPosition, 0, 0, color, Align.Left );
}


/// Print some 2D text.
/// Sprite for batch printing
/// Text to print
/// X position in window coordinates
/// Y position in window coordinates
/// Color of the text
public void Print( Sprite sprite, string text, int xPosition, int yPosition, int color ) Toggle
{
Print( sprite, text, xPosition, yPosition, 0, 0, color, Align.Left );
}


/// Call after the device is reset.
public void OnResetDevice() Toggle
{
if ( m_font != null )
{
m_font.OnResetDevice();
}
}


/// Call when the device is lost
public void OnLostDevice() Toggle
{
if ( m_font != null && !m_font.IsDisposed )
{
m_font.OnLostDevice();
}
}


/// Call when the device is destrroyed
public void OnDestroyDevice() Toggle
{
if ( m_font != null )
{
m_font.Dispose();
m_font = null;
}
}


/// Gets the font size.
public int Size Toggle
{
get { return m_size; }
}

}
}

Through the use of a few overloaded methods, the process of creating and printing 2D text is simplified. The Direct3D.Font class constructor contains parameters that we will probably never change, so my Font wrapper eliminates the need to specify these parameters.

The Direct3D.Font.DrawText method is also wrapped up in its own Print method. The Print methods simplify the process of printing and aligning text by setting certain parameters automatically.

/// Renders the current frame.
/// The Direct3D device
/// Time elapsed since last frame
public override void OnRenderFrame( Device device, float elapsedTime )
{
device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0 );
device.BeginScene();

// Clip…

m_font.Print( “Printing text without a sprite likernthis slows things down a bit.”, BackBufferWidth – 300, BackBufferHeight – 100, Color.ForestGreen.ToArgb() );

// Batch print text with a Sprite is a good optimization
m_textSprite.Begin( SpriteFlags.AlphaBlend | SpriteFlags.SortTexture );
m_font.Print( m_textSprite, sometext, 50, 100, 200, 0, Color.Red.ToArgb(), Font.Align.Center );
m_font.Print( m_textSprite, “We can even align a bunch of text along the right side. Isn’t that neat?”,
BackBufferWidth – 110, BackBufferHeight – (int)( BackBufferHeight / 2 ) – 30, 100, 0, Color.LightBlue.ToArgb(), Font.Align.Right );
m_textSprite.End();

// Clip…

device.EndScene();
device.Present();
}

The above code shows how to use the Font wrapper class to print text in your applications. Notice after the first line of text is printed, a Sprite instance is making a call to Sprite.Begin. When you are making lots of printing calls, using a Sprite increases the performance of your application by a noticeable amount. In my tests, I received as much as a 20% increase in performance by using a Sprite. To take advantage of this Sprite optimization, we simply put all our Print calls in between Sprite.Begin and Sprite.End while passing the sprite in to each Print method.

///
/// This event will be fired immediately after the Direct3D device has been
/// reset, which will happen after a lost device scenario, a window resize, and a
/// fullscreen toggle. This is the best location to create_ Pool.Default resources
/// since these resources need to be reloaded whenever the device is reset. Resources
/// created here should be released in the OnLostDevice callback.
///

/// The Direct3D device
public override void OnResetDevice( Device device )
{
// Clip…

// Create the light
device.Lights[0].LightType = LightType.Directional;
device.Lights[0].Diffuse = Color.White;
device.Lights[0].Direction = Vector3.Normalize( new Vector3( –1.0f, –1.0f, 1.0f ) );
device.Lights[0].Update();
device.Lights[0].Enabled = true;

// Set render states
device.RenderState.Lighting = true;
device.RenderState.Ambient = Color.FromArgb( 80, 80, 80 );

// Create our 3d text mesh
m_text3D = Microsoft.DirectX.Direct3D.Mesh.TextFromFont( device, new System.Drawing.Font( “Arial”, 20 ), “C-Unit”, 0.001f, 0.4f );

// Create the material
Material material = new Material();
material.DiffuseColor = ColorValue.FromColor( Color.CornflowerBlue );
material.AmbientColor = ColorValue.FromColor( Color.CornflowerBlue );
device.Material = material;

// Clip…
}

3D text is stored in a Mesh class. A Mesh class is basically an encapsulation of a vertex buffer and an index buffer. Since text in a Mesh is 3D, it is affected by lighting and the world transform matrix, therefore we have to specify a light and a material to view 3D text properly. To create_ the text mesh, we call simply Mesh.TextFromFont. The color of the mesh can be controlled through the specified material of the device.

/// Renders the current frame.
/// The Direct3D device
/// Time elapsed since last frame
public override void OnRenderFrame( Device device, float elapsedTime )
{
device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0 );
device.BeginScene();

// Clip…

device.RenderState.FillMode = m_framework.FillMode;
device.Transform.World = m_transform.Transform;

// Render 3D text
if ( m_text3D != null )
{
m_text3D.DrawSubset( 0 );
}

// Clip…

device.EndScene();
device.Present();
}

To render the 3D text, all we do is call BaseMesh.DrawSubset.

The Font and Sprite classes have methods, OnLostDevice and OnResetDevice, that need to be called whenever the device is lost or reset. Also, since the Mesh.TextFromFont method creates the mesh in the Pool.Default memory pool, the mesh needs to be disposed when the device is lost by calling Mesh.Dispose. The code for these calls is so small, I didn’t bother to display them here. Check out the source code to see all the resource handling.

The Direct3D Font class is an easy to use interface for printing text. However, pretty much all of the text displayed in the C-Unit Framework does not use the Direct3D Font class. I wrote my own bitmap font system which gives better performance and allows for better looking fonts at the cost of less justification options. You’ll learn about my bitmap font system in a later tutorial.

Text