Tuesday 1 November 2011

Two Months, One Code Sample and Half an Update


Wow! Just Wow. 2 months since my last post is very bad on my part. Especially as I've been making progress (abeit slowly) on at least one of my projects.
From my last post I was hoping to do an initial post on each of my projects, and then have weekly or bi-monthly updates on the progress of those projects. However, they're all still quite rough looking and not much to say. However, we'll see. I might change my mind. But even though the posts have been thin, I've been making progress on at least one of my projects and have ended up creating a lot of, what I think are, useful components. Because of this, I thought I would start sharing them with my fellow readers. So if you're not a developer you may want to avoid any post from now on that has the tag "codesample". I must also warn that all code samples will be for XNA (determined by the "XNA" tag) for the foreseeable future, but this of course could change. But anyway...on with the code.

SpriteText - A text container with a twist

In order to describe and show some of my more useful components, I need to really start with some of my more basic components. This one is basically a container for all information that relates to drawing text to the screen. This
container contains public methods for each of the required parameters in the SpriteBatch.DrawString method. This is as follows:

/// <summary>
/// The font to draw the text in
/// </summary>
public SpriteFont Font { get; set; }

/// <summary>
/// The text to draw
/// </summary>
public string Text 
{
 get
 {
  return _text;
 }
 set
 {
  _text = value;
  this.Size = new Vector2(Font.MeasureString(value).X * this.Scale.X,
  Font.MeasureString(value).Y * this.Scale.Y);
  this.SetOrigin(_currentOrigin);
 }
}

/// <summary>
/// The position to draw the text at
/// </summary>
public Vector2 Position { get; set; }

/// </summary>
/// The size of the text
/// </summary>
public Vector2 Size
{
 get 
 {
  return _size;
 }
 private set
 {
  _size = value;
  _size = Vector2.Transform(_size, Matrix.CreateRotationZ(_rotation));
 }
}

/// <summary>
/// The colour to draw the text in
/// </summary>
/// <remarks>
/// Default is white
/// </remarks>
public Color Colour { get; set; }

/// <summary>
/// The rotation to draw the text
/// </summary>
public float Rotation 
{
 get
 {
  return _rotation;
 }
 set
 {
  _rotation = value;
  this.Size = new Vector2(Font.MeasureString(_text).X * _scale.X,
  Font.MeasureString(_text).Y * _scale.Y);
 }
}

/// <summary>
/// The origin of the text
/// </summary>
/// <remarks>
/// Default is the centre of the text
/// </remarks>
public Vector2 Origin { get; protected set; }

/// <summary>
/// The scale at which to draw the text in
/// </summary>
public Vector2 Scale
{
 get
 {
  return _scale;
 }
 set
 {
  _scale = value;
  _size = new Vector2(Font.MeasureString(_text).X * _scale.X,
  Font.MeasureString(_text).Y * _scale.Y);
 }
}

/// <summary>
/// The effect to apply to the text
/// </summary>
/// <remarks>
/// Default is none
/// </remarks>
public SpriteEffects Effect { get; set; }

/// <summary>
/// The depth at which the text is drawn
/// </summary>
/// <remarks>
/// By default this is 0
/// </remarks>
public float LayerDepth { get; set; }

I feel this is all very self explainatory. Most of the properties are automatic. The ones that aren't have corresponding protected variables. Scale and Rotation both change the Size property value when changed as these effect the overall size of the text object. Text sets a text property but then calls a method called SetOrigin passing in a private variable call _currentOrigin. "What's this doing?" I hear you ask. Well let me explain.

The twist that SpriteText does is it auto adjusts the origin value that is used when drawing the text so that it will always appear in the same location when the text is changed. Take the following example; You want to use the sprite text object to display a score on the right hand side of the screen. Now you can do this two ways; you can have origin set to Vector2.Zero and calculate the position taking into account the length of the text, or you can set the position to the far right and adjust the origin to the length of the text.


This is fine if your text doesn't change, but as soon as the length of the text changes, you need to change the value of either the origin or the position. As I'm a programmer, I'm lazy and want this done automagically. So incomes UpdateOrigin and _currentOrigin. _currentOriginis set in the constructor of SpriteText (along with other defaults for the other properties) and is a custom enum called Alignment (the code of which can be found below)

/// <summary>
/// Constructor
/// </summary>
/// <param name="spriteBatch">The sprite batch to draw the text to</param>
/// <param name="font">The font to draw the text in</param>
/// <param name="text">The text to draw</param>
public SpriteText(SpriteBatch spriteBatch, SpriteFont font, string text)
{
 this.Font = font;
 _currentOrigin = Alignment.Centre;
 this._spriteBatch = spriteBatch;
 this.Colour = Color.White;
 this.Scale = Vector2.One;
 this.Effect = SpriteEffects.None;
 this.Text = text;
 this.LayerDepth = 0;
}

/// <summary>
/// Represents the different alignments an object could be placed in
/// </summary>
public enum Alignment
{
 None,
 TopLeft,
 Top,
 TopRight,
 Right,
 BottomRight,
 Bottom,
 BottomLeft,
 Left,
 Centre
}

SetOrigin (which is a public method that can be called to change the origin "on the fly") takes an Alignment parameter and updates the origin/position values for the SpriteText object so it will always appear in the correct position on the screen. The code of which is below

/// <summary>
/// Set the origin of text
/// </summary>
/// <param name="origin">The origin alignment</param>
public void SetOrigin(Alignment origin)
{
 _currentOrigin = origin;

 switch (origin)
 {
  case Alignment.Left:
   this.Origin = new Vector2(0.0f,
   this.Font.MeasureString(this.Text).Y / 2);
   break;
  case Alignment.TopLeft:
   this.Origin = new Vector2(0.0f,
   0.0f);
   break;
  case Alignment.Top:
   this.Origin = new Vector2(this.Font.MeasureString(this.Text).X / 2,
   0.0f);
   break;
  case Alignment.TopRight:
   this.Origin = new Vector2(this.Font.MeasureString(this.Text).X,
   0.0f);
   break;
  case Alignment.Right:
   this.Origin = new Vector2(this.Font.MeasureString(this.Text).X,
   this.Font.MeasureString(this.Text).Y / 2);
   break;
  case Alignment.BottomRight:
   this.Origin = new Vector2(this.Font.MeasureString(this.Text).X,
   this.Font.MeasureString(this.Text).Y);
   break;
  case Alignment.Bottom:
   this.Origin = new Vector2(this.Font.MeasureString(this.Text).X / 2,
   this.Font.MeasureString(this.Text).Y);
   break;
  case Alignment.BottomLeft:
   this.Origin = new Vector2(0.0f,
   this.Font.MeasureString(this.Text).Y);
   break;
  case Alignment.Centre:
   this.Origin = new Vector2(this.Font.MeasureString(this.Text).X / 2,
   this.Font.MeasureString(this.Text).Y / 2);
   break;
  default:
  throw new ArgumentException("Alignment not supported");
 }
}

Then there is the standard draw method which just calls the SpriteBatch.DrawString method passing in the corresponding methods. As you can see it doesn't do anything special but I find it useful when wanting to draw text on the screen in XNA. The full code sample is available to download.

No comments:

Post a Comment

Got an opinion? Who hasn't? Post it here...