Cómo hacer un Puzzle Bobble o Bust a Move en XNA – Parte 1


En este post voy a mostrarles una forma para crear un clon del famoso juego Puzzle Bobble, puede que no sea el mejor algoritmo pero funciona Lengua fuera.

El juego Puzzle Bobble para los que no sepan, es un juego donde existe un área rectangular y hay unas burbujas de diferentes colores pegadas en la parte superior del área rectangular, también hay un “puntero” que dispara burbujas de diferentes colores, la burbuja sale disparada y si choca con otra burbuja o llega al límite superior la burbuja queda pegada junto a las otras, si se pega a otras burbujas del mismo color y forma un grupo de 3 o más del mismo color, todas estallan, y cualquier burbuja que estuviera debajo de las que explotaron y no tienen otra burbuja pegada a ellas, también caen.

Imagen tomada del juego Puzzle Bobble de NeoRageX

Ahora que saben bien de que hablamos,  se va a empezar a programar, lo primero que se va a hacer es crear el proyecto y dibujar el puntero, para ello se ha buscado una imagen en internet que se asemeje a una flecha, luego se dibuja en la posición (350,400):

image

        Rotación

Se va a crear una variable float que tendrá la rotación del puntero, y cada vez que se oprima la tecla Izquierda o Derecha, la rotación aumentará o disminuirá, hay que tener en cuenta que en el método Draw se debe indicar la variable rotación y también indicar que el origen de la textura va a ser en el centro de la textura, para que cuando rote, rote sobre el centro de la textura, también para evitar que la imagen rote muy rápido cuando se tenga una de las teclas presionadas,  se guarda en una variable el tiempo transcurridodel juego, y solo si es mayor a 0.05 segundos no se aumenta la rotación, luego se vuelve a dejar la variable en 0.

El código hasta el momento es:

Texture2D launcher;
float elapsed;
float rotacion;

protected override void Initialize()
{
   rotacion = 0f;
   elapsed = 0f;
   base.Initialize();
}

protected override void LoadContent()
{
     spriteBatch = new SpriteBatch(GraphicsDevice);
      launcher = Content.Load("arrow");
}

protected override void Update(GameTime gameTime)
{
  // Allows the game to exit
  if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
     this.Exit();
     elapsed += (float)gameTime.ElapsedGameTime.TotalSeconds;
     if (elapsed > 0.05f)
     {
         if (Keyboard.GetState().IsKeyDown(Keys.Left))
         {
             rotacion -= 0.1f;
         }
         if (Keyboard.GetState().IsKeyDown(Keys.Right))
         {
             rotacion += 0.1f;
         }
         elapsed = 0f;
     }
     base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
  GraphicsDevice.Clear(Color.CornflowerBlue);
  spriteBatch.Begin();
  spriteBatch.Draw(launcher, ubicacionPuntero, null, Color.White, rotacion, new Vector2(launcher.Width/2,launcher.Height/2), 1f, SpriteEffects.None, 1);
  spriteBatch.End();
  base.Draw(gameTime);
}

Disparar Burbujas

Es necesario tener una textura para las burbujas, para empezar se crea una clase llamada Burbuja, que tendrá algunas propiedades como la posición y dirección de la burbuja, también el color que tendrán y una variable que indicará si la bola se mueve o no:

public class Burbuja
{
    public Vector2 Posicion;
    public Vector2 Direccion;

    public Color Color { get; private set; }
    public Boolean moviendo = false;

    public Burbuja(Vector2 posicion, Color color)
    {
        Posicion = posicion;
        this.Direccion = Vector2.Zero;
        Color = color;
    }

    public void Mover()
    {
        if (moviendo)
        {
            this.Posicion += Direccion;
        }
    }
}

Se crea un objeto Burbuja que será lanzado cuando se oprima la tecla Espacio:

Texture2D bubble;
Burbuja burbujaLanzada;
//Initialize
// Se resta la mitad del tamaño de burbuja
burbujaLanzada = new Burbuja(new Vector2(350 - 21, 400 - 21), Color.White);
// LoadContent
bubble = Content.Load("bubble-1");
// Método Update
elapsed += (float)gameTime.ElapsedGameTime.TotalSeconds;

