Juego de la Ranita -Frogger- XNA


Siguiendo con el tema de los juegos clásicos, he decidido mostrar como hacer un clon de Frogger, aunque la verdad aún me falta muchas cosas por mejorarlé, voy a explicar cómo lo desarrollé para que puedan terminarlo o inspirarse y hacer uno mucho mejor.

Frogger es un juego en donde el objetivo es pasar una ranita de una lado a otro, a través de una carretera y un rio, la carretera por su puesto está llena de Carros, los cuales deben ser evitados para ser aplastada, y en el rio debe evitar caer saltando de un tronco a otro hasta llegar al otro lado, en la Versión Original el rio es un rio común y corriente, pero me parece extraño que una rana no sepa nadar, por lo que en este juego el rio va a ser de Lava jejejeje

Use una clase que hice para mostrar cómo crear Animaciones de Sprites, aunque le modifique algunas cosillas para que se adecuara al juego, la clase me fue muy útil porque pude cargar animaciones y Sprites sencillos.

Mover la Rana:

La Rana, se mueve solo una vez cada que oprimimos las teclas de Movimiento, además ejecutará la animación simulando el salto, para hacer esto use dos variables que guardan el estado del teclado y cuando son diferentes (una con la tecla de mover presionada y otra no) la rana se mueve

Como la textura de la rana es una sola, cada vez que cambiamos la dirección, también cambiamos la rotación para mostrar el cambio de dirección en la textura.

La rana tendrá una propiedad que guardará el tamaño en un Rectángulo, para después usarlo en las colisiones.

No se permitirá a la rana salirse de los límites de la pantalla, para hacer esto verificamos en cada movimiento, y si se ha pasado, devolvemos la rana a su posición anterior.

oldteclado = teclado;
teclado = Keyboard.GetState();

if (teclado.IsKeyDown(Keys.Up) == true && oldteclado != teclado)
{
  ranita.Rotacion = 0;
  ranita.iniciarTodo(0.2f, false);
  ranita.Mover(new Vector2(0, -velocidadRana), gameTime);
  if (ranita.Posicion.Y < 0)
   {
    ranita.Mover(new Vector2(0, velocidadRana), gameTime);
   }
}

if (teclado.IsKeyDown(Keys.Down) == true && oldteclado != teclado)
{
  ranita.Rotacion = MathHelper.ToRadians(180);
  ranita.iniciarTodo(0.2f, false);
  ranita.Mover(new Vector2(0, velocidadRana), gameTime);
  if (ranita.Posicion.Y > Window.ClientBounds.Height)
  {
    ranita.Mover(new Vector2(0, -velocidadRana), gameTime);
   }
}
if (teclado.IsKeyDown(Keys.Right) == true && oldteclado != teclado)
{
  ranita.Rotacion = MathHelper.ToRadians(90);
  ranita.iniciarTodo(0.2f, false);
  ranita.Mover(new Vector2(velocidadRana, 0), gameTime);
  if (ranita.Posicion.X > Window.ClientBounds.Width)
  {
    ranita.Mover(new Vector2(-velocidadRana, 0), gameTime);
  }
}
if (teclado.IsKeyDown(Keys.Left) == true && oldteclado != teclado)
{
  ranita.Rotacion = MathHelper.ToRadians(270);
  ranita.iniciarTodo(0.2f, false);
  ranita.Mover(new Vector2(-velocidadRana, 0), gameTime);
  if (ranita.Posicion.X < 0)
  {
   ranita.Mover(new Vector2(velocidadRana, 0), gameTime);
  }
}

Tráfico de Vehículos

Para el control de los carros, tengo una lista, donde tengo los carros, cada carro tiene una textura, una dirección, posición y velocidad.

List<Animacion> carros = new List<Animacion>();
float Velocidad1 = 1.2f;
float Velocidad2 = 1.5F;
float Velocidad3 = 1.8F;
float Velocidad4 = 2F;
float Velocidad5 = 1.9F;
float Velocidad6 = 1.85F;

Aunque el método que he usado para inicializar las propiedades de los carros no ha sido la mejor (Hacerlo carro por carro), puede ser útil para entender cómo no hacerlo :P.

Cargando e inicializando los carros:

