14 diciembre 2008

Remake (parcial) de Fruity Frank... 21 - Rediseñando usando clases (2 - Escribir textos, marcador, varios niveles, colisiones)

Como el rediseño usando clases ha supuesto muchos cambios, en la entrega anterior habíamos creado una primera versión, que todavía tenía muchas carencias:

  • No se podía escribir textos. Es fácil crear una función "EscribirTextoOculta", similar a la de las versiones anteriores, e incluso una clase "Fuente" para que en nuestro programa "normal" no aparezca ninguna referencia a "IntPtr". Así, normalmente para escribir daremos estos pasos:


    private Fuente fuenteSans18;  // Declaramos la variable como atributo
fuenteSans18 = new Fuente("FreeSansBold.ttf",18); // Leemos, en el constructor
Hardware.EscribirTextoOculta( // Usamos:
"Hola", // texto
110,440, // posición
0xFF, 0xAA, 0xAA, // color (R,G,B)
fuenteSans18); // fuente


  • En cuanto podamos escribir textos, ya podemos hacer que el marcador nos muestra la puntuación actual. Para que también nos muestre el nivel actual, deberíamos añadir algo como "GetNumeroNivel" a la clase "Juego". Y para saber el número de vidas, el juego deberá tener un "GetPersonaje" que permita acceder al personajes, y éste tendrá un "GetNumeroVidas" que indique cuantas vidas tenemos. Así, la rutina de dibujar el marcador en pantalla terminaría con:


    public  void Dibujar()
{
...
Hardware.EscribirTextoOculta("Nivel",380,60,
0x88, 0xFF, 0xFF, fuenteSans14);
Hardware.EscribirTextoOculta(miJuego.GetNumeroNivel().ToString(),420,60,
0xFF, 0xFF, 0x88, fuenteSans14);
Hardware.EscribirTextoOculta("Vidas",480,60,
0x88, 0xFF, 0xFF, fuenteSans14);
for (byte i=0; i<miJuego.GetPersonaje().GetNumVidas()-1; i++)
iconoVida.Dibujar( (short)(520+i*30),48);
}


  • Ahora el marcador necesita comunicarse con el juego. Una forma de conseguirlo es que el constructor del "Marcador" reciba como parámetro el juego que lo está manipulando, algo que ya hicimos con el "personaje" en la primera entrega:


    public class Marcador
{
// Atributos
private Juego miJuego; // Para comunicar con el resto de elementos
...

// Constructor
public Marcador(Juego j)
{
miJuego = j;
...


  • Y cuando el juego cree el marcador, lo hará así:


    miMarcador = new Marcador(this);


  • Sigamos ampliando... No se podía cambiar de nivel. Esto supone un par de cambios: por una parte, deberemos crear otra clase "Nivel2" (similar a Nivel1), que se apoye en un "Mapa2" (parecido a Mapa1), y que puede tener un distinto número de enemigos. Para cambiar de un nivel a otro, antes era el juego el que comprobaba la cantidad de frutas desde "ComprobarColisiones"; ahora podría ser el propio nivel, el que tuviera una variable booleana de control, llamada "completo" (a la que accederíamos con "GetCompleto") para saber si se ha completado un nivel, lo que además nos da la versatilidad de que un nivel pueda terminarse por otros motivos, no sólo porque se acaben las frutas. Así, por ejemplo, podríamos crear una función "siguienteFotograma", que se encargara de mover todos los enemigos y demás elementos del nivel, y de cambiar al siguiente nivel cuando corresponda. Esta función se llamaría en cada pasada del bucle principal:


    // Anima los enemigos y demás elementos del nivel.
// Cambia el nivel si corresponde.
private void SiguienteFotograma()
{
miNivel.SiguienteFotograma();
if (miNivel.GetCompleto())
SiguienteNivel();
}

// Bucle principal del juego
public void BuclePrincipal()
{
// Parte repetitiva ("bucle de juego")
NuevaPartida();
do {
DibujarPantalla();
ComprobarTeclas();
ComprobarColisiones();
SiguienteFotograma();
// Pausa de 40 ms, para velocidad de 25 fps (1000/40 = 25)
Hardware.Pausa(40);
// Fin de la parte repetitiva
} while (! partidaTerminada); // Hasta tecla ESC
}


  • La versión anterior no comprobaba colisiones: la función "ColisionCon" que comprueba si dos "ElementosGraficos" coinciden, siempre devolvía "false". El esqueleto de la función ya lo teníamos antes en "sdl_n.cs". También teníamos la idea de cómo comprobar colisiones desde la clase "Juego", recorriendo todos los enemigos con un "for." Podemos reescribirlo ligeramente usando un "foreach", de modo que ambas funciones quedarían como sigue:


    // ColisionCon, en "ElemGrafico"
public bool ColisionCon(ElemGrafico otroElem)
{
if ((otroElem.x+otroElem.ancho > x)
&& (otroElem.y+otroElem.alto > y)
&& (x+ancho > otroElem.x)
&& (y+alto > otroElem.y))
return true;
else
return false;
}

// ComprobarColisiones, en "Juego"
private void ComprobarColisiones()
{
foreach (Enemigo e in miNivel.GetEnemigos() )
if ( e.ColisionCon(miPersonaje) )
{
miPersonaje.PerderVida();
if (miPersonaje.GetNumVidas() == 0)
PartidaTerminada();
break; // Para no perder 2 vidas si se choca con 2
}
}


  • Eso sí, sigue faltando una cosa: animaciones en el movimiento de los personajes. Pero lo ideal sería que la clase "ElementoGráfico" nos permitiera tanto manejar elementos con una única imagen estática como elementos que tengan varias imágenes, que actuarían como distintos fotogramas para dar una sensación de movimiento más real. Incluso sería deseable que pidiéramos tener distintas secuencias de imágenes según el sentido en que se muestra el personaje (izquierda, derecha, arriba o abajo). Esto supone varios cambios que merecen una entrega aparte, así que queda para la versión 0.22...



Como siempre, todo el fuente del proyecto está en: code.google.com/p/fruityfrank