burbujaLanzada.Mover();
// verificar colision con los límites de la pantalla
if (burbujaLanzada.Posicion.X = graphics.PreferredBackBufferWidth)
{
  // se cambia la dirección X para que se vaya en sentido contrario
  burbujaLanzada.Direccion.X *= -1;
}
///...
if (Keyboard.GetState().IsKeyDown(Keys.Space))
{
   burbujaLanzada.moviendo = true;
   // disparar burbuja dependiendo de la rotación del puntero
   // 1.5f es la velocidad
   Matrix m = Matrix.CreateRotationZ(rotacion);
   burbujaLanzada.Direccion.X += m.M12 * 1.5f;
   burbujaLanzada.Direccion.Y -= m.M11 * 1.5f;
}
base.Update(gameTime);
// Método Draw
spriteBatch.Draw(launcher, ubicacionPuntero, null, Color.White, rotacion,new Vector2(launcher.Width/2,launcher.Height/2), 1f, SpriteEffects.None, 1);
spriteBatch.Draw(bubble, burbujaLanzada.Posicion, burbujaLanzada.Color);

Si se ejecuta la aplicación, se puede ver que al mover el puntero y luego oprimir la tecla espacio, la burbuja es disparada y cuando choca con los límites derecho e izquierdo de la pantalla, la burbuja rebota.

image

Falta dibujar un rectángulo que define el áre de juego, también se crean unas constantes que se utilizarán como límites, para dibujar el rectángulo o el área donde se va a jugar se utiliza una textura de 1X1 pixeles, también se modifican las colisiones para que la burbuja se detenga en cuanto choque con el límite de ariba:

// límites
public static Int32 limiteDerecho = 526;
public static Int32 limiteIzquierdo = 190;
public static Int32 limiteArriba = 44;
public static Int32 limiteAbajo = 350;

Texture2D pointText;

//Update
...
burbujaLanzada.Mover();
if (burbujaLanzada.Posicion.X = limiteDerecho - 42) // choca
{
   burbujaLanzada.Direccion.X *= -1;
}
if (burbujaLanzada.Posicion.Y {
   // choca en el techo, detenerla y pegarla
   burbujaLanzada.Direccion = Vector2.Zero;
}
//Draw
...
//dibujar rectángulo
spriteBatch.Draw(pointText, new Rectangle(limiteIzquierdo, limiteArriba, limiteDerecho - limiteIzquierdo, limiteAbajo), null, Color.Blue);

image

Lo que queda por hacer es que cuando la burbuja choque con el límite superior, se cree una nueva burbuja con un color aleatorio y se puedan disparar más burbujas, también se crea una lista que guardará las burbujas que se encuentran pegadas:

// clase Game1
Vector2 posicionBurbuja;
public static List burbujasPegadas { get; private set; }
//Initialize
posicionBurbuja = new Vector2(350 - 21, 400 - 21);
//burbujaLanzada = new Burbuja(posicionBurbuja, Color.White);\
GenerarNuevaBurbuja();
burbujasPegadas = new List();
// Update
if (burbujaLanzada.Posicion.Y {
  // choca en el techo, detenerla y pegarla
  burbujasPegadas.Add(burbujaLanzada);
  burbujaLanzada = null;
}
...
if (burbujaLanzada == null)
{
  GenerarNuevaBurbuja();
}
base.Update(gameTime);
}

public void GenerarNuevaBurbuja()
{
 //genera una burbuja aleatoria
 Int32 color = new Random().Next(0, 7);
 Color colorBurbuja = new Color();
 switch (color)
 {
    case 0:
       colorBurbuja = Color.White;
       break;
    case 1:
       colorBurbuja = Color.Red;
       break;
    case 2:
       colorBurbuja = Color.Black;
       break;
    case 3:
       colorBurbuja = Color.Blue;
       break;
    case 4:
        colorBurbuja = Color.Green;
        break;
     case 5:
        colorBurbuja = Color.Yellow;
        break;
     case 6:
        colorBurbuja = Color.Purple;
        break;
     case 7:
        colorBurbuja = Color.Orange;
        break;
  }
  burbujaLanzada = new Burbuja(posicionBurbuja, colorBurbuja);
 }
// Draw
if (burbujaLanzada != null)
{
   spriteBatch.Draw(bubble, burbujaLanzada.Posicion, burbujaLanzada.Color);
}
foreach (Burbuja burb in burbujasPegadas)
{
 spriteBatch.Draw(bubble, burb.Posicion, null, burb.Color, 0f, Vector2.Zero, 1f, SpriteEffects.None, 1);

 }
}

image