// primera línea
            carros.Add(new Animacion(graphics, new Vector2(0, 530)));
            carros.Add(new Animacion(graphics, new Vector2(-150, 530)));
            carros.Add(new Animacion(graphics, new Vector2(-300, 530)));
            carros.Add(new Animacion(graphics, new Vector2(-450, 530)));
            carros.Add(new Animacion(graphics, new Vector2(-600, 530)));
            // segunda línea
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width, 480)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 200, 480)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 400, 480)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 600, 480)));
            // tercera línea
            carros.Add(new Animacion(graphics, new Vector2(0, 430)));
            carros.Add(new Animacion(graphics, new Vector2(-50, 430)));
            carros.Add(new Animacion(graphics, new Vector2(-200, 430)));
            carros.Add(new Animacion(graphics, new Vector2(-250, 430)));
            // cuarta línea
            carros.Add(new Animacion(graphics, new Vector2(0, 380)));
            carros.Add(new Animacion(graphics, new Vector2(-250, 380)));
            carros.Add(new Animacion(graphics, new Vector2(-500, 380)));
            carros.Add(new Animacion(graphics, new Vector2(-750, 380)));
            // quinta línea
            carros.Add(new Animacion(graphics, new Vector2(0, 330)));
            carros.Add(new Animacion(graphics, new Vector2(-250, 330)));
            carros.Add(new Animacion(graphics, new Vector2(-600, 330)));
            carros.Add(new Animacion(graphics, new Vector2(-850, 330)));
            // sexta línea
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width, 280)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 50, 280)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 100, 280)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 150, 280)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 500, 280)));
           carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 550, 280)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 600, 280)));
            carros.Add(new Animacion(graphics, new Vector2(Window.ClientBounds.Width + 650, 280)));
carros[0].iniciar(0, 0, 0, false);
            carros[0].velocidad = new Vector2(Velocidad1, 0);
            carros[1].iniciar(0, 0, 0, false);
            carros[1].velocidad = new Vector2(Velocidad1, 0);
            carros[2].iniciar(0, 0, 0, false);
            carros[2].velocidad = new Vector2(Velocidad1, 0);
            carros[3].iniciar(0, 0, 0, false);
            carros[3].velocidad = new Vector2(Velocidad1, 0);
            carros[4].iniciar(0, 0, 0, false);
            carros[4].velocidad = new Vector2(Velocidad1, 0);
            carros[5].iniciar(1, 1, 0, false);
            carros[5].Rotacion = MathHelper.ToRadians(180);
            carros[5].velocidad = new Vector2(-Velocidad2, 0);
            carros[6].iniciar(1, 1, 0, false);
            carros[6].Rotacion = MathHelper.ToRadians(180);
            carros[6].velocidad = new Vector2(-Velocidad2, 0);
            carros[7].iniciar(1, 1, 0, false);
            carros[7].Rotacion = MathHelper.ToRadians(180);
            carros[7].velocidad = new Vector2(-Velocidad2, 0);
            carros[8].iniciar(1, 1, 0, false);
            carros[8].Rotacion = MathHelper.ToRadians(180);
            carros[8].velocidad = new Vector2(-Velocidad2, 0);
            carros[9].iniciar(2, 2, 0, false);
            carros[9].velocidad = new Vector2(Velocidad3, 0);
            carros[10].iniciar(2, 2, 0, false);
            carros[10].velocidad = new Vector2(Velocidad3, 0);
            carros[11].iniciar(2, 2, 0, false);
            carros[11].velocidad = new Vector2(Velocidad3, 0);
            carros[12].iniciar(2, 2, 0, false);
            carros[12].velocidad = new Vector2(Velocidad3, 0);
            carros[13].iniciar(0, 0, 0, false);
            carros[13].velocidad = new Vector2(Velocidad4, 0);
            carros[14].iniciar(0, 0, 0, false);
            carros[14].velocidad = new Vector2(Velocidad4, 0);
            carros[15].iniciar(0, 0, 0, false);
            carros[15].velocidad = new Vector2(Velocidad4, 0);
            carros[16].iniciar(0, 0, 0, false);
            carros[16].velocidad = new Vector2(Velocidad4, 0);
            carros[17].iniciar(3, 3, 0, false);
            carros[17].velocidad = new Vector2(Velocidad5, 0);
            carros[18].iniciar(3, 3, 0, false);
            carros[18].velocidad = new Vector2(Velocidad5, 0);
            carros[19].iniciar(3, 3, 0, false);
            carros[19].velocidad = new Vector2(Velocidad5, 0);
            carros[20].iniciar(3, 3, 0, false);
            carros[20].velocidad = new Vector2(Velocidad5, 0);
            carros[21].iniciar(2, 2, 0, false);
            carros[21].velocidad = new Vector2(-Velocidad6, 0);
            carros[22].iniciar(2, 2, 0, false);
            carros[22].velocidad = new Vector2(-Velocidad6, 0);
            carros[23].iniciar(2, 2, 0, false);
            carros[23].velocidad = new Vector2(-Velocidad6, 0);
            carros[24].iniciar(2, 2, 0, false);
            carros[24].velocidad = new Vector2(-Velocidad6, 0);
            carros[25].iniciar(2, 2, 0, false);
            carros[25].velocidad = new Vector2(-Velocidad6, 0);
            carros[26].iniciar(2, 2, 0, false);
            carros[26].velocidad = new Vector2(-Velocidad6, 0);
            carros[27].iniciar(2, 2, 0, false);
            carros[27].velocidad = new Vector2(-Velocidad6, 0);
            carros[28].iniciar(2, 2, 0, false);
            carros[28].velocidad = new Vector2(-Velocidad6, 0);

