I'm working on a RTS like game and just implemented context menus. When you click on a tree or anything a context menu will popup with the available options. It works, but I'd like to know if it could be done nicer / better.
I've got a ContextMenu class where the drawing and events are handled, a Tree class to show how it's used and a part of Game1.cs:
ContextMenu.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace MyGame
{
public class ContextMenu
{
public static int minMenuWidth = 64;
public static int minMenuHeight = 64;
public static Sprite contextMenuBackground;
public static Sprite contextMenuBorder;
private Game1 theGame;
private Vector2 position;
private bool visible;
private GameObject target;
private List<ContextMenuItem> itemList = new List<ContextMenuItem>();
private int width = minMenuWidth;
private int height = minMenuHeight;
public ContextMenu(Game1 g, List<ContextMenuItem> items)
{
theGame = g;
foreach (ContextMenuItem item in items)
{
if (9 * item.getText().Length > width)
width = 9 * item.getText().Length;
}
itemList = items;
target = null;
}
public ContextMenu(Game1 g)
{
theGame = g;
target = null;
}
public bool onClick(MouseState mouseState)
{
Point mousePosition = new Point(mouseState.X, mouseState.Y);
Rectangle menuHitBox = new Rectangle((int)position.X, (int)position.Y, width, height);
if (menuHitBox.Contains(mousePosition))
{
for (int i = 0; i < itemList.Count; i++)
{
ContextMenuItem item = itemList;
Rectangle hitBox = new Rectangle((int)position.X + 10, (int)position.Y + 8 + 12 * (i), width - 20, 12);
if (hitBox.Contains(mousePosition))
{
item.onClick(target);
return true;
}
}
return true;
}
return false;
}
public void setTarget(GameObject o)
{
target = o;
}
public GameObject getTarget()
{
return target;
}
public void updateHovers(MouseState mouseState)
{
Point mousePosition = new Point(mouseState.X, mouseState.Y);
Rectangle menuHitBox = new Rectangle((int)position.X, (int)position.Y, width, height);
if (menuHitBox.Contains(mousePosition))
{
for (int i = 0; i < itemList.Count; i++)
{
ContextMenuItem item = itemList;
item.setHovering(false);
Rectangle hitBox = new Rectangle((int)position.X + 10, (int)position.Y + 8 + 12 * (i), width - 20, 12);
if (hitBox.Contains(mousePosition))
{
item.setHovering(true);
}
}
}
}
public Rectangle getHitBox()
{
return new Rectangle((int)position.X, (int)position.Y, width, height);
}
public bool isVisible()
{
return visible;
}
public void addContextMenuItem(ContextMenuItem item)
{
itemList.Add(item);
}
public ContextMenuItem getContextMenuItem(int i)
{
if (i >= 0 && i < itemList.Count)
return itemList;
else
return null;
}
public void draw()
{
if (!visible || itemList.Count == 0)
return;
// bg
for (int y = 0; y < ((float)height / 64); y++)
{
for (int x = 0; x < ((float)width / 64); x++)
{
if (y * 64 + 64 <= height && x * 64 + 64 <= width)
{
theGame.spriteBatch.Draw(contextMenuBackground.getTexture(), new Rectangle((int)position.X + 64 * x, (int)position.Y + 64 * y, 64, 64), contextMenuBackground.getSourceRectangle(0), Color.White);
}
else
{
Rectangle sourceRect = contextMenuBackground.getSourceRectangle(0);
sourceRect.Width = width - x * 64;
sourceRect.Height = height - y * 64;
theGame.spriteBatch.Draw(contextMenuBackground.getTexture(), new Rectangle((int)position.X + 64 * x, (int)position.Y + 64 * y, sourceRect.Width, sourceRect.Height), sourceRect, Color.White);
}
}
}
// left top
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X, (int)position.Y, 8, 8), contextMenuBorder.getSourceRectangle(0), Color.White);
// right top
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X + width - 8, (int)position.Y, 8, 8), contextMenuBorder.getSourceRectangle(1), Color.White);
// left
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X, (int)position.Y + 8, 8, height - 16), contextMenuBorder.getSourceRectangle(4), Color.White);
// right
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X + width - 8, (int)position.Y + 8, 8, height - 16), contextMenuBorder.getSourceRectangle(5), Color.White);
// top
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X + 8, (int)position.Y, width - 16, 8), contextMenuBorder.getSourceRectangle(7), Color.White);
// bottom
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X + 8, (int)position.Y + height - 8, width - 16, 8), contextMenuBorder.getSourceRectangle(6), Color.White);
// left bottom
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X, (int)position.Y + height - 8, 8, 8), contextMenuBorder.getSourceRectangle(2), Color.White);
// right bottom
theGame.spriteBatch.Draw(contextMenuBorder.getTexture(), new Rectangle((int)position.X + width - 8, (int)position.Y + height - 8, 8, 8), contextMenuBorder.getSourceRectangle(3), Color.White);
// Draw the items:
for (int i = 0; i < itemList.Count; i++)
{
ContextMenuItem item = itemList;
if (item.isHovering())
{
Rectangle hitBox = new Rectangle((int)position.X + 9, (int)position.Y + 9 + 12 * (i), width - 18, 12);
theGame.spriteBatch.Draw(theGame.dummy, new Vector2((float)hitBox.X, (float)hitBox.Y), null, new Color(255, 255, 255) * 0.3f, 0f, Vector2.Zero, new Vector2(hitBox.Width, hitBox.Height), SpriteEffects.None, 0f);
}
theGame.drawShadedString(theGame.spriteBatch, theGame.defaultFont, item.getText(), new Vector2((int)position.X + 10, (int)position.Y + 8 + 12*(i)), Color.White, 1.0f, new Vector2(1.0f, 1.0f), Color.Black);
}
}
public void setPosition(int x, int y)
{
position.X = (float) x;
position.Y = (float) y;
}
public void setWidth(int w)
{
width = w;
}
public void setHeight(int h)
{
height = h;
}
public void show()
{
visible = true;
}
public void hide()
{
visible = false;
}
}
}
Tree.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace MyGame
{
class Tree : ResourceObject
{
public static int treeWidth = 16;
public static int treeHeight = 16;
public static double maxTreeHealth = 100.0;
public static ContextMenu contextMenu;
private Sprite sprite;
private Random rand;
public Tree(Game1 g, Level l, Sprite s, int x, int y)
{
// bla bla
}
public override void tick(GameTime gameTime)
{
}
public override void draw()
{
theGame.spriteBatch.Draw(sprite.getTexture(), new Rectangle((int)position.X, (int)position.Y, width, height), sprite.getSourceRectangle(), Color.White);
}
public override ContextMenu getContextMenu()
{
return contextMenu;
}
}
}
The action that will happen when an item is clicked is defined in Game1.cs:
List<ContextMenuItem> menuItemlist = new List<ContextMenuItem>();
menuItemlist.Add(new ContextMenuItem("Chop tree", delegate(GameObject o)
{
if (o != null)
{
if (selectedAnts.Count > 0)
{
foreach (Ant selectedAnt in selectedAnts)
{
Point walkTarget = new Point(o.getX(), o.getY());
selectedAnt.setTarget(o);
selectedAnt.walkNear(walkTarget);
contextMenu.hide();
}
}
}
}));
Tree.contextMenu = new ContextMenu(this, menuItemlist);
Is this a good way of doing this? Using delegates as parameters for menu items?
Another idea I had was the following:
Create a IContextMenu interface with 2 methods: getContextMenuItems() and contextMenuOnClick(ContextMenuItem clickedItem) a right-clickable gameobject will than implement this interface. The drawing and handling of the clicks are than handled within Game1.cs.
Thanks
Bas