Discount week - May Swift week
Save up to 80 % on our Swift e-learning courses. Only this week!
Get up to 60 % extra points for free! More info

Lesson 13 - Tetris in MonoGame: Game Menu

In the previous lesson, Tetris in MonoGame: Game Scene Management, we implemented a game scene management system and added a game menu component.

Nothing prevents us from making the menu reality today :)

We, of course, want our menu to be nice and with visual effects. First, we'll add a new background texture to our MenuComponent. Download it here:

Menu background

and add it into the Sprites/ folder. Also add the respective field to the class:

private Texture2D background;

We'll load the texture in LoadContent():

background = robotrisGame.Content.Load<Texture2D>(@"Sprites\background_menu");

And render it in Draw():

robotrisGame.spriteBatch.Begin();
robotrisGame.spriteBatch.Draw(background, new Vector2(0, 0), Color.White);
robotrisGame.spriteBatch.End();

The result:

Sample Tetris game in MonoGame

We'll divide the menu into two components to make it reusable in other games and game scenes without needing any major changes. The MenuComponent class will have only those menu parts that are specific to Robotris. We'll also add another more universal drawable component to manage menu items. The component will be named MenuItemsComponent. Here's its empty source code that we'll start implementing (keep in mind that the namespace should be only Robotris, even it's in the Components/ subfolder):

namespace Robotris
{
    public class MenuItemsComponent : Microsoft.Xna.Framework.DrawableGameComponent
    {

        private RobotrisGame robotrisGame;

        public MenuItemsComponent(RobotrisGame robotrisGame)
            : base(robotrisGame)
        {
            this.robotrisGame = robotrisGame;
        }

        public override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            base.LoadContent();
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
        }
    }
}

We'll leave it empty for now. Instead, we'll add another class directly into the Robotris project, which will represent the menu item.

Menu Item

We'll name the new class MenuItem. It'll contain its screen coordinates, size, and text. The item size is quite important because we'll want to scale the item and animate it. The class is very simple, here's its source code:

public class MenuItem
{
    public string text;
    public Vector2 position;
    public float size;

    public MenuItem(string text, Vector2 position)
    {
        this.text = text;
        this.position = position;
        size = 0.6f;
    }
}

To use the Vector2 type structure, we'll add the following using statement:

using Microsoft.Xna.Framework;

Let's move to MenuItemsComponent. This component will manage, store, switch between, and render the menu items. We'll add the item-specific events (such as when clicking an item) later in MenuComponent, because they're specific to our game.

We'll add an item collection, the currently selected item, and the menu position as class fields:

private List<MenuItem> items;
public MenuItem selectedItem;
private Vector2 position;

We'll try to make the component as customizable as possible, so we'll add the item color, selected item color, and the text size:

private Color itemColor;
private Color selectedItemColor;
private int textSize;

Let's add some more parameters to the constructor, and initialize the class fields:

public MenuItemsComponent(RobotrisGame robotrisGame, Vector2 position, Color itemColor, Color selectedItemColor, int textSize): base(robotrisGame)
{
    this.position = position;
    this.robotrisGame = robotrisGame;
    this.itemColor = itemColor;
    this.selectedItemColor = selectedItemColor;
    this.textSize = textSize;
    items = new List<MenuItem>();
    selectedItem = null;
}

Menu Items Management

Let's add several methods to work with the menu items collection. We'll start with adding a new item. The method will take the item text in its parameter, then it'll create a new menu item and set its position below the existing menu items. The item will then be added into the internal collection, and if there's no item selected, the method will select the newly created one (so the first item added to the menu will always be selected). The method could look like this:

public void AddItem(string text)
{
    // setting up the position according to the item's collection index
    Vector2 p = new Vector2(position.X, position.Y + items.Count * textSize);
    MenuItem item = new MenuItem(text, p);
    items.Add(item);
    // selecting the first item
    if (selectedItem == null)
        selectedItem = item;
}

