Building a Game UI System, Part 1

In War of Words, there’s a fairly sophisticated UI that displays all of the character attributes while you play the game.  Things like health, status, level, gamerpic, score, and spells needs to be conveyed to the player.  One of the most frustrating things in game creation can be UI programming.

In the past, I’ve pieced together the UI by putting all of the coordinates in code and piecing everything together by hand.  This works only for the simplest UIs.  Now that I’ve got to do something more challenging, it’s time to develop something a little more robust.  The solution is to model a lot of other UI frameworks (for example, WinForms) and make it work for the things I need and within the context of the XNA framework.

We start by making an abstract base class that all UI parts implement.  We’ll call this class UIElement and it will be able to hold references to child elements, have a position (relative to a parent), a size, and it will receive Draw and Update calls to show itself and animate itself if needed.  Here’s the class in it’s entirety:

/// <summary>
/// Represents the base class for UIElements.
/// </summary>
public abstract class UIElement
{
    /// <summary>
    /// Master element constructor.
    /// </summary>
    public UIElement()
    {
        // we're the master!
        MasterElement = this;
        // TODO: set bounds based on viewport
        Bounds = new Rectangle(0, 0, 1280, 720);

        Children = new UIElementCollection();
    }

    /// <summary>
    /// Non-master child element constructor.
    /// </summary>
    public UIElement(UIElement parent, int x, int y, int width, int height)
    {
        Bounds =
            new Rectangle((int)parent.Position.X + x,
                                 (int)parent.Position.Y + y, width, height);
        Parent = parent;

        Parent.Children.Add(this);
        Children = new UIElementCollection();
    }

    /// <summary>
    /// Gets/sets the absolute position of the element.
    /// </summary>
    public Vector2 Position
    {
        get
        {
            return new Vector2(Bounds.X, Bounds.Y);
        }
        set
        {
            Bounds.Offset((int)value.X - Bounds.Left, (int)value.Y - Bounds.Top);
        }
    }

    /// <summary>
    /// Gets the width of the element.
    /// </summary>
    public virtual int Width
    {
        get
        {
            return Bounds.Width;
        }
    }

    /// <summary>
    /// Gets the height of the element.
    /// </summary>
    public virtual int Height
    {
        get
        {
            return Bounds.Height;
        }
    }

    /// <summary>
    /// Gets the rectangle bounds of the element.
    /// </summary>
    public Rectangle Bounds { get; private set; }

    /// <summary>
    /// Gets the parent element of the element.  This will be null
    /// for master elements.
    /// </summary>
    public UIElement Parent { get; private set; }

    /// <summary>
    /// Gets the child element collection.
    /// </summary>
    public UIElementCollection Children { get; private set; }

    /// <summary>
    /// Gets whether any children are contained at this element.
    /// </summary>
    public bool HasChildren
    {
        get { return Children.Count > 0; }
    }

    /// <summary>
    /// Gets/sets the master element.
    /// </summary>
    public static UIElement MasterElement { get; private set; }

    /// <summary>
    /// Gets a value of true if the UI element is the
    /// </ins>master element.
    /// </summary>
    public bool IsMaster
    {
        get
        {
            return this == MasterElement;
        }
    }

    /// <summary>
    /// Draws the element and its children.
    /// </summary>
    public void Draw(SpriteBatch spriteBatch)
    {
        OnDraw(spriteBatch);

        // draw each child
        foreach (UIElement child in Children)
        {
            child.Draw(spriteBatch);
        }
    }

    /// <summary>
    /// Meant to be overridden in the concrete classes in order to
    /// allow an element to draw itself.
    /// </summary>
    ///
    protected virtual void OnDraw(SpriteBatch spriteBatch)
    {

    }

    /// <summary>
    /// Updates the element and its children.
    /// </summary>
    public void Update(GameTime gameTime)
    {
        OnUpdate(gameTime);

        foreach (UIElement child in Children)
        {
            child.Update(gameTime);
        }
    }

    /// <summary>
    /// Meant to be overridden in a concrete class to provide
    /// updating for each UI element.
    /// </summary>
    protected virtual void OnUpdate(GameTime gameTime)
    {

    }

Central to my system is the concept of a “master” UIElement. The master element is the root of the UI hierarchy and represents the entire game screen.  Any child elements of the master element are then placed within the master element relative to it (which makes them relative to the screen itself).  To create the master element, you simply use the parameterless constructor.  There is always a reference available to the master element with the static UIElement.MasterElement property.  Master elements have a value of null for their Parents.

To make non-master elements, you use the second constructor.  This constructor calls for a parent element, the relative x and y coordinates within the parent element and a specific size.  Currently, my UI system does not allow resizing elements but there’s no reason why you couldn’t extend it to do that.

The Position property gets the current position of the upper-left corner of the element.  This position is actually in screen coordinates even though relative coordinates are used to initially position it.  This allows ease of access within the draw routine so that you can pass Position to SpriteBatch to position your sprite (or whatever way you are drawing).  It can also set the element relative to its current position (which in turn is relative to its parent).

You can also see that the Children property allows you to access the child UIElements of the element.  When you construct a non-master element, it will automatically place your element as a child of the parent.

The Update() and Draw() methods are called by your code to update and draw the element and its children automatically.  The protected methods OnUpdate() and OnDraw() are the methods that can be overridden to provide element-specific drawing and updating.  For example, if you are making an UIElement that displays a simple text string, you’d want to override OnDraw() and provide the correct SpriteBatch.DrawString() call to display it.

I’ll be writing more on UIElement and specific implementations of it in future posts.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: