Game Creation with XNA/2D Development/Heads-Up-Display

Heads-Up-DisplayEdit

File:HUD view.jpg
Head Up Display (HUD) view from FA-18 Hornet.

A 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.

IntroductionEdit

ApplicationEdit

There 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 & livesEdit

Health 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 & itemsEdit

Most 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.

MenusEdit

Menus for different game related aspects (e.g. start game, exit game or change settings).

TimeEdit
 
HUD of the RTS game Warzone 2100.

This 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 InformationEdit

This contains information that are only shown when necessary or important (e.g. tutorial messages, one/off abilities, subtitles or action events).

Game progressionEdit

This 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-ArrowEdit

Games 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.

SpeedometerEdit

Used in most games that feature drivable vehicles. Usually shown only when driving one of these.

Cursor & CrosshairEdit

The crosshair indicates the direction the player is pointing or aiming to.

ExamplesEdit

  1. Beautiful video game HUD designs
  2. Great HUDs in gaming
  3. Games without using HUDs

Less is moreEdit

In 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 HUDEdit

Every 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 fontsEdit

SpriteFont spriteFont = contentManager.Load<SpriteFont>("Path//Fontname");

Displaying fontsEdit

spriteBatch.DrawString(spriteFont, textLabel + ": " + textValue, position, textColor);

(Semi-)TransparencyEdit

Color myTransparentColor = new Color(0, 0, 0, 127);

BackgroundEdit

Rectangle 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 HUDEdit

Since 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 ImagesEdit

contentManager.Load<Texture2D>("Path//Filename")

or you could try this one :

contentManager.Load<Texture2D>(@"Path/Filename")

With this aproach we use the default "content" folder and the "doubled" ("//") slash is not necessary.

Displaying imagesEdit

spriteBatch.Draw(image, position, null, color, 0 , new Vector2(backgroundImage.Width/2, backgroundImage.Height/2), scale, SpriteEffects.None, 0);

ComponentsEdit

The following components are templates that are ready to use. They can be easily customized to fit the individual requirements.

TextEdit

 
Text HUD component in XNA game.
InformationEdit

This 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 variablesEdit
private SpriteBatch spriteBatch;
private SpriteFont spriteFont;
private GraphicsDevice graphicsDevice;

private Vector3 position;

private String textLabel;
private String textValue;
private Color textColor;

private bool enabled;
ConstructorEdit
/// <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;
   }
EnableEdit
/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void Enable(bool enabled)
   {
   this.enabled = enabled;
   }
UpdateEdit
/// <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;
   }
DrawEdit
/// <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);
      }
   }

MeterEdit

 
Meter HUD component in XNA game.
InformationEdit

This 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 variablesEdit
private 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;
ConstructorEdit
/// <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);
   }
EnableEdit
/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void Enable(bool enabled)
   {
   this.enabled = enabled;
   }
UpdateEdit
/// <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;
   }
DrawEdit
/// <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();
      }
   }

RadarEdit

 
Radar HUD component in XNA game.
InformationEdit

This 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 variablesEdit
private 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;
ConstructorEdit
/// <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;
   }
EnableEdit
/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void Enable(bool enabled)
   {
   this.enabled = enabled;
   }
UpdateEdit
/// <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;
   }
DrawEdit
/// <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);
       }
   }

BarEdit

 
Bar HUD component in XNA game.
InformationEdit

This 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 variablesEdit
 private SpriteBatch spriteBatch;
private GraphicsDevice graphicsDevice;

private Vector2 position;
private Vector2 dimension;

private float valueMax;
private float valueCurrent;

private bool enabled;
ConstructorEdit
/// <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;
   }
EnableEdit
/// <summary>
/// Sets whether the component should be drawn.
/// </summary>
/// <param name="enabled">enable the component</param>
public void enable(bool enabled)
   {
   this.enabled = enabled;
   }
UpdateEdit
/// <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;
   }
DrawEdit
/// <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 linksEdit

UI game designEdit

  1. Video game interface design
  2. Rethinking HUD in game design
  3. Thoughts on HUDs

HUD design in PhotoshopEdit

  1. Ironman view interface
  2. High tech style HUD rings

ResourcesEdit

  1. Fonts for HUDs

ReferencesEdit

  1. Beginning XNA 3.0 Game Programming: From Novice to Professional; Alexandre Santos Lobão, Bruno Evangelista, José Antonio Leal de Farias, Riemer Grootjans, 2009
  2. Microsoft® XNA Game Studio 3.0 UNLEASHED; Chad Carter; 2009
  3. Microsoft® XNA Game Studio Creator's Guide: An Introduction to XNA Game Programming; Stephen Cawood, Pat McGee, 2007


AuthorsEdit

Christian Höpfner