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:
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:
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:
We can see that selecting menu items works well.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 130x (17.59 MB)
Application includes source codes