31. La aproximación orientada a objetos (4). MiniMiner 4: Una pantalla de juego real
Ahora, en vez de dibujar desde el cuerpo del progama un fondo sencillo formado por varias casillas repetitivas de suelo, tendremos una pantalla de fondo más compleja, formada por varios elementos de distinto tipo. En esta versión será un fondo estático, pero dentro de poco será capaz de indicar al personaje y los enemigos si se pueden mover a una cierta posición, y tambíen será capaz de mostrar objetos en movimiento.
Nuestro diagrama de clases incluirá una nueva clase, que llamaremos "Nivel":
Para diseñar como será cada nivel, usaremos un mapa de casillas, como ya hicimos con el juego de la serpiente. Como en este caso, estamos imitando un juego existente, cuadricularemos la pantalla original para tratar de descomponer en elementos básicos. Para eso nos pueden ayudar ciertos programas de retoque de imágenes, como el GIMP, que permite superponer a la imagen una cuadrícula, que en nuestro caso es de 16x16 píxeles (en la versión 2.6.8 se hace desde el menú "Filtros", en la opción Renderizado / Patrón / Rejilla):
Ahora podemos crear un array bidimensional de caracteres, que represente a esa misma pantalla, algo como:
L V T T V L
L V L
L L
L L
L VA A L
LSSSSSSSSSSSSSFFFFSFFFFSSSSSSSSL
L VL
LSSS L
L LLL A L
LSSSS DDDDDDDDDDDDDDDDDDDD L
L SSL
L L
L A LLLFFFFFSSSL
L SSSSSSSSSSSSSSS PPL
L PPL
LSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSL
Es más, éste mapa podría estar en un fichero, de modo que nuestra clase Nivel lea dicho fichero, y se pueda modificar un nivel (o pronto, crear nuevos niveles) sin necesidad de alterar el código fuente.
Así, esta clase nivel podría tener apenas dos métodos: un "leerDeFichero" y un "dibujarOculta". El fichero de cabecera podría ser así:
/*------------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* Nivel.h: */ /* La pantalla del nivel 1 */ /* Miniminer (version 0.04) */ /* Fichero de cabecera */ /* */ /* Comprobado con: */ /* - DevC++ 4.9.9.2(gcc 3.4.2) */ /* y Allegro 4.03 - WinXP */ /* - gcc 4.4.3 y Allegro 4.2 */ /* en Ubuntu 10.04 */ /*------------------------------*/ #ifndef Nivel_h #define Nivel_h #include <vector> #include <string> using namespace std; #include "ElementoGraf.h" class Nivel { public: Nivel(); void dibujarOculta(Hardware h); void leerDeFichero(); private: int nivelActual; /* El mapa que representa a la pantalla */ /* 16 filas y 32 columnas (de 16x16 cada una) */ #define MAXFILAS 16 #define MAXCOLS 32 char mapa[MAXFILAS][MAXCOLS]; ElementoGraf *fragmentoNivel[MAXFILAS][MAXCOLS]; }; #endif
Y su desarrollo:
/*------------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* Nivel.cpp: */ /* La pantalla del nivel 1 */ /* Miniminer (version 0.04) */ /* Fichero de desarrollo */ /* */ /* Comprobado con: */ /* - DevC++ 4.9.9.2(gcc 3.4.2) */ /* y Allegro 4.03 - WinXP */ /* - gcc 4.4.3 y Allegro 4.2 */ /* en Ubuntu 10.04 */ /*------------------------------*/ #include "Hardware.h" #include "ElementoGraf.h" #include "Nivel.h" #ifdef __cplusplus #include <cstdlib> #include <cstdio> #else #include <stdlib.h> #include <stdio.h> #endif #include <math.h> #include <string> #include <allegro.h> Nivel::Nivel() { nivelActual = 1; int i, j; int anchoImagen = 16, altoImagen = 16; int margenIzq = 64, margenSup = 48; for(i=0; i<MAXCOLS; i++) for (j=0; j<MAXFILAS; j++) { fragmentoNivel[j][i] = new ElementoGraf(); fragmentoNivel[j][i]->moverA( margenIzq + i*anchoImagen, margenSup + j*altoImagen); } leerDeFichero(); } /** dibujarOculta: muestra el nivel sobre el fondo * de pantalla, con su imagen correspondiente * segun el tipo de ladrillo del que se trate */ void Nivel::dibujarOculta(Hardware h) { int i, j; h.borrarOculta(); for(i=0; i<MAXCOLS; i++) for (j=0; j<MAXFILAS; j++) h.dibujarOculta( *fragmentoNivel[j][i] ); } /** leerNivel: lee de fichero los datos del nivel actual */ void Nivel::leerDeFichero() { // Si hay fichero de datos, lo leo FILE *fichDatos; char linea[MAXCOLS+2]; char nombreFich[50]; sprintf(nombreFich, "nivel%03d.dat", nivelActual); fichDatos = fopen(nombreFich, "rt"); //### Mas adelante: creciente if (fichDatos != NULL) for (int j=0; j<MAXFILAS;j++) { fgets(linea,MAXCOLS+1,fichDatos); if (strlen(linea) < 5) // Salto los avances de linea de Windows fgets(linea,MAXCOLS+1,fichDatos); for (int i=0; i<MAXCOLS;i++) { switch(linea[i]) { case 'S': fragmentoNivel[j][i]->crearDesdeFichero("suelo.bmp"); break; case 'F': fragmentoNivel[j][i]->crearDesdeFichero("sueloFragil.bmp"); break; case 'L': fragmentoNivel[j][i]->crearDesdeFichero("ladrillo.bmp"); break; case 'V': fragmentoNivel[j][i]->crearDesdeFichero("llave.bmp"); break; case 'P': fragmentoNivel[j][i]->crearDesdeFichero("puerta.bmp"); break; case 'D': fragmentoNivel[j][i]->crearDesdeFichero("deslizante.bmp"); break; case 'A': fragmentoNivel[j][i]->crearDesdeFichero("arbol.bmp"); break; case 'T': fragmentoNivel[j][i]->crearDesdeFichero("techo.bmp"); break; default: fragmentoNivel[j][i]->crearDesdeFichero("fondoVacio.bmp"); break; } } } }
Y el cuerpo del programa, apenas cambia en que ya no es necesaria la función "dibujarFondo()", sino que se crea un objeto de la clase "Nivel" y se llama a su método "dibujarOculta()":
/*------------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* miner04.cpp */ /* */ /* Ejemplo: */ /* "MiniMiner" (version 0.04) */ /* */ /* Comprobado con: */ /* - DevC++ 4.9.9.2(gcc 3.4.2) */ /* y Allegro 4.03 - WinXP */ /* - gcc 4.4.3 y Allegro 4.2 */ /* en Ubuntu Linux 10.04 */ /*------------------------------*/ #include "Hardware.h" #include "ElementoGraf.h" #include "Personaje.h" #include "Enemigo.h" #include "Presentacion.h" #include "Nivel.h" /* -------------- Constantes globales ------------- */ #define ANCHOPANTALLA 640 #define ALTOPANTALLA 480 /* -------------- Variables globales -------------- */ Hardware hard; Personaje *personaje; Enemigo *enemigo; Presentacion *presentacion; Nivel *primerNivel; int partidaTerminada; int incrX = 4; int incrY = 4; int tecla; int ySuelo = 232; // Prototipos de las funciones que usaremos void comprobarTeclas(); void moverElementos(); void comprobarColisiones(); void dibujarElementos(); void pausaFotograma(); void moverDerecha(); void moverIzquierda(); void lanzarPresentacion(); void moverEnemigo(); void dibujarFondo(); // --- Bucle principal del juego ----- void buclePrincipal() { partidaTerminada = false; do { comprobarTeclas(); moverElementos(); comprobarColisiones(); dibujarElementos(); pausaFotograma(); } while (partidaTerminada != true); } // -- Comprobac de teclas para mover personaje o salir void comprobarTeclas() { if (hard.comprobarTecla(TECLA_ESC)) partidaTerminada = true; if (hard.comprobarTecla(TECLA_DCHA)) personaje->moverDerecha(); else if (hard.comprobarTecla(TECLA_IZQD)) personaje->moverIzquierda(); } // -- Mover otros elementos del juego void moverElementos() { enemigo->mover(); } // -- Comprobar colisiones de nuestro elemento con otros, o disparos con enemigos, etc void comprobarColisiones() { // Por ahora, no hay colisiones que comprobar } // -- Dibujar elementos en pantalla void dibujarElementos() { hard.borrarOculta(); primerNivel->dibujarOculta(hard); hard.dibujarOculta( *enemigo ); hard.dibujarOculta( *personaje ); hard.visualizarOculta(); } // -- Pausa hasta el siguiente fotograma void pausaFotograma() { // Para 25 fps: 1000/25 = 40 milisegundos de pausa hard.pausa(40); } // -- Funciones que no son de la logica de juego, sino de // funcionamiento interno de otros componentes // -- Pantalla de presentacion void lanzarPresentacion() { presentacion->mostrar(hard); } /* -------------- Rutina de inicializacin -------- */ int inicializa() { hard.inicializar(640,480); personaje = new Personaje(); enemigo = new Enemigo(); presentacion = new Presentacion(); primerNivel = new Nivel(); // Y termino indicando que no ha habido errores return 0; } /* ------------------------------------------------ */ /* */ /* -------------- Cuerpo del programa ------------- */ int main() { int i,j; // Intento inicializar if (inicializa() != 0) exit(1); lanzarPresentacion(); buclePrincipal(); hard.pausa(1000); return 0; } /* Termino con la "macro" que me pide Allegro */ END_OF_MAIN();
Y la apariencia resultante ya es mucho más cercana a la del juego original: en windows 7 se vería así:
y en Ubuntu 10.04 así: