10 diciembre 2008

Remake (parcial) de Fruity Frank... 20 - Rediseñando usando clases (1)

LLega el momento de hacer cambios drásticos. La última ampliación que hemos hecho al juego era sencilla: un único disparo que se movía en una única dirección, y aun así nos ha supuesto hacer modificaciones en cinco partes distintas del programa. Cada vez que queramos hacer una ampliación, o bien una corrección, tendremos que pelear con más y más zonas de código dispersas.

Parece más razonable cambiar un poco el planteamiento "estructurado" que estábamos usando por uno "orientado a objetos": expresar el problema como una serie de objetos que interaccionan.

Esto supondrá hacer varios cambios profundos en nuestro fuente. La mayoría de los bloques podremos "copiarlos y pegarlos" del fuente antiguo a la nueva estructura, pero aun así va a ser trabajoso...

A cambio de esta trabajo, será más fácil localizar cualquier parte de código para realizar correcciones o ampliaciones, y además sería más fácil repartir el trabajo entre distintas personas. De hecho, la nueva forma de pensar debería ser "más natural"...

Ahora nuestro programa será una serie de objetos interrelacionados, que se pasan "mensajes" unos a otros. Por ejemplo, tenemos los siguientes objetos:


  • Existe un "personaje" que nosotros manejamos.

  • Existen dos tipos de "enemigos": los señores "pepino" y los señores "nariz", que nos matan si nos tocan.

  • Todos ellos son "elementos gráficos": objetos que tienen una imagen representativa, que se pueden dibujar en pantalla, mover a una cierta posición, etc.

  • En el juego debemos superar varios "niveles", cada uno de ellos con una serie de premios y obstáculos, representados en un "mapa". Además, en cada nivel existirá un número y tipo de enemigos distinto.

  • En el mapa aparecerán distintos elementos, como "obstáculos" que no podemos atravesar (manzanas) y "premios" que podemos recoger (cerezas, plátanos). Al mapa de un nivel le podremos preguntar también si es posible mover a una cierta posición, o incluso los puntos que se obtienen al mover a cierta posición (si había un premio en ella).

  • Nos vendrá bien tener objetos auxiliares, que nos oculten la librería gráfica que estamos empleando, lo que permitirá simplificar algunas operaciones y además nos dará la posibilidad de cambiar la librería gráfica por otra distinta si fuera necesario. Podemos tener al menos una clase llamada "Hardware", que centralice casi todas las operaciones, y también nos puede interesar una clase "Imagen" y una clase "Fuente" (tipo de letra).



Este planteamiento nos permitirá hacer cambios "globales" con una cierta facilidad. Por ejemplo, cuando ampliemos las posibilidades de un "elemento gráfico" para que pueda mostrar una sucesión de imágenes en vez una imagen estática, todos los elementos gráficos del programa podrán mostrar animaciones sin necesidad de cambios en el resto del programa.

Una primera aproximación a los objetos que podemos emplear se podría representar con este diagrama de clases, que refinaremos más adelante:



De este diagrama se podrían "leer" detalles como:


  • La clase "Juego" es la que coordina todo.

  • Tenemos "elementos gráficos", como nuestro personaje, o los enemigos, o el disparo.

  • Nuestro personaje puede moverse a la derecha, izquierda, arriba o abajo, como respuesta a cuando el usuario pulse una tecla (o intentarlo: realmente llamará antes a "EsPosibleMover", del mapa actual, para comprobar si es posible moverse en esa dirección).
  • El nivel podrá por ahora poco más que dibujar su mapa y los enemigos que le corresponden.

  • Los enemigos, además de "dibujarse" (como cualquier otro elemento gráfico), se podrán "mover" con un movimiento prefijado.

  • El marcador se puede "dibujar", pero también se puede leer su puntuación, o cambiar el valor de ésta, o simplemente incrementarlo (que será lo más habitual durante el juego, a medida que recojamos "premios").



La comunicación se realiza entre clases que estén directamente relacionadas (por ejemplo, un "nivel" puede pedirle a su "mapa" que se dibuje); si las clases están más alejadas, se hace acudiendo a la clase "Juego" como intermediaria. Por ejemplo, para mover a la derecha, el personaje pide antes al juego que mire en su nivel para ver si la siguiente casilla está disponible, y después le pide al juego que aumente su marcador en la puntuación correspondiente a esa casilla, así:

  public  void MoverDerecha()
{
if (miJuego.GetNivelActual().EsPosibleMover( (short) (x+incrX), y) )
{
x += incrX;
miJuego.GetMarcador().IncrPuntuacion(
miJuego.GetNivelActual().PuntosMover( x, y ));
}
}



En cuanto a funcionalidades, esta versión es un paso atrás comparado con las anteriores: todavía no permite escribir textos (la clase "fuente" no está lista), ni cambiar de nivel (no tenemos más que un nivel), ni comprueba colisiones, ni tiene animaciones en el movimiento e los personajes, pero ya tenemos un esqueleto que mejoraremos en la siguiente entrega hasta conseguir imitar lo que ya teníamos, y poder ampliar con más facilidad a partir de entonces.

Si quieres más detalles, todo el fuente del proyecto está en: code.google.com/p/fruityfrank