Warning: Parameter 1 to wp_default_scripts() expected to be a reference, value given in /var/www/xnafan.net/public_html/wp-includes/plugin.php on line 571

Warning: Parameter 1 to wp_default_styles() expected to be a reference, value given in /var/www/xnafan.net/public_html/wp-includes/plugin.php on line 571
xnaFan's Blog » Blog Archive » Creating a visual selection control from scratch

Creating a visual selection control from scratch

In this post we will extend the DrawableGameComponent to create a control which will let the user select one of a number of possible options.

Here you can see what we want to achieve:

The different options will be implemented as one big picture, where only the  currently selected option is displayed in the control.

visiblepart

How we will build the control

  1. Subclass DrawableGameComponent
  2. Add basic properties for
    1. positioning the control (Vector2)
    2. the menutexture (Texture2D)
    3. the number of possible selections (int)
    4. the currently selected index (int)
    5. the SpriteBatch to draw to
  3. Add functions for manipulating the selected index
    1. MoveToNext()
    2. MoveToPrevious()
  4. Override base Update() and Draw() to react to keyboard input and change selection

After building this control in a basic version, which "blinks" from selection to selection,  we will improve it, so the selection slides from one selected item to the next.

Subclassing DrawableGameComponent

The benefits of using DrawableGameComponent for basic game mechanics like this one is that once the gameobject is created and added to the Game object's Components collection, our component will automatically have Update() called (if our object's Enabled property is true) and Draw() called (if our object's Visible property is true). This way we can leave a lot of the "housekeeping" to the XNA engine.

So go ahead, whip up a new XNA solution in your favorite editor and add a new class "VisualSelectionControl", which inherits DrawableGameComponent:

public class VisualSelectionControl : DrawableGameComponent
{
}

When subclassing another class which doesn't have a default constructor (a "default constructor" is also known as  an "empty constructor" or a "parameterless constructor"), we often choose to pass the parameters for the superclass' constructor to the subclass' constructor, so the subclass can pass the parameters on to the superclass'. This means that our class will not compile as it is right now, because we can't create a VisualSelectionControl without creating a DrawableGameComponent. And the DrawableGameComponent wants a Game object in its constructor - or it just won't play :).

So we add a constructor to our VisualSelectionControl which takes a Game object, which we pass on to DrawableGameComponent's constructor:

public class VisualSelectionControl : DrawableGameComponent
{
    //creates a VisualSelectionControl by first creating a DrawableGameComponent
    //The constructor takes a Game object, and passes it on to the superclass (the "base" class)
    public VisualSelectionControl (Game game) : base(game)
	{
	}
}

Add basic properties

Since we already know what properties we need for our control to work (se item 2 in "How we will build our control"), let's add them to the class, and as parameters to the constructor, so we can't instantiate a VisualSelectionControl object without these variables:

public class VisualSelectionControl : DrawableGameComponent
{
    //the position (top-left corner) of the menu
    public Vector2 Position { get; set; }
    //the spritebatch to draw to
    public SpriteBatch SpriteBatch { get; set; }
    //stores the entire menu in one big picture
    public Texture2D MenuTexture { get; private set; }
    //how many selectable items the texture should be split into
    public int NumberOfPossibleSelections { get; private set; }
    //Currently selected index 
    public int CurrentIndex { get; set; }

    //creates a VisualSelectionControl by first creating a DrawableGameComponent
    //takes a Game object, and passes it on to the superclass (DrawableGameComponent)
    public VisualSelectionControl(Game game, Vector2 position, 
                                    SpriteBatch spriteBatch, Texture2D menuTexture,
                                    int numberOfPossibleSelections) : base(game)
    {
        //store parameters
        Position = position;
        SpriteBatch = spriteBatch;
        MenuTexture = menuTexture;
        NumberOfPossibleSelections = numberOfPossibleSelections;
    }
}

As you can see we've omitted the CurrentIndex property from the parameters to the constructor, since we're okay with that defaulting to zero when the control is created.

All we need to do now for our control to work is implement the Draw method on the control, so we can see what is selected.

Since the Draw method already exists on the DrawableGameComponent, we override it:

public override void Draw(GameTime gameTime)
{
    //call the superclass' implementation of the method we're overriding
    base.Draw(gameTime);
    //calculate the height of one menuitem
    int heightOfOneMenuItem = MenuTexture.Height / NumberOfPossibleSelections;
    //get the source rectangle we want, based on current index
    Rectangle sourceRect = new Rectangle(0, heightOfOneMenuItem* CurrentIndex, 
        (int)MenuTexture.Width, heightOfOneMenuItem);
    //draw to spritebatch, at control's position, from the sourcerectangle
    SpriteBatch.Draw(MenuTexture, Position, sourceRect, Color.White);
}

