Cómo hacer un Menú gráfico en XNA


Este es otro tutorial de Menús para XNA (de tantos que hay en la red), voy a mostrar cómo crear un efecto de Zoom en el Item seleccionado, mostrar una imagen en el Item Seleccionado y usar el Mouse y el teclado para seleccionarlos.

El menú que voy a usar va a tener una lista donde va a guardar la descripción o el texto de cada ítem, otra lista donde se va a guardar la posición y la escala que nos va a servir para darle el efecto de Zoom.

 

La clase Menú es la siguiente:

class Menu
{
private static int MAX = 20; // máximo de Items en el menú
private List<String> itemsMenu; // lista de los textos de cada Item
private List<Vector2> posicionItems; // lista de la posición de cada Item
private List<Double> escala; // es la escala de cada menú
private Color noSeleccionado;       //Color del Item que no está seleccionado
private Color seleccionado;               //Color del Item Seleccionado
private SpriteFont fuente;          // Sprite para la lera
  private Texture2D check;            // imagen que sirve para poner antes del texto, puede ser null
public int ItemActual { get; set; } //es el Index o id del Item actual seleccionado

 // propiedad que muestra el total de items del Menú
public int totalItems
{
get
{
return itemsMenu.Count;
}
}
// propiedad que devuelve el tamaño que ocupa el texto
public Rectangle tamanoItem(int indexItem)
{
return new Rectangle((int)posicionItems[indexItem].X,
(int)posicionItems[indexItem].Y,
(int)fuente.MeasureString(itemsMenu[indexItem]).X,
(int)fuente.MeasureString(itemsMenu[indexItem]).Y);
}

public Menu(Color noSeleccionadoColor, Color seleccionadoColor, SpriteFont sp, Texture2D textura)
{
if (textura != null)
{
check = textura;
}
fuente = sp;
itemsMenu = new List<string>();
posicionItems = new List<Vector2>();
escala = new List<double>();
noSeleccionado = noSeleccionadoColor;
seleccionado = seleccionadoColor;
ItemActual = 0;
}

// función para agregar Items al menú
public void addItemMenu(String descripcion, Vector2 posicion)
{
if (totalItems < MAX)
{
itemsMenu.Add(descripcion);
posicionItems.Add(posicion);
escala.Add(1.0f);
}
}

public void seleccionarSiguiente()
{
if (ItemActual < totalItems - 1)
{
ItemActual++;
}
else
{
ItemActual = 0;
}
}

public void seleccionarAnterior()
{
if (ItemActual > 0)
{
           ItemActual--;
}
else
{
ItemActual = totalItems - 1;
}
}

      public void Update(GameTime gameTime)
{
for (int x = 0; x < totalItems; x++)
{
if (x == ItemActual)
{
if (escala[x] < 2.0f)
{
escala[x] += 0.04 + 10.0f * gameTime.ElapsedGameTime.Seconds;
}
}
else if (escala[x] > 1.0f && x != ItemActual)
{
escala[x] -= 0.04 + 10.0f * gameTime.ElapsedGameTime.Seconds;
}
}
}

public void Draw(SpriteBatch spriteBatch)
      {
spriteBatch.Begin();
for (int x = 0; x < totalItems; x++)
{
if (x == ItemActual)
           {
Vector2 p = posicionItems[x];
p.X -= (float)(escala[x] / 2);
p.Y -= (float)(escala[x] / 2);
if (check != null)
{
spriteBatch.Draw(check, new Vector2(p.X - check.Width, p.Y), Color.White);
}
spriteBatch.DrawString(fuente, itemsMenu[x], p, seleccionado, 0.0f, new Vector2(0, 0), (float)escala[x], SpriteEffects.None, 0);
}
else
{
Vector2 p = posicionItems[x];
p.X -= (float)(escala[x] / 2);
p.Y -= (float)(escala[x] / 2);
spriteBatch.DrawString(fuente, itemsMenu[x], p, noSeleccionado, 0.0f, new Vector2(0, 0), (float)escala[x], SpriteEffects.None, 0);
}
}
spriteBatch.End();
}
}

En el Constructor de la clase, recibimos como parámetros:

–          El color de los Items que están Seleccionados

–          El color de los Items que no están Seleccionados

–          El tipo de Fuente que se va a usar (SpriteFont)

–          La textura de una imagen que va a estar antes del texto del Item

La función addItemMenu adiciona el texto, la posición del Item

seleccionarSiguiente selecciona el Item Siguiente de la lista, si llega al final se devuelve al inicio para comenzar el recorrido

seleccionarAnterior Selecciona el Item anterior de la lista

La función Update verifica los items, y a los seleccionados les duplica el tamaño suavemente y a los que no están seleccionados los deja en su tamaño normal.

La función Draw, dibuja cada Item, si esta seleccionado se le cambia el Color al color de “seleccionado”, si no están seleccionado se dejan en el color de “No seleccionado”, además de dibujar una textura el Item Seleccionado para que el Usuario sepa cuál tiene seleccionado.

La propiedad itemActual es útil para saber que Item está seleccionado, esta propiedad representa el índice de la lista y nos sirve para dibujar, actualizar y saber que Item seleccionamos y tomar una desición.

La propiedad totalItems es útil para saber cuántos ítems tiene el Menú

La propiedad tamanoItem nos sirve para usar el Mouse y saber si el Mouse se encuentra sobre el texto, esta propiedad devuelve un rectángulo que tiene el tamaño que ocupa el texto.

Ahora para usar nuestra clase hacemos lo siguiente:

Declaramos las variables que vamos a usar:

