Robot Foot Games Helping you improve your games as we improve our own. :)

22May/112

Multiple Column Menus

This tutorial is based on theĀ Game State Management (singleplayer games) from the xna website but theĀ Network Game State Management (multiplayer games) can also be used based on your needs. There's no better place to start and if you're new to the XNA world it's a great resource to use and learn from. For this tutorial I'll be using the game state management but both should be nearly identical with the exception of the networking menus.

This tutorial focuses on adding left and right movement to your menus so you can have multiple columns of menus. Check out the video below for an example of the final product. NOTE: I have debug code in to modify the number of columns and number of menu entries just to show how you can scale it very easily. You can download the project by clicking on the zipped folder below. Left/right triggers add/remove menu entries and left/right shoulder buttons increase/decrease the number of columns.

First off open InputState.cs and we're going to replace the IsMenuUp(...) and IsMenuRigh(...) methods and add 2 new ones for our left/right movement. If you have a better way to handle the analog input (thumbsticks) than I have below please let me know!

//The code below for analog input (thumbsticks) ONLY responds to controller #1.
//The commented out lines don't give a leeway to joystick input so navigating
//the menus with a joystick is impossible if you use the commented out line
//instead of the 2 lines below it.
public bool IsMenuUp(PlayerIndex? controllingPlayer)
{
    PlayerIndex playerIndex;
    return IsNewKeyPress(Keys.Up, controllingPlayer, out playerIndex) ||
           IsNewButtonPress(Buttons.DPadUp, controllingPlayer, out playerIndex) ||
           //IsNewButtonPress(Buttons.LeftThumbstickUp, controllingPlayer, out playerIndex) ||
           (CurrentGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.Y > 0.5f &&
           LastGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.Y < 0.5f);
}
public bool IsMenuDown(PlayerIndex? controllingPlayer)
{
    PlayerIndex playerIndex;
    return IsNewKeyPress(Keys.Down, controllingPlayer, out playerIndex) ||
           IsNewButtonPress(Buttons.DPadDown, controllingPlayer, out playerIndex) ||
           //IsNewButtonPress(Buttons.LeftThumbstickUp, controllingPlayer, out playerIndex) ||
           (CurrentGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.Y < -0.5f &&
           LastGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.Y > -0.5f);
}
public bool IsMenuLeft(PlayerIndex? controllingPlayer)
{
    PlayerIndex playerIndex;
    return IsNewKeyPress(Keys.Left, controllingPlayer, out playerIndex) ||
           IsNewButtonPress(Buttons.DPadLeft, controllingPlayer, out playerIndex) ||
           //IsNewButtonPress(Buttons.LeftThumbstickUp, controllingPlayer, out playerIndex) ||
           (CurrentGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.X < -0.5f &&
           LastGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.X > -0.5f);
}
public bool IsMenuRight(PlayerIndex? controllingPlayer)
{
    PlayerIndex playerIndex;
    return IsNewKeyPress(Keys.Right, controllingPlayer, out playerIndex) ||
           IsNewButtonPress(Buttons.DPadRight, controllingPlayer, out playerIndex) ||
           //IsNewButtonPress(Buttons.LeftThumbstickUp, controllingPlayer, out playerIndex) ||
           (CurrentGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.X > 0.5f &&
           LastGamePadStates[(int)PlayerIndex.One].ThumbSticks.Left.X < 0.5f);
}

 

Now we move onto MenuScreen.cs. Let's add this variable to the top and declare it protected so any menu screen that inherits from MenuScreen (which is all of them I believe) can change this variable to support any number of columns they would like.

protected int numberOfColumns = 1;

In the HandleInput(...) method you have 2 if statements relating to IsMenuUp and IsMenuDown. We'll replace those with this block of code which slightly modifies them and adds input for moving left and right in our menus.

if (input.IsMenuUp(ControllingPlayer))
{
    selectedEntry--;
    if (selectedEntry < 0)
        selectedEntry = menuEntries.Count - 1;
}
else if (input.IsMenuDown(ControllingPlayer))
{
    selectedEntry++;
    if (selectedEntry >= menuEntries.Count)
        selectedEntry = 0;
}
else if (input.IsMenuRight(ControllingPlayer))
{
    selectedEntry += (int)Math.Ceiling(menuEntries.Count / (float)numberOfColumns);
    if (selectedEntry >= menuEntries.Count)
        selectedEntry = menuEntries.Count - 1;
}
else if (input.IsMenuLeft(ControllingPlayer))
{
    selectedEntry -= (int)Math.Ceiling(menuEntries.Count / (float)numberOfColumns);
    if (selectedEntry < 0)
        selectedEntry = 0;
}

 

Move onto the Draw(...) method in MenuScreen.cs. There's a for loop here and you'll want to replace it with this code. The comments should hopefully explain what's going on some more.

//This will get the # of rows we want in each column
int maxRowsPerColumn = (int)Math.Ceiling(menuEntries.Count / (float)numberOfColumns);
// Draw each menu entry in turn.
for (int i = 0; i < menuEntries.Count; i++)
{
    MenuEntry menuEntry = menuEntries[i];
    bool isSelected = IsActive && (i == selectedEntry);
    menuEntry.Draw(this, position, isSelected, gameTime);
    position.Y += menuEntry.GetHeight(this);
    //We need to handle an odd # of menu entries different
    //from an even # of entries so we mod to figure out
    //if we need to start a new column.
    if (maxRowsPerColumn % 2 == 2) //Even
    {
        if (i % maxRowsPerColumn == maxRowsPerColumn)
        {
            //We use 'hard' values in here to keep the rows and columns
            //lined up perfectly. These will vary greatly from project to
            //project based on what you want to do so make sure to play
            //with them to get a result you like.
            position.X += 200;
            //This variable should be the same as the 'position' value
            //set at the top of this method. It looks like this (for me)
            //so make sure to match the Y value:
            //Vector2 position = new Vector2(100, 150);
            position.Y = 150;
        }
    }
    else //Odd
    {
        if (i % maxRowsPerColumn == maxRowsPerColumn - 1)
        {
            //Same comments from above apply here.
            position.X += 200;
            position.Y = 150;
        }
    }
}

 

And that's it for the code changes! To set the number of columns you want for each screen you can do it in the constructor. We'll use MainMenuScreen.cs since that's probably the easiest way to show it off. In the constructor you'll see some lines that look like this:

MenuEntries.Add(playGameMenuEntry);

and you'll need to add this line after those:

numberOfColumns = 2;

 

You can change the 2 to whatever you'd like just try to be reasonable with it. If you have 100 menu entries and you try to put them in 1 column they're not going to fit very well on screen. Same goes for putting 2 menu entries in 6 columns...

Comments (2) Trackbacks (0)
  1. I would like to do something slightly different from this example, using only 1 column. If I have 10 menu entries stored in a list, how can I display the first 5. And if the user presses the left/right directional button, the first 5 menu entries transitions off and the remaining 5 transitions on?

    • The easiest way would be to make it 2 different screens with 5 menu options each. The GameStateManagement sample has built in transitions already so if you just move between the 2 screens then you’ll get the transition effect without any extra work!


Leave a comment

(required)

No trackbacks yet.