(For more resources on XNA 4.0, see here.)
Unlike a Windows Forms application, XNA cannot use the TrueType fonts that are installed on your computer. In order to use a font, it must first be converted into a SpriteFont, a bitmap based representation of the font in a particular size that can be drawn with the SpriteBatch.DrawString() command.
Technically, any Windows font can be turned into a SpriteFont, but licensing restrictions on most fonts will prevent you from using them in your XNA games. The redistributable font package is provided by Microsoft to address this problem and give XNA developers a range of usable fonts that can be included in XNA games. Following are samples of each of the fonts included in the font package:
SpriteFont pericles36Font;
pericles36Font = Content.Load<SpriteFont>(@"FontsPericles36");
Adding a SpriteFont to your game is very similar to adding a texture image. Since both are managed by the Content Pipeline, working with them is identical from a code standpoint. In fact, SpriteFonts are really just specialized sprite sheets, similar to what we used for our game pieces, and are drawn via the same SpriteBatch class we use to draw our sprites.
The .spritefont file that gets added to your project is actually an XML document containing information that the Content Pipeline uses to create the .XNB file that holds the bitmap information for the font when you compile your code. The .spritefont file is copied from a template, so no matter what you call it, the XML will always default to 14 point Kootenay. In steps 4 and 5, we will edit the XML to generate 36 point Pericles instead.
Just as with a Texture2D, we declare a variable (this time a SpriteFont) to hold the Pericles 36 point font. The Load() method of the Content object is used to load the font.
SpriteFonts and extended characters
When a SpriteFont is built by the Content Processor, it actually generates bitmap images for each of the characters in the font. The range of characters generated is controlled by the <CharacterRegions> section in the SpriteFont’s XML description. If you attempt to output a character not covered by this range, your game will crash. You can avoid this by removing the HTML comment characters (<!–and –>) from around the <DefaultCharacter> definition in the XML file. Whenever an unknown character is output, the character defined in <DefaultCharacter> will be used in its place.
Displaying the player’s score with our new SpriteFont is simply a matter of calling the SpriteBatch.DrawString() method.
Vector2 scorePosition = new Vector2(605, 215);
ToString();" and replace the line with:
spriteBatch.DrawString(pericles36Font,
playerScore.ToString(),
scorePosition,
Color.Black);
Using named vectors to store things like text positions, allows you to easily move them around later if you decide to modify the layout of your game screen. It also makes code more readable, as we have the name scorePosition instead of a hard-coded vector value in the spriteBatch.DrawString() call. Since our window size is set to 800 by 600 pixels, the location we have defined above will place the score into the pre-defined score box on our background image texture.
The DrawString() method accepts a font to draw with (pericles36Font), a string to output (playerScore.ToString()), a Vector2 specifying the upper left corner of the location to begin drawing (scorePosition), and a color for the text to be drawn in (Color.Black).
Simply drawing the player’s score is not very exciting, so let’s add another use for our SpriteFont. In some puzzle games, when the player scores, the number of points earned is displayed in the center of the screen, rapidly growing larger and expanding until it flies off of the screen toward the player.
We will implement this functionality with a class called ScoreZoom that will handle scaling the font.
using Microsoft.Xna.Framework.Graphics;
public string Text;
public Color DrawColor;
private int displayCounter;
private int maxDisplayCount = 30;
private float scale = 0.4f;
private float lastScaleAmount = 0.0f;
private float scaleAmount = 0.4f;
public float Scale
{
get { return scaleAmount * displayCounter; }
}
public bool IsCompleted
{
get { return (displayCounter > maxDisplayCount); }
}
public ScoreZoom(string displayText, Color fontColor)
{
Text = displayText;
DrawColor = fontColor;
displayCounter = 0;
}
public void Update()
{
scale += lastScaleAmount + scaleAmount;
lastScaleAmount += scaleAmount;
displayCounter++;
}
The ScoreZoom class holds some basic information about a piece of text and how it will be displayed to the screen. The number of frames the text will be drawn for are determined by displayCounter and maxDisplayCount.
To manage the scale, three variables are used: scale contains the actual scale size that will be used when drawing the text, lastScaleAmount holds the amount the scale was increased by during the previous frame, and scaleAmount determines the growth in the scale factor during each frame.
You can see how this is used in the Update() method. The current scale is increased by both the lastScaleAmount and scaleAmount. lastScaleAmount is then increased by the scale amount. This results in the scale growing in an exponential fashion instead of increasing linearly by a scaleAmount for each frame. This will give the text a zooming effect as it starts growing slowly and then speeds up rapidly to fill the screen.
Queue<ScoreZoom> ScoreZooms = new Queue<ScoreZoom>();
private void UpdateScoreZooms()
{
int dequeueCounter = 0;
foreach (ScoreZoom zoom in ScoreZooms)
{
zoom.Update();
if (zoom.IsCompleted)
dequeueCounter++;
}
for (int d = 0; d < dequeueCounter; d++)
ScoreZooms.Dequeue();
}
UpdateScoreZooms();
ScoreZooms.Enqueue(new ScoreZoom("+" +
DetermineScore(WaterChain.Count).ToString(),
new Color(1.0f, 0.0f, 0.0f, 0.4f)));
foreach (ScoreZoom zoom in ScoreZooms)
{
spriteBatch.DrawString(pericles36Font, zoom.Text,
new Vector2(this.Window.ClientBounds.Width / 2,
this.Window.ClientBounds.Height / 2),
zoom.DrawColor, 0.0f,
new Vector2(pericles36Font.MeasureString(zoom.Text).X / 2,
pericles36Font.MeasureString(zoom.Text).Y / 2),
zoom.Scale, SpriteEffects.None, 0.0f);
}
Since all ScoreZoom objects “live” for the same amount of time, we can always be certain that the first one we create will finish before any created during a later loop. This allows us to use a simple Queue to hold ScoreZooms since a Queue works in a first-in-first-out manner.
When UpdateScoreZooms() is executed, the dequeueCounter holds the number of ScoreZoom objects that have finished updating during this cycle. It starts at zero, and while the foreach loop runs, any ScoreZoom that has an IsCompleted property of true increments the counter. When the foreach has completed, ScoreZooms.Dequeue() is run a number of times equal to dequeueCounter.
Adding new ScoreZoom objects is accomplished in step 4, with the Enqueue() method. The method is passed a new ScoreZoom object, which is constructed with a plus sign (+) and the score being added, followed by a red color with the alpha value set to 0.4f, making it a little more than halfway transparent.
Just as the SpriteBatch.Draw() method has multiple overloads, so does the SpriteBatch.DrawString() method, and in fact, they follow much the same pattern. This form of the DrawString() method accepts the SpriteFont (pericles36Font), the text to display, a location vector, and a draw color just like the previous call.
For the draw location in this case, we use this.Window.ClientBounds to retrieve the width and height of the game window. By dividing each by two, we get the coordinates of the center of the screen.
The remaining parameters are the same as those of the extended Draw() call we used to draw rotated pieces. After the color value is rotation, which we have set to 0.0f, followed by the origin point for that rotation. We have used the MeasureString() method of the SpriteFont class to determine both the height and width of the text that will be displayed and divided the value by two to determine the center point of the text. Why do this when there is no rotation happening? Despite what the order of the parameters might indicate, this origin also impacts the next parameter: the scale.
When the scale is applied, it sizes the text around the origin point. If we were to leave the origin at the default (0, 0), the upper left corner of the text would remain in the center of the screen and it would grow towards the bottom right corner. By setting the origin to the center of the text, the scale is applied evenly in all directions:
Just as with the extended Draw() method earlier, we will use SpriteEffects.None for the spriteEffects parameter and 0.0f for the layer depth, indicating that the text should be drawn on top of whatever has been drawn already.
Now that we can draw text, we can add a new game state in preparation for actually letting the game end when the facility floods.
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…
Software architecture is one of the most discussed topics in the software industry today, and…