Hay un método Mover, que es útil para mover cada carro de la lista, dependiendo de la velocidad, se mueven a la derecha o hacia la izquierda, cuando se pasan de los límites de la pantalla, vuelven a su posición inicial para empezar el ciclo del movimiento.

Cuando se pasa de Nivel, la velocidad de cada carro aumenta un poco para aumentar también la dificultad.

private void MoverCarros(GameTime gameTime)
{
  foreach (Animacion carro in carros)
  {
    if (carro.velocidad.X > 0)
    {
      if (carro.Posicion.X >= Window.ClientBounds.Width)
      {
         carro.ponerPosicion(0, carro.posicionInicial.Y);
      }
      else
      {
         carro.Mover();
      }
    }
    else
    {
     if (carro.Posicion.X < 0)
      {
       carro.ponerPosicion(Window.ClientBounds.Width, carro.posicionInicial.Y);
      }
      else
      {
        carro.Mover();
      }
    }
  }
}

Con este método, si la velocidad del carro es negativa quiere decir que va hacia la izquierda, entonces inicializamos el carro cuando su posición sea menor de 0, y viceversa para el movimiento hacia la derecha

Aplastando la rana

Para la colisión de la rana con los carros, recorro cada carro y verifico la propiedad tamano del carro no choque  con la de la ranita. Cuando los rectángulos se interceptan la rana vuelve a su posición inicial y se descuenta una vida, además de mostrar la animación de la ranita aplastada

 private void colisionCarros()
{
  foreach (Animacion carro in carros)
  {
   if (carro.Tamano.Intersects(ranita.Tamano))
   {
     ranaMuerte();
   }
  }
}

Moviendo Troncos

Para la inicialización de los troncos se uso la misma técnica del movimiento de los carros (la verdad no es una técnica, es simple pereza de hacerlo mejor) y para el movimiento también se hizo lo mismo:

foreach (Animacion tronco in troncos)
{
 if (tronco.velocidad.X > 0)
 {
  if (tronco.Posicion.X >= Window.ClientBounds.Width)
   {
    tronco.ponerPosicion(0, tronco.posicionInicial.Y);
   }
  else
   {
    tronco.Mover();
   }
 }
 else
 {
  if (tronco.Posicion.X < 0)
  {
   tronco.ponerPosicion(Window.ClientBounds.Width, tronco.posicionInicial.Y);
  }
  else
  {
   tronco.Mover();
  }
 }
}

Subiendo en los troncos

Para el paso del rio, se usa una lógica muy diferente al paso de la carretera, ya que solo si la rana ha colisionado con algún tronco, la rana puede seguir con vida, si la rana ha colisionado el rectángulo del rio, pero no con algún tronco entonces la rana pierde una vida.

private Boolean colisionRio()
{
 if (ranita.Tamano.Intersects(rio) || rio.Contains(ranita.Tamano))
 {
   return true;
 }
 else
 {
  return false;
 }
}
private Boolean colisionTroncos(Animacion tronco)
{
  Boolean estado = false;
  if (ranita.Tamano.Intersects(tronco.Tamano) || tronco.Tamano.Contains(ranita.Tamano))
  {
    estado = true;
  }
  return estado;
 }

Cuando la rana se encuentra en el rio, y se sube sobre un tronco, la rana se mueve hacia la dirección y con la misma velocidad que tiene el tronco, cuando llega a la pantalla puede o caerse al rio o saltar hacia otro tronco.

Para saber si la rana se encuentra en algún tronco, se verifica con cada tronco, y si se encuentra en un tronco, marcamos un flag para saber que si esta en un tronco, al salir del ciclo, se verifica si el flag está en verdadero o falso, si es falso, significa que la rana no se encuentra en ningún tronco y pierde una vida.