Antes de continuar con la programación, voy a explicar como se manejan las burbujas en el juego, ya que si se fijan bien en el juego original, las burbujas quedan siempre como en casillas ya definidas, o sea que siempre estan separadas por la misma distancia, las casillas son de la misma distancia de las burbujas y las casillas impares se encuentran más hacia la derecha, en una imagen se muestra como es el juego original de NeoRage y se les han dibujado las casillas:

image

Para que entiendan mejor como funciona el juego, se van a dibujar las casillas en el juego, las casillas impares se corren la mitad del tamaño de la burbuja, para hacer esto se hace uso de la operación modulo (%) y así saber si la posición Y de la casilla es par o impar, también se crea una variable que guardará el tamaño de la burbuja:

public static Int32 tamanoBurbuja = 42;
 // Método Draw
 // Dibujar las casillas donde se posicionaran las burbujas
 //dibujar rectángulo
 spriteBatch.Draw(pointText, new Rectangle(limiteIzquierdo, limiteArriba,
 limiteDerecho - limiteIzquierdo, limiteAbajo), null, Color.Blue);

 for (int y = 0; y < 9; y++)
 {
   for (int x = 0; x < 9; x++)
   {

    Rectangle dRect = new Rectangle((x * tamanoBurbuja) + limiteIzquierdo +
 ((y % 2) * (tamanoBurbuja / 2)), (int)y * tamanoBurbuja + limiteArriba,
 tamanoBurbuja, tamanoBurbuja);
     //Dibujar las líneas
    if (x < 8)
     spriteBatch.Draw(pointText,
         new Rectangle(dRect.X, dRect.Y, tamanoBurbuja, 1),
         new Color(255, 0, 0, 100));
    if (y < 8)
     spriteBatch.Draw(pointText,
         new Rectangle(dRect.X, dRect.Y, 1, tamanoBurbuja),
         new Color(255, 0, 0, 100));
    }
 }

image

Ya que saben donde se encuentra cada casilla y el tamaño de cada una, se modifica la clase burbuja para que cada burbuja guarde la posición de la casilla, por ejemplo si la burbuja se encuentra en la casilla superior de la izquierda, la posición sería (0,0), y así sucesivamente, también se crea un método que retorne la casilla más cercana a la posición de la burbuja:

 // Modificación clase Burbuja
  public Vector2 posicionCasilla { get; set; }

  public void encontrarCasillaMasCercana()
  {
  int fy = (int)(Posicion.Y - Game1.limiteArriba +
      (Game1.tamanoBurbuja / 2)) / Game1.tamanoBurbuja;
  int fx = (int)((Posicion.X - Game1.limiteIzquierdo +
      (Game1.tamanoBurbuja / 2) - ((fy % 2) *
     (Game1.tamanoBurbuja / 2))) / Game1.tamanoBurbuja);

  posicionCasilla = new Vector2(fx, fy);
  }

El método para encontrar la casilla más cercana a la posición de la burbuja, hace lo contrario a las operaciones que se hicieron al dibujar las casillas.