Menu mimenu; // clase del Menú
Int32 mx, my; // posiciones del puntero del mouse
bool mpressed, prev_mpressed = false; // variables usadas en el control del botón del Mouse
bool menuUp, menuDown = false; // variables usadas en el control de las teclas

En la función Initialize:

IsMouseVisible = true;

En la función LoadContent:

mimenu = new Menu(Color.White, Color.Red, Content.Load<SpriteFont>("sp"), Content.Load<Texture2D>("Check"));
mimenu.addItemMenu("Iniciar", new Vector2(100, 120));
mimenu.addItemMenu("Guardar", new Vector2(100, 220));
mimenu.addItemMenu("Cargar", new Vector2(100, 320));
mimenu.addItemMenu("Salir", new Vector2(100, 420));

He adicionado antes un SpriteFont y una imagen que va a representar el icono de Seleccionado:

 

En la función Update:

mimenu.Update(gameTime);
MouseState mouse_state = Mouse.GetState();
mx = mouse_state.X;
my = mouse_state.Y;
prev_mpressed = mpressed;
mpressed = mouse_state.LeftButton == ButtonState.Pressed;
for (int x = 0; x < mimenu.totalItems; x++)
{
if(clic_Item(mimenu.tamanoItem(x),mx,my))
{
mimenu.ItemActual = x;
if (!mpressed && prev_mpressed)
{
switch (mimenu.ItemActual)
{
case 0:
break;
case 1:
             break;
case 2:
break;
case 3:
this.Exit();
break;
}
}
}
 }

KeyboardState k = Keyboard.GetState();
if (k.IsKeyDown(Keys.Up) && menuUp)
{
mimenu.seleccionarAnterior();
menuUp = false;
}
else if (k.IsKeyUp(Keys.Up))
{
menuUp = true;
}
if (k.IsKeyDown(Keys.Down) && menuDown)
{
mimenu.seleccionarSiguiente();
menuDown = false;
   }
else if (k.IsKeyUp(Keys.Down))
{
menuDown = true;
}
if (k.IsKeyDown(Keys.Enter))
{
switch (mimenu.ItemActual)
{
case 0:
break;
     case 1:
break;
case 2:
break;
case 3:
this.Exit();
break;
}
}

Usamos una función clic_item que nos devuelve true cuando el Mouse se encuentra en el rectángulo imaginario del tamaño del Item, y false cuando no se encuentra cerca de ningún ítem, si se encuentra sobre un ítem, se selecciona ese Item.

Además de manejar la interacción con el teclado, para que solo se pueda mover de un ítem cuando una tecla es presionada, en este ejemplo solo el Item de Salir funciona.

Función clic_Image:

Boolean clic_Item(Rectangle rect, int x, int y)
{
return (x >= rect.X &&
x <= rect.X + rect.Width &&
y >= rect.Y &&
y <= rect.Y + rect.Height);
}

Función Draw:

GraphicsDevice.Clear(Color.CornflowerBlue);
mimenu.Draw(spriteBatch);

Ahora si ejecutamos el ejemplo vemos lo siguiente:

 

Código Fuente Aquí.

Fuentes:

Antiguo Tutorial de Ziggyware hecho por Dane Anderson

12 pensamientos en “Cómo hacer un Menú gráfico en XNA

  1. Pingback: Administrador de Escenas – XNA « Escarbando Código

  2. Hola queria hacerte una pregunta respecto al codigo:

    Cuando intento añadir un elemento mas al menu, por ejemplo:

    mimenu.addItemMenu(“Opcion”, new Vector2(100, 20));

    Me salta un error en la clase Menu, concretamente en el metodo update, en la linea:

    else if (escala[x] > 1.0f && x != ItemActual)

    y el error es:

    El índice estaba fuera del intervalo. Debe ser un valor no negativo e inferior al tamaño de la colección.
    Nombre del parámetro: index

    El error sale cuando añado un elemento mas al menu, o quito uno, es como si el menu estuviese hecho para solo albergar 4 opciones.

    Alguna idea?

    Muchas gracias, de antemano.

    • Hola, ya vi el error😦, ocurre con la propiedad totalItems, no debería hacerlo con itemsMenu.Capacity, ya que esta propiedad Cpacity, esta propiedad no guarda el total de items en la Lista, sino que muestra algo así como la cantidad de memoria o items reservados. La corrección ya la hice en la página, pero si algo lo correcto es:

      public int totalItems
      {
      get
      {
      return itemsMenu.Count;
      }
      }

  3. Muy buenas yo veo un problema aqui, sabes es que no me entero de nada de lo que estoy haciendo por tanto para mi esto no es programar, estoy empezando en esto del XNA y no me entero de nada, por tanto me gustaria tener una explicacion mejor de la que das aqui para poder enterarme de todo, tienes mi cuenta de facebook cuando veas mi nombre y apellidfos sabras quien soy, gracias.

    • Hola, a que te refieres a que salte a otro frame? a que salte a otra pantalla? Si es así, en las siguientes líneas, puedes saber sobre que opción se hizo enter y realizar alguna acción:
      if (k.IsKeyDown(Keys.Enter))
      {
      switch (mimenu.ItemActual)
      {
      case 0:
      //Se hizo enter en la opción uno, realizar cualquier acción
      break;
      case 1:
      //Se hizo enter en la opción dos, realizar cualquier acción
      break;
      case 2:
      //Se hizo enter en la opción tres, realizar cualquier acción
      break;
      case 3:
      this.Exit();
      break;
      }

  4. Hola Martín, muchas gracias por el código, muy útil. Estoy haciendo un juego en xna para mi trabajo fin de grado, te importaría que usase este código para el menú principal?
    Muchas gracias

Deseas comentar o sugerir algo?

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s