if (colisionRio())
{
  enTronco = false;
  for (int i = 0; i < 3; i++)
  {
    if (colisionTroncos(troncos[i]))
    {
     if (ranita.Posicion.X <= Window.ClientBounds.Width)
      {
        ranita.Mover(troncos[i].velocidad, gameTime);
        enTronco = true;
      }
     }
   }
   if (!enTronco)
    {
      ranaMuerte();
    }
}

La verdad, el algoritmo de los troncos parece que tiene algo mal, algunas veces cuando la rana se encuentra saltando en los troncos, se muere, como si los rectángulos no estuvieran colisionando, si alguien puede verificar o dar una mejor solución, Bienvenida la solución

Mostrando Vidas

Para el manejo de las Vidas, he manejado una lista que guardará el total de las vidas, el total está dado por un contador, cuando se dibuja, se dibuja cada ranita en la esquina superior derecha pero separadas, cuando se pierde una vida estrellándose con un carro o cayendo al rio, se disminuye el contador de las vidas y se remueve de la lista la rana.

for (int i = 0; i < contVidas; i++)
{
  vidas.Add(new Animacion(graphics, new Vector2(((Window.ClientBounds.Width - 40) - (45 * i)), 20)));
  vidas[i].iniciar(0, 0, 0, false);
}
private void ranaMuerte()
{
  estado = Estados.Muriendo;
  muerte.ponerPosicion(ranita.Posicion.X, ranita.Posicion.Y);
  contVidas--;
  if (contVidas >= 0)
  {
   vidas.Remove(vidas[contVidas]);
  }
  else
  {
    this.Exit();
  }
}

Comiendo Moscas

En este juego, la rana deberá atravesar todo el camino, para poder comerse unas “deliciosas” moscas, cuando se coma las 3 moscas que hay al otro lado se aumenta el nivel.

private void colisionMosca()
{
 if (moscas.Count == 0)
 {
// subir de nivel
   for (int i = 0; i < 3; i++)
   {
    moscas.Add(new Animacion(graphics, new Vector2((250 * i) + 40, 30)));
    moscas[i].iniciar(0, 0, 0, false);
    foreach (Animacion mosca in moscas)
    {
     mosca.adicionarImagen(Content.Load<Texture2D>("Mosca"), 1);
    }
   }
  }
  foreach (Animacion mosca in moscas)
  {
   if (mosca.Tamano.Intersects(ranita.Tamano))
   {
    moscas.Remove(mosca);
    ranita.ponerPosicion(ranita.posicionInicial.X, ranita.posicionInicial.Y);
    puntuacion += 1000;
    return;
   }
  }
}

Manejo de Estados en el Juego

Para tener un mejor control del juego, se ha creado una enumeración para los estados del Juego, el estado Jugando indica que todos los objetos se pueden mover y el juego se encuentra en normalidad, el estado muriendo es cuando la rana pierde una vida y sirve para mostrar la animación de la muerte de la rana, el juego Pausado es cuando el usuario oprime la tecla P, en este caso nada se actualiza y todo queda en el mismo estado hasta que el usuario vuelve a oprimir la letra P.

if (enPause)
{
 if (teclado.IsKeyDown(Keys.P) == true)
 {
   estado = Estados.Jugando;
   enPause = false;
   return;
 }
}
if (estado == Estados.Jugando)
{
 // Allows the game to exit
 if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
 this.Exit();
 if (teclado.IsKeyDown(Keys.P) == true )
 {
  enPause = true;
  estado = Estados.Pausado;   
  }
…

if (estado == Estados.Pausado)
{
 spriteBatch.DrawString(fuente, "JUEGO PAUSADO", new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height / 2), Color.Black);
}

Con los estados también podemos manejar el Menú Inicial del Juego

Y el juego queda de la siguiente forma:

Les dejo el código Fuente, como siempre falta adicionarle sonido y corregirle algunas cosillas, espero les sea útil.
CODIGO FUENTE.

7 pensamientos en “Juego de la Ranita -Frogger- XNA

  1. Pingback: Sonidos MP3,Wav y otros en XNA « Escarbando Código

  2. Hola buenas noches perdone la molestia, me encanto su tutorial pero aun no logro saber como hizo las clases, intente descargar el código fuente pero no esta disponible en enlace espero pueda arreglarlo o pasármelo por favor, de antemano muchas gracias y un cordial saludo.

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