También se modifica la clase Game1 para agregar un método que dado una posición en la casilla de la burbuja devuelva una ubicación para ubicar la burbuja, por ejemplo la casilla (0,0) tendrá la ubicación (190,44) que son los limites superior e izquierdo, y la casilla (0,1) será la ubicación (211,86) debido a que la posición Y es impar, también se modifica el código al chocar con el límite de arriba y para finalizar se crea un método que pegue las burbujas, este método más adelante verificará si la burbuja se encuentra en grupo de iguales colores:

 // Método Update
 //...
 if (burbujaLanzada.Posicion.Y  {
     // choca en el techo, detenerla y pegarla
     burbujaLanzada.encontrarCasillaMasCercana();
     pegarBurbuja(burbujaLanzada);
     burbujaLanzada = null;
 }
 public void pegarBurbuja(Burbuja burbuja)
 {
   burbuja.Posicion = posicionDeCasilla((Int32)burbuja.posicionCasilla.X,
     (Int32)burbuja.posicionCasilla.Y);
   burbujasPegadas.Add(burbuja);
 }
 private Vector2 posicionDeCasilla(int cx, int cy)
 {
  Rectangle dRect = new Rectangle((cx * tamanoBurbuja) + limiteIzquierdo +
     ((cy % 2) * (tamanoBurbuja / 2)),
     (int)cy * tamanoBurbuja + limiteArriba, tamanoBurbuja, tamanoBurbuja);

  return new Vector2(dRect.X, dRect.Y);
 }

image

Ahora cuando la burbuja choca, se busca la casilla más cercana a la posición de la burbuja y luego se cambia la posición con la posición de la casilla.

Lo que falta es la colisión de la burbuja con otras burbujas, para la colisión voy a usar el mismo usado en un antiguo post:

 public Boolean ChoqueBurbujas(Burbuja objetoActual, Burbuja objeto2)
 {
  float dx = objeto2.Posicion.X - objetoActual.Posicion.X;
  float dy = objeto2.Posicion.Y - objetoActual.Posicion.Y;
  Int32 radio1 = bubble.Width / 2;
  Int32 radio2 = bubble.Width / 2;
  Int32 radioTotal = radio1 + radio2;
  double diff1 = Math.Pow(dx, 2) + Math.Pow(dy, 2);
  double radioE = Math.Pow(radioTotal, 2);
  if (diff1   {
     return true;
  }
  return false;
 }
 if (burbujaLanzada.Posicion.Y   {
     // choca en el techo, detenerla y pegarla
     burbujaLanzada.encontrarCasillaMasCercana();
     pegarBurbuja(burbujaLanzada);
     burbujaLanzada = null;
  }
  else
  {
     // verificar colisión con todas las burbujas pegadas
     for (int i = 0; i      {
        if (burbujasPegadas[i] != burbujaLanzada)
        {
           if (ChoqueBurbujas(burbujaLanzada, burbujasPegadas[i]))
           {
              burbujaLanzada.encontrarCasillaMasCercana();
              pegarBurbuja(burbujaLanzada);
              burbujaLanzada = null;
              break;
           }
        }
     }
  }

image

Lo que viene ahora, es verificar que hayan grupos de burbujas pegadas con el mismo color, para hacerlo se necesita primero saber si una burbuja es vecina de otra, luego se debe verificar que las burbujas vecinas de la burbuja que esta chocando, sean del mismo color y forman un grupo de 3 o más burbujas.

image

En la imagen anterior se muestran todas las casillas con su respectiva posición, digamos que se tiene una burbuja en la posición (2,1), si se ve la imagen de arriba los vecinos serían: (0,2), (0,3), (1,1), (3,1), (2,2) y (3,2), ahora si se tiene la casilla (2,2) los vecinos serían: (1,1), (2,1),(1,2),(3,2),(1,3) y (2,3), al verificar detenidamente los ejemplos anteriores, se puede encontrar un patrón para buscar los vecinos, aunque para evitar hacerlo se les tiene el patrón o fórmula:

Arriba izquierda:

(X – (Y + 1) % 2, Y – 1)

Arriba derecha:

(X – ((Y + 1) % 2) + 1, Y – 1)

Izquierda:

(X-1,Y)

Derecha:

(X + 1, Y)

Abajo izquierda:

(X – (Y + 1) % 2, Y + 1)

Abajo Derecha:

(X – ((Y + 1) % 2) + 1, Y + 1)

Ahora se crea en la clase burbuja el método que indicará si una burbuja es vecina, también se agrega una lista pública para mostrar las burbujas vecinas de cada burbuja:

 //Clase Burbuja
 public List Vecinos { get; set; }
 public void encontrarVecinos()
 {
     Vecinos.Clear();

     foreach (Burbuja bubble in Game1.burbujasPegadas)
     {
         // buscar si hay burbujas cerca
         if (proximoA(bubble))
         {
             Vecinos.Add(bubble);
         }
     }
 }

 public bool proximoA(Burbuja bubble)
 {
     if (bubble.posicionCasilla == new Vector2(posicionCasilla.X -
 ((posicionCasilla.Y + 1) % 2), posicionCasilla.Y - 1))
     {
         // verificar arriba izq
         return true;
     }
     else if (bubble.posicionCasilla == new Vector2(posicionCasilla.X -
 ((posicionCasilla.Y + 1) % 2) + 1, posicionCasilla.Y - 1))
     {
         // verificar arriba der
         return true;
     }
     else if (bubble.posicionCasilla == new Vector2(posicionCasilla.X - 1,
 posicionCasilla.Y))
     {
         // verificar izq
         return true;
     }
     else if (bubble.posicionCasilla == new Vector2(posicionCasilla.X + 1,
  posicionCasilla.Y))
     {
         // verificar der
         return true;
     }
     else if (bubble.posicionCasilla == new Vector2(posicionCasilla.X -
 ((posicionCasilla.Y + 1) % 2), posicionCasilla.Y + 1))
     {
         // verificar abajo izq
         return true;
     }
     else if (bubble.posicionCasilla == new Vector2(posicionCasilla.X + 1 -
 ((posicionCasilla.Y + 1) % 2), posicionCasilla.Y + 1))
     {
         // verificar abajo der
         return true;
     }
     return false;
 }
Para entender lo que es la recursividad, 
primero hay que saber qué es la recursividad

Ahora que se puede saber cuando una bola tiene vecinos, se crea el método que verifica si hay grupos del mismo color, para hacerlo se usa una función recursiva que va a recibir como parámetro una lista y una burbuja, y por cada vecino verifica si son del mismo color, luego lo adiciona a la lista solo si antes no estaba, y así sucesivamente con cada burbuja vecina:

 // Clase Burbuja
 public Boolean destruir = false;

 public List encontrarIguales()
 {
     List lista = new List();
     lista.Add(this);
     this.destruir = true;
     // encontrar iguales recursivamente
     encontrarIguales(lista, this);

     return lista;
 }

 public void encontrarIguales(List lista, Burbuja burbuja)
 {
     burbuja.encontrarVecinos();
     foreach (Burbuja bubble in burbuja.Vecinos)
     {
         // si es igual el color y no se ha agregado anteriormente a la lista
         if (Color == bubble.Color && !lista.Contains(bubble))
         {
             bubble.destruir = true;
             lista.Add(bubble);
             encontrarIguales(lista, bubble);
         }
     }
 }

Ahora se modifica el método pegarBurbuja de la clase Game1 para que verifique si la burbuja recién pegada forma un grupo de colores iguales:

 public void pegarBurbuja(Burbuja burbuja)
 {
  List grupoIguales = burbuja.encontrarIguales();
  if (grupoIguales.Count < 3)
  {
     burbuja.Posicion = posicionDeCasilla((Int32)burbuja.posicionCasilla.X,
        (Int32)burbuja.posicionCasilla.Y);
     burbujasPegadas.Add(burbuja);

  }
  else
  {
     // hay un grupo del mismo colore de 3 o más burbujas
     burbuja.Posicion = posicionDeCasilla((Int32)burbuja.posicionCasilla.X,
        (Int32)burbuja.posicionCasilla.Y);
     burbujasPegadas.Add(burbuja);
 }

Por el momento cuando hay más de 2 burbujas iguales, no se hace nada, más adelante se van a crear otros métodos que hacen estallar las burbujas que son del mismo grupo de la burbuja lanzada, y también aquellas que se encuentran conectadas a las que se van a estallar y no están conectadas a otras.

También se va a agregar un método que hará que cierto tiempo las burbujas pegadas se vayan cayendo, haciendo más difícil el juego, falta agregar la puntuación y una línea que ayudará al jugador para saber a donde va a chocar la burbuja antes de lanzarla.

 

Código Fuente

Anuncios

9 pensamientos en “Cómo hacer un Puzzle Bobble o Bust a Move en XNA – Parte 1

  1. Grandisimo Martín. Por cierto vas a subir la carpeta con el codigo¿?

    pd: Me están sirviendo de mucho tus post. cojo cachitos de el para trastear cosilla y vienen de fabula, ojalá hubiese mucha gente como tu.
    Sigue así

    • Gracias Juan, sí tengo pensado subir el código, no lo subí porque cuando publiqué el post no lo tenía a la mano y en este momento tampoco, pero en cuánto suba la otra parte actualizó este con el código, que bien que te sirvan 🙂

  2. Pingback: No se encontró la página « Escarbando Código

    • Hola, necesitas el Visual Studio o puedes descargar Visual C# Express 2010, luego descargar XNA 4.0, con eso ya puedes empezar, opcional descargar el framework para desarrollar juegos para Windows Phone 7.
      El resto ya es teoría, aprenderás con libros, páginas o videos

      • spriteBatch.Draw(launcher, ubicacionPuntero, null, Color.White, rotacion, new Vector2(launcher.Width/2,launcher.Height/2), 1f, SpriteEffects.None, 1);

        what does ubicacionPuntero means ??
        what is its usage ?

      • Hello, ubicacionpuntero is the position or location of the launcher.
        I missed writing this in the post, but if you need it, the code:
        / / Global Variable
        Vector2 ubicacionPuntero;

        / / Initialize
        ubicacionPuntero = new Vector2 (350, 432);

  3. Hola disculpa la molestia si quiero cambiar el fondo del juego quitar el color azul que parte del codigo deberia modificar no encontre ninguna referencial al fondo

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