Calling overridden method from overriding method

As you can see, we call the base class' Draw() method from our new Draw() method. This is important to do whenever you override a method from a class where you're not entirely sure what is going on inside the overriden method. This is to ensure that the functionality in the parent object remains intact.

Then we add our own code to calculate how tall one menuitem is based on the complete menutexture and the number of items in the menu. Finally we create a source Rectangle, which we use to render only that part of our texture to the screen.

Test it in the Game class

Go ahead and download the Doom difficulty menu image (right click and save)

doom_difficulty

and add it to your content project.

Then add code to the LoadContent() method of the Game class, to load the texture and instantiate a VisualSelectionControl, which we add to the Game object's Components collection

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    VisualSelectionControl _selectionControl;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    Texture2D _levelselectionTexture = Content.Load<Texture2D>("doom_difficulty");
    _selectionControl = new VisualSelectionControl(this, Vector2.One * 100, spriteBatch, _levelselectionTexture, 5);
    Components.Add(_selectionControl);
}

Adding the VisualSelectionControl to the Game.Components collection ensures (as we've been over) that our component will automatically:

  1. have Update() called if our object's Enabled property is true (Enabled is inherited from GameComponent)
  2. have Draw() called if our object's Visible property is true (Visible is inherited from DrawableGameComponent)

BUT - if we run the game now, we will get an exception, as our control tries to draw to the SpriteBatch, and we haven't called SpriteBatch.Begin. So let's change the Draw() method in the Game class, to ensure that we call SpriteBatch.Begin() before calling the Draw() of all GameComponents, and SpriteBatch.End() afterwards:

protected override void Draw(GameTime gameTime)
{
    //clear the screen using any color you like (I like blood red)
    GraphicsDevice.Clear(Color.DarkRed);
    //begin drawing
    spriteBatch.Begin();
    //call the Game object's Draw() method, 
    //which draws all visible DrawableGameComponents in the Components collection
    base.Draw(gameTime);
    //end drawing
    spriteBatch.End();
}

Run the program - and voilà:

image

 

To test the menu control, you can try setting the CurrentIndex to 2 in the LoadContent method

_selectionControl.CurrentIndex = 2;

That should change the selected index, which in turn will make the calculations in Draw() create a different source Rectangle, rendering a different part of the Texture2D:

image

Add functions for manipulating the selected index

Now the good thing about having a property

//Currently selected index 
public int CurrentIndex { get; set; }

instead of a public variable

//Currently selected index 
public int CurrentIndex;

is that we've encapsulated the data inside with accessors, so we have full control over reading and writing to and from the variable. We will use that now, to ensure we only store valid values.

First we change the automatic property to a property with a backing variable:

//Currently selected index 
private int _currentIndex;

public int CurrentIndex
{
    get { return _currentIndex; }
    set { _currentIndex = value; }
}

and then we only set the _currentIndex variable, if the value we get is valid

set {
    //ensure only valid values are set
    if (value >= 0 &amp;amp;&amp;amp; value < NumberOfPossibleSelections) 
    {
        _currentIndex = value;
    }

This way we can easily add two helpermethods for moving step by step:

public void MoveToPrevious() { CurrentIndex--; }
public void MoveToNext() { CurrentIndex++; } 

The code in the CurrentIndex property's Set will ensure we don't go beyond the boundaries of the menu.

Add code to handle Keys.Up and Keys.Down presses

The choice of where to implement this functionality is up to you. It can go in your Game class, or in the control we're making. The problem with creating it in the control, is that it will listen to the KeyboardState all the time, and many different controls in the same game could start reacting to the same keypresses. But you can easily set the VisibleSelectionControl's Enabled property to false and by that make sure the Update() method of the control is not called. So we're going to go ahead and implement keyboard handling in the control for now. First we will implement simple keyboard handling with a flaw. Then we will look at why it doesn't work, and improve it by adding a little more code.

Naïve implementation of keyboard handling

We add a variable to the control for storing the keyboard's state:

//stores the state of the keyboard
private KeyboardState _currentKeyboardState;

Then we make a naïve implementation of reacting to the keyboard input in the Update() method:

public override void Update(GameTime gameTime)
{
    //get the keyboard's state
    _currentKeyboardState = Keyboard.GetState();
    //call the superclass' implementation of the method we're overriding
    base.Update(gameTime);
    //use input to change state
    if (_currentKeyboardState.IsKeyDown(Keys.Up)) { MoveToPrevious(); }
    if (_currentKeyboardState.IsKeyDown(Keys.Down)) { MoveToNext(); }
}

Go ahead and try it out. You will only see the first and last options in the menu.

"Why?" you ask?

Because the Update() method is called arond sixty times per second, and you probably won't release the Up or Down key fast enough to only trigger MoveToPrevious()/MoveToNext() once or twice, but five or ten times. So we have to only move one step, right when the key is pressed.

We do this by storing the keyboard's state from the previous Update() and then only acting on a new press, i.e. when the key's last state was UP and it is now DOWN.

Improved keyboard handling

So go ahead and update your code:

//stores the state of the keyboard
private KeyboardState _currentKeyboardState, _previousKeyboardState;

public override void Update(GameTime gameTime)
{
    //get the keyboard's state
    _currentKeyboardState = Keyboard.GetState();
    //call the superclass' implementation of the method we're overriding
    base.Update(gameTime);
    //use input to change state
    if (_currentKeyboardState.IsKeyDown(Keys.Up) &amp;amp;&amp;amp; _previousKeyboardState.IsKeyUp(Keys.Up)) { MoveToPrevious(); }
    if (_currentKeyboardState.IsKeyDown(Keys.Down) &amp;amp;&amp;amp; _previousKeyboardState.IsKeyUp(Keys.Down)) { MoveToNext(); }
    //store this Update()'s keyboard state for use in next Update
    _previousKeyboardState = _currentKeyboardState;
}

and try it out again. Now you should only trigger movement when you first press the key, and have to release it again to make another move - COOL huh? Smiley, der blinker

Not good enough you say?

So you think it would be nicer to have a smooth, scrolling motion - huh? Well okay - for a final encore, let's implement scrolling and call it a day Smiley med åben mund

Whenever you want movement in a game (even in a menu) you can make a variable to store the amount you want to change an object's position. Then  for every update you can add that amount to the position of whatever you want moved. In our case, we want the source rectangle (the place we grab the visible part of the menu from) to move gradually from where it was, to a position based on the new CurrentIndex.

To do that, all we need is to store how far we are from where we want to be, and then gradually diminish that distance every Update.

This technique can be used for a LOT of other animations as well in a game, just so you know Smiley, der blinker

Okay - so let's implement that...

Store where we are currently at, so we can calculate how to get to where we want to be

Since we want the source rectangle to move gradually now, we want to store its current position in a variable, so we can manipulate it in every Update().

So go ahead and move the source rectangle into its own member variable on the control. The position of the source rectangle will change every update and move towards the currently selected index.

//store the source rectangle
private Rectangle _sourceRectangle;

And since we will be needing the height of one menu item more places than one now, move it into its own member variable as well

//stores the height of one menu item
private float _heightOfOneMenuItem;

and initialize both of those variables in the constructor:

//calculate the height of one menuitem
_heightOfOneMenuItem = MenuTexture.Height / NumberOfPossibleSelections;
//create the source rectangle
_sourceRectangle = new Rectangle(0, 0, MenuTexture.Width, (int)_heightOfOneMenuItem);

All we need to do now is move the source rectangle towards its destination in the Update() method:

//calculate where the sourcerectangle is supposed to be on the Y axis of the texture
float sourceRectangleYDestination = _heightOfOneMenuItem * CurrentIndex;
//get the difference between where it is now and where it is supposed to be
float differenceOnYAxis = sourceRectangleYDestination - _sourceRectangle.Top;
//calculate a thrirty percent movement
float thirtyPercentMovement = differenceOnYAxis * .3f;
//move the source rectangle
_sourceRectangle.Offset(0, (int)thirtyPercentMovement);

Here we calculate what the difference is between where the source rectangle is now and where it should be. Then we move the source rectangle thirty percent of the distance every Update(). That percentage will result in a slowing down as the absolute distance in pixels gets smaller (30 % of 200 pixels is 60, but 30 % of 40 pixels is only 12 Smiley)

This section of code is a functional unit, which performs one operation, so it is a perfect candidate for moving  into its own function, so we can call it from the Update() method:

MoveSourceRectangleTowardsDestination();

This method of cutting parts of your code out and making them into their own well-named methods is a good idea. It keeps your code readable for both yourself and others as it gets more complex.

That's it!

We're done - we've got a control which works, and we can use it with very few lines of code in any game.

Your next assigment is to create a control which let's the user slide selections sideways Smiley med åben mund Have fun!

Here's the final code:

public class VisualSelectionControl : DrawableGameComponent
{

    #region Variables and properties
    //the position (top-left corner) of the menu
    public Vector2 Position { get; set; }
    //the spritebatch to draw to
    public SpriteBatch SpriteBatch { get; set; }
    //stores the entire menu in one big picture
    public Texture2D MenuTexture { get; private set; }
    //how many selectable items the texture should be split into
    public int NumberOfPossibleSelections { get; private set; }
    //Currently selected index 
    private int _currentIndex;
    //stores the states of the keyboard
    private KeyboardState _currentKeyboardState, _previousKeyboardState;
    //store the source rectangle
    private Rectangle _sourceRectangle;
    //stores the height of one menu item
    private float _heightOfOneMenuItem;

    public int CurrentIndex
    {
        get { return _currentIndex; }
        //ensures only valid values are set
        set
        {
            if (value >= 0 &amp;amp;&amp;amp; value < NumberOfPossibleSelections)
            {

                //calculate the height of one menuitem
                int heightOfOneMenuItem = MenuTexture.Height / NumberOfPossibleSelections;

                _currentIndex = value;

            }
        }
    }
    #endregion

    #region Constructor
    //creates a VisualSelectionControl by first creating a DrawableGameComponent
    //takes a Game object, and passes it on to the superclass (DrawableGameComponent)
    public VisualSelectionControl(Game game, Vector2 position,
                                    SpriteBatch spriteBatch, Texture2D menuTexture,
                                    int numberOfPossibleSelections)
        : base(game)
    {
        //store parameters
        Position = position;
        SpriteBatch = spriteBatch;
        MenuTexture = menuTexture;
        NumberOfPossibleSelections = numberOfPossibleSelections;
        //calculate the height of one menuitem
        _heightOfOneMenuItem = MenuTexture.Height / NumberOfPossibleSelections;
        //create the source rectangle
        _sourceRectangle = new Rectangle(0, 0, MenuTexture.Width, (int)_heightOfOneMenuItem);
    } 
    #endregion

    #region Draw and Update
    public override void Draw(GameTime gameTime)
    {
        //call the superclass' implementation of the method we're overriding
        base.Draw(gameTime);
        //draw to spritebatch, at control's position, from the sourcerectangle
        SpriteBatch.Draw(MenuTexture, Position, _sourceRectangle, Color.White);
    }

    public override void Update(GameTime gameTime)
    {
        //get the keyboard's state
        _currentKeyboardState = Keyboard.GetState();
        //call the superclass' implementation of the method we're overriding
        base.Update(gameTime);
        //use input to change state
        if (_currentKeyboardState.IsKeyDown(Keys.Up) &amp;amp;&amp;amp; _previousKeyboardState.IsKeyUp(Keys.Up)) { MoveToPrevious(); }
        if (_currentKeyboardState.IsKeyDown(Keys.Down) &amp;amp;&amp;amp; _previousKeyboardState.IsKeyUp(Keys.Down)) { MoveToNext(); }

        MoveSourceRectangleTowardsDestination();

        //store this Update()'s keyboard state for use in next Update
        _previousKeyboardState = _currentKeyboardState;
    } 
    #endregion

    #region Helpermethods
    private void MoveSourceRectangleTowardsDestination()
    {
        //calculate where the sourcerectangle is supposed to be on the Y axis of the texture
        float sourceRectangleYDestination = _heightOfOneMenuItem * CurrentIndex;
        //get the difference between where it is now and where it is supposed to be
        float differenceOnYAxis = sourceRectangleYDestination - _sourceRectangle.Top;
        //calculate a thrirty percent movement
        float thirtyPercentMovement = differenceOnYAxis * .3f;
        //move the source rectangle
        _sourceRectangle.Offset(0, (int)thirtyPercentMovement);
    }

    public void MoveToPrevious() { CurrentIndex--; }
    public void MoveToNext() { CurrentIndex++; } 
    #endregion

}

And for the Game class:

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    VisualSelectionControl _selectionControl;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        Texture2D _levelselectionTexture = Content.Load<Texture2D>("doom_difficulty");
        _selectionControl = new VisualSelectionControl(this, Vector2.One * 100, spriteBatch, _levelselectionTexture, 5);
        Components.Add(_selectionControl);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.DarkRed);
        spriteBatch.Begin();
        base.Draw(gameTime);
        spriteBatch.End();
    }
}

Class diagram

Here you can see how our VisualSelectionControl inherits the DrawableGameComponent, which in turn inherits the GameComponent

image

 

Source code for download

Here's the code Smiley

Leave a Reply