Game Creation with XNA/2D Development/Heads-Up-Display
Heads-Up-Display
editA Heads-Up-Display (short HUD) is any transparent display that presents information without requiring users to look away from their usual viewpoints. The origin of the name stems from the modern aircraft pilots being able to view information with heads "up" and looking forward, instead of angled down looking at lower instruments.
Although they were initially developed for military aviation, HUDs are now used in commercial aircraft, automobiles, and even in todays game design. There the HUD relays information to the player as part of a game's user interface.
This article will feature examples for HUD elements and XNA templates for some of these basic components. Since good sprites are really important for creating a great looking HUD, designing these with professional image processing applications, such as Gimp or Photoshop is vital. Developing the skills will not be part of this article.
Introduction
editApplication
editThere are many different types of information that can be displayed using a HUD. Below is an outline of the most important stats displayed on video game HUDs
Health & lives
editHealth is of extreme importance. Hence this is one of the most important HUD Stats on display. This contains information about the player's character or about NPC's, such as allies and enemies. RTS games (e.g. Starcraft) usually display the health level of all units that are visible on screen. In many action oriented games (first- or third-person shooters) the screen flashes briefly, when the player is attacked, and shows arrows indicating the direction the threat came from.
Weapons & items
editMost action games (first- and third-person shooters in particular) show information about the weapons currently used, ammunition left, other weapons, objects or items that are available.
Menus
editMenus for different game related aspects (e.g. start game, exit game or change settings).
Time
editThis contains timer counting up or down to display information about certain events (e.g. end of round), records such as lap times or the length of time a player can last in survival based game. HUDS can be used to display in-game time (time, day, year within the game) or even show real time.
Context-sensitive Information
editThis contains information that are only shown when necessary or important (e.g. tutorial messages, one/off abilities, subtitles or action events).
Game progression
editThis contains information about the player's current game progress (e.g. stats on a gamer's progress within one particular task or quest, accumulated experience points or a gamer's current level). It also includes information about the player's current task.
Mini-maps, Compass, Quest-Arrow
editGames are all about reaching objectives, so HUDs must clearly state them, either in the form of a compass or quest arrow. A small map of the area that can act like a radar, showing the terrain, allies and/or enemies, locations like safe houses and shops or streets.
Speedometer
editUsed in most games that feature drivable vehicles. Usually shown only when driving one of these.
Cursor & Crosshair
editThe crosshair indicates the direction the player is pointing or aiming to.
Examples
editLess is more
editIn order to increase realism information normally displayed using a HUD can be instead disguised as part of the scenery or part of the vehicle the player is using. For example, when the player is driving a car that can sustain a certain number of hits, a smoke trail or fire might appear from the car to indicate that the car is seriously damaged and will break down soon. Wounds and bloodstains may sometimes appear on injured characters who may also limp or breathe heavily to indicate that they are injured.
In some cases, no HUD is displayed at all. Leaving the player to interpret the auditory and visual cues in the game world creates a more intense athmosphere.
Text in HUD
editEvery font installed on your computer can be used to display text in your HUD. Therefore the font has to be added as an "Existing file" to the project in Visual Studio. Afterwards a .spritefont (XML) file can be found in the content folder of your project. There all parameters, such as style, size or kerning, can be easily configured.
Loading fonts
editSpriteFont spriteFont = contentManager.Load<SpriteFont>("Path//Fontname");
Displaying fonts
editspriteBatch.DrawString(spriteFont, textLabel + ": " + textValue, position, textColor);
(Semi-)Transparency
editColor myTransparentColor = new Color(0, 0, 0, 127);
Background
editRectangle rectangle = new Rectangle();
rectangle.Width = spriteFont.MeasureString(text).X + 10;
rectangle.Height = spriteFont.MeasureString(text).Y + 10;
Texture2D texture = new Texture2D(graphicsDevice, 1, 1);
texture.SetData(new Color[] {color});
spriteBatch.Draw(texture, rectangle, color);
Images in HUD
editSince there is no concept of drawing on canvas elements, images or sprites are an important element for creating HUDs. XNA supports many different image formats, such as .jpeg or .png (including transparency).
Loading Images
editcontentManager.Load<Texture2D>("Path//Filename")
or you could try this one :
contentManager.Load<Texture2D>(@"Path/Filename")
With this approach we use the default "content" folder and the "doubled" ("//") slash is not necessary.
Displaying images
editspriteBatch.Draw(image, position, null, color, 0 , new Vector2(backgroundImage.Width/2, backgroundImage.Height/2), scale, SpriteEffects.None, 0);
Components
editThe following components are templates that are ready to use. They can be easily customized to fit the individual requirements.
Text
editInformation
editThis component displays a text field. It can be used to display a big variety of information, such as time, scores or objectives. In order to increase readability a semi transparent background is displayed behind the text.
Class variables
editprivate SpriteBatch spriteBatch;
private SpriteFont spriteFont;
private GraphicsDevice graphicsDevice;
private Vector3 position;
private String textLabel;
private String textValue;
private Color textColor;
private bool enabled;
Constructor
edit/// <summary>
/// Creates a new TextComponent for the HUD.
/// </summary>
/// <param name="textLabel">Label text that is displayed before ":".</param>
/// <param name="position">Component position on the screen.</param>
/// <param name="spriteBatch">SpriteBatch that is required to draw the sprite.</param>
/// <param name="spriteFont">Font that will be used to display the text.</param>
/// <param name="graphicsDevice">Graphicsdevice that is required to create the semi transparent background texture.</param>
public TextComponent(String textLabel, Vector2 position, SpriteBatch spriteBatch, SpriteFont spriteFont, GraphicsDevice graphicsDevice)
{
this.textLabel = textLabel.ToUpper();
this.position = position;
this.spriteBatch = spriteBatch;
this.spriteFont = spriteFont;
this.graphicsDevice = graphicsDevice;
}
Enable
edit/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void Enable(bool enabled)
{
this.enabled = enabled;
}
Update
edit/// <summary>
/// Updates the text that is displayed after ":".
/// </summary>
/// <param name="textValue">Text to be displayed.</param>
/// <param name="textColor">Text color.</param>
public void Update(String textValue, Color textColor)
{
this.textValue = textValue.ToUpper();
this.textColor = textColor;
}
Draw
edit/// <summary>
/// Draws the TextComponent with the values set before.
/// </summary>
public void Draw()
{
if (enabled)
{
Color myTransparentColor = new Color(0, 0, 0, 127);
Vector2 stringDimensions = spriteFont.MeasureString(textLabel + ": " + textValue);
float width = stringDimensions.X;
float height = stringDimensions.Y;
Rectangle backgroundRectangle = new Rectangle();
backgroundRectangle.Width = (int)width + 10;
backgroundRectangle.Height = (int)height + 10;
backgroundRectangle.X = (int)position.X - 5;
backgroundRectangle.Y = (int)position.Y - 5;
Texture2D dummyTexture = new Texture2D(graphicsDevice, 1, 1);
dummyTexture.SetData(new Color[] { myTransparentColor });
spriteBatch.Draw(dummyTexture, backgroundRectangle, myTransparentColor);
spriteBatch.DrawString(spriteFont, textLabel + ": " + textValue, position, textColor);
}
}
Meter
editInformation
editThis component displays a round instrument. It can be used to display a big variety of information, such as speed, rounds, fuel, height/depth, angle or temperature. The background image is displayed at the passed position. The needle image is rotated accordingly to the ratio between maximum and current value. The rotation angle is interpolated to create a smooth, life like impression.
Class variables
editprivate SpriteBatch spriteBatch;
private const float MAX_METER_ANGLE = 230;
private bool enabled = false;
private float scale;
private float lastAngle;
private Vector2 meterPosition;
private Vector2 meterOrigin;
private Texture2D backgroundImage;
private Texture2D needleImage;
public float currentAngle = 0;
Constructor
edit/// <summary>
/// Creates a new TextComponent for the HUD.
/// </summary>
/// <param name="position">Component position on the screen.</param>
/// <param name="backgroundImage">Image for the background of the meter.</param>
/// <param name="needleImage">Image for the neede of the meter.</param>
/// <param name="spriteBatch">SpriteBatch that is required to draw the sprite.</param>
/// <param name="scale">Factor to scale the graphics.</param>
public MeterComponent(Vector2 position, Texture2D backgroundImage, Texture2D needleImage, SpriteBatch spriteBatch, float scale)
{
this.spriteBatch = spriteBatch;
this.backgroundImage = backgroundImage;
this.needleImage = needleImage;
this.scale = scale;
this.lastAngle = 0;
meterPosition = new Vector2(position.X + backgroundImage.Width / 2, position.Y + backgroundImage.Height / 2);
meterOrigin = new Vector2(52, 18);
}
Enable
edit/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void Enable(bool enabled)
{
this.enabled = enabled;
}
Update
edit/// <summary>
/// Updates the current value of that should be displayed.
/// </summary>
/// <param name="currentValue">Value that to be displayed.</param>
/// <param name="maximumValue">Maximum value that can be displayed by the meter.</param>
public void Update(float currentValue, float maximumValue)
{
currentAngle = MathHelper.SmoothStep(lastAngle, (currentValue / maximumValue) * MAX_METER_ANGLE, 0.2f);
lastAngle = currentAngle;
}
Draw
edit/// <summary>
/// Draws the MeterComponent with the values set before.
/// </summary>
public void Draw()
{
if (enabled)
{
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);
spriteBatch.Draw(backgroundImage, meterPosition, null, Color.White, 0, new Vector2(backgroundImage.Width / 2, backgroundImage.Height / 2), scale, SpriteEffects.None, 0); //Draw(backgroundImage, position, Color.White);
spriteBatch.Draw(needleImage, meterPosition, null, Color.White, MathHelper.ToRadians(currentAngle), meterOrigin, scale, SpriteEffects.None, 0);
spriteBatch.End();
}
}
Radar
editInformation
editThis component displays a radar map. It can be used to display a big variety of information, such as objective or enemies. The background image is displayed at the passed position. Dots representing objects in the map are displayed accordingly to an array of positions.
Class variables
editprivate SpriteBatch spriteBatch;
GraphicsDevice graphicsDevice;
private bool enabled = false;
private float scale;
private int dimension;
private Vector2 position;
private Texture2D backgroundImage;
public float currentAngle = 0;
private Vector3[] objectPositions;
private Vector3 myPosition;
private int highlight;
Constructor
edit/// <summary>
/// Creates a new RadarComponent for the HUD.
/// </summary>
/// <param name="position">Component position on the screen.</param>
/// <param name="backgroundImage">Image for the background of the radar.</param>
/// <param name="spriteBatch">SpriteBatch that is required to draw the sprite.</param>
/// <param name="scale">Factor to scale the graphics.</param>
/// <param name="dimension">Dimension of the world.</param>
/// <param name="graphicsDevice">Graphicsdevice that is required to create the textures for the objects.</param>
public RadarComponent(Vector2 position, Texture2D backgroundImage, SpriteBatch spriteBatch, float scale, int dimension, GraphicsDevice graphicsDevice)
{
this.position = position;
this.backgroundImage = backgroundImage;
this.spriteBatch = spriteBatch;
this.graphicsDevice = graphicsDevice;
this.scale = scale;
this.dimension = dimension;
}
Enable
edit/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void Enable(bool enabled)
{
this.enabled = enabled;
}
Update
edit/// <summary>
/// Updates the positions of the objects to be drawn and the angle for the rotation of the radar.
/// </summary>
/// <param name="objectPositions">Position of all objects to be drawn.</param>
/// <param name="highlight">Index of the object to be highlighted. Object with a smaller or a
/// greater index will be displayed in a smaller size and a different color.</param>
/// <param name="currentAngle">Angle for the rotation of the radar.</param>
/// <param name="myPosition">Position of the player.</param>
public void update(Vector3[] objectPositions, int highlight, float currentAngle, Vector3 myPosition)
{
this.objectPositions = objectPositions;
this.highlight = highlight;
this.currentAngle = currentAngle;
this.myPosition = myPosition;
}
Draw
edit/// <summary>
/// Draws the RadarComponent with the values set before.
/// </summary>
public void Draw()
{
if (enabled)
{
spriteBatch.Draw(backgroundImage, position, null, Color.White,0 , new Vector2(backgroundImage.Width / 2, backgroundImage.Height / 2), scale, SpriteEffects.None, 0);
for(int i = 0; i< objectPositions.Length; i++)
{
Color myTransparentColor = new Color(255, 0, 0);
if (highlight == i)
{
myTransparentColor = new Color(255, 255, 0);
}
else if(highlight > i)
{
myTransparentColor = new Color(0, 255, 0);
}
Vector3 temp = objectPositions[i];
temp.X = temp.X / dimension * backgroundImage.Width / 2 * scale;
temp.Z = temp.Z / dimension * backgroundImage.Height / 2 * scale;
temp = Vector3.Transform(temp, Matrix.CreateRotationY(MathHelper.ToRadians(currentAngle)));
Rectangle backgroundRectangle = new Rectangle();
backgroundRectangle.Width = 2;
backgroundRectangle.Height = 2;
backgroundRectangle.X = (int) (position.X + temp.X);
backgroundRectangle.Y = (int) (position.Y + temp.Z);
Texture2D dummyTexture = new Texture2D(graphicsDevice, 1, 1);
dummyTexture.SetData(new Color[] { myTransparentColor });
spriteBatch.Draw(dummyTexture, backgroundRectangle, myTransparentColor);
}
myPosition.X = myPosition.X / dimension * backgroundImage.Width / 2 * scale;
myPosition.Z = myPosition.Z / dimension * backgroundImage.Height / 2 * scale;
myPosition = Vector3.Transform(myPosition, Matrix.CreateRotationY(MathHelper.ToRadians(currentAngle)));
Rectangle backgroundRectangle2 = new Rectangle();
backgroundRectangle2.Width = 5;
backgroundRectangle2.Height = 5;
backgroundRectangle2.X = (int)(position.X + myPosition.X);
backgroundRectangle2.Y = (int)(position.Y + myPosition.Z);
Texture2D dummyTexture2 = new Texture2D(graphicsDevice, 1, 1);
dummyTexture2.SetData(new Color[] { Color.Pink });
spriteBatch.Draw(dummyTexture2, backgroundRectangle2, Color.Pink);
}
}
Bar
editInformation
editThis component displays a bar. I can be used to display any kind of information that is related to percentages (e.g. fuel, health or time left to reach an objective). The current percent value is represented by the length of the colore bar. Accordingly to the displayed value, the color changes from green over yellow to red.
Class variables
edit private SpriteBatch spriteBatch;
private GraphicsDevice graphicsDevice;
private Vector2 position;
private Vector2 dimension;
private float valueMax;
private float valueCurrent;
private bool enabled;
Constructor
edit/// <summary>
/// Creates a new Bar Component for the HUD.
/// </summary>
/// <param name="position">Component position on the screen.</param>
/// <param name="dimension">Component dimensions.</param>
/// <param name="valueMax">Maximum value to be displayed.</param>
/// <param name="spriteBatch">SpriteBatch that is required to draw the sprite.</param>
/// <param name="graphicsDevice">Graphicsdevice that is required to create the semi transparent background texture.</param>
public BarComponent(Vector2 position, Vector2 dimension, float valueMax, SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
{
this.position = position;
this.dimension = dimension;
this.valueMax = valueMax;
this.spriteBatch = spriteBatch;
this.graphicsDevice = graphicsDevice;
this.enabled = true;
}
Enable
edit/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void enable(bool enabled)
{
this.enabled = enabled;
}
Update
edit/// <summary>
/// Updates the text that is displayed after ":".
/// </summary>
/// <param name="valueCurrent">Text to be displayed.</param>
public void update(float valueCurrent)
{
this.valueCurrent = valueCurrent;
}
Draw
edit/// <summary>
/// Draws the BarComponent with the values set before.
/// </summary>
public void Draw()
{
if (enabled)
{
float percent = valueCurrent / valueMax;
Color backgroundColor = new Color(0, 0, 0, 128);
Color barColor = new Color(0, 255, 0, 200);
if (percent < 0.50)
barColor = new Color(255, 255, 0, 200);
if (percent < 0.20)
barColor = new Color(255, 0, 0, 200);
Rectangle backgroundRectangle = new Rectangle();
backgroundRectangle.Width = (int)dimension.X;
backgroundRectangle.Height = (int)dimension.Y;
backgroundRectangle.X = (int)position.X;
backgroundRectangle.Y = (int)position.Y;
Texture2D dummyTexture = new Texture2D(graphicsDevice, 1, 1);
dummyTexture.SetData(new Color[] { backgroundColor });
spriteBatch.Draw(dummyTexture, backgroundRectangle, backgroundColor);
backgroundRectangle.Width = (int)(dimension.X*0.9);
backgroundRectangle.Height = (int)(dimension.Y*0.5);
backgroundRectangle.X = (int)position.X + (int)(dimension.X * 0.05);
backgroundRectangle.Y = (int)position.Y + (int)(dimension.Y*0.25);
spriteBatch.Draw(dummyTexture, backgroundRectangle, backgroundColor);
backgroundRectangle.Width = (int)(dimension.X * 0.9 * percent);
backgroundRectangle.Height = (int)(dimension.Y * 0.5);
backgroundRectangle.X = (int)position.X + (int)(dimension.X * 0.05);
backgroundRectangle.Y = (int)position.Y + (int)(dimension.Y * 0.25);
dummyTexture = new Texture2D(graphicsDevice, 1, 1);
dummyTexture.SetData(new Color[] { barColor });
spriteBatch.Draw(dummyTexture, backgroundRectangle, barColor);
}
}
Useful links
editUI game design
editHUD design in Photoshop
editResources
editReferences
edit- Beginning XNA 3.0 Game Programming: From Novice to Professional; Alexandre Santos Lobão, Bruno Evangelista, José Antonio Leal de Farias, Riemer Grootjans, 2009
- Microsoft® XNA Game Studio 3.0 UNLEASHED; Chad Carter; 2009
- Microsoft® XNA Game Studio Creator's Guide: An Introduction to XNA Game Programming; Stephen Cawood, Pat McGee, 2007
Authors
editChristian Höpfner