After adding the items, we'll need to select the next and the previous item in the menu. Except for the transition from the last to the first item, there won't be anything interesting in the item selecting method. Since we store the selected item instance only, not its current index, we must find its index everytime. Luckily, it's not a big issue in our case.

public void SelectNext()
{
    int index = items.IndexOf(selectedItem);
    if (index < items.Count - 1)
        selectedItem = items[index + 1];
    else
        selectedItem = items[0];
}

The method for selecting the previous item will be very similar:

public void SelectPrevious()
{
    int index = items.IndexOf(selectedItem);
    if (index > 0)
        selectedItem = items[index - 1];
    else
        selectedItem = items[items.Count - 1];
}

Updating Items

We'll set the down and up keys to switch to the next and previous item. Let's move to Update() and implement this behavior there:

// key pressing
if (robotrisGame.NewKey(Keys.Up))
    SelectPrevious();
if (robotrisGame.NewKey(Keys.Down))
    SelectNext();

Rendering Items

Because we need to specify the text size of our items as well, the TextWithShadow() method won't be enough. Therefore, we'll add another DrawString() method overload to the BetterSpriteBatch class, which will render scaled text with a shadow:

public void TextWithShadowScaled(SpriteFont spriteFont, string text, Vector2 position, Color color, float size)
{
    DrawString(spriteFont, text, new Vector2(position.X + 2, position.Y + 2), Color.Black * 0.8f, 0.0f, new Vector2(0, 0), size, SpriteEffects.None, 0);
    DrawString(spriteFont, text, position, color, 0.0f, new Vector2(0, 0), size, SpriteEffects.None, 0);
}

Now it's easy for us to draw the menu item text in Draw():

robotrisGame.spriteBatch.Begin();
foreach (MenuItem item in items)
{
    Color color = itemColor;
    if (item == selectedItem)
        color = selectedItemColor;
    robotrisGame.spriteBatch.TextWithShadowScaled(robotrisGame.fontBlox, item.text, item.position, color, item.textSize);
}
robotrisGame.spriteBatch.End();

You can see a dependency to the fontBlox font. Ideally, we'd pass the font, but because the font is loaded after the LoadContent() method is called, it'd be very complicated. So we'll rather keep it as simple as possible.

Nesting Components

Now we'll put the MenuItemsComponent component into MenuComponent. We haven't put a component into another one yet, so let's try it now. Let's move into MenuComponent.cs and add a field to store the MenuItemsComponent instance there:

private MenuItemsComponent menuItems;

However, we'll create this instance in RobotrisGame.cs, because we need to add the component into a game scene there. Of course, it could be solved in different ways, but let's prefer the most simple one. Then we'll pass this instance to MenuComponent via constructor, which we'll now modify:

public MenuComponent(RobotrisGame robotrisGame, MenuItemsComponent menuItems)
    : base(robotrisGame)
{
    this.robotrisGame = robotrisGame;
    this.menuItems = menuItems;
}

Let's move to Initialize() in RobotrisGame.cs, and create the MenuItemsComponent instance there:

MenuItemsComponent menuItems = new MenuItemsComponent(this, new Vector2(700, 250), Color.Red, Color.Yellow, 80);

And right below it, we'll add several items to the menu:

menuItems.AddItem("StArT");
menuItems.AddItem("ToP ScOrE");
menuItems.AddItem("CrEdItS");
menuItems.AddItem("QuIt");

In the texts we switch upper and lower case letters, because it looks good with the Blox font :)

We'll pass menuItems to the MenuComponent constructor call:

MenuComponent menu = new MenuComponent(this, menuItems);

And we'll do the same to the GameScene constructor:

menuScene = new GameScene(this, clouds, menu, menuItems);

Finally, we'll open the font_blox.spritefont file in the Fonts/ folder, and change the Style from Regular to Bold. When we run the project, we should get the following result:

Sample Tetris game in MonoGame

We can see that selecting menu items works well.


 

Download

Downloaded 0x (17.59 MB)
Application includes source codes

 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Previous article
Tetris in MonoGame: Game Scene Management
All articles in this section
Tetris From Scratch
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!