Por Nacho Cabanes
Vamos con ello...
Vamos a ampliar un poco la estructura del juego: ahora el Mapa ya no tendrá definidos los datos de un único nivel, sino que crearemos una nueva clase "Nivel" y el mapa contendrá un array de niveles.
El juego que imitamos tenía 20 niveles. Para los detalles de cada nivel concreto, no crearemos un nuevo objeto de la clase Nivel, que sería suficiente para este juego, sino que tomaremos la alternativa "avanzada", que es más trabajosa pero también mucho más versátil: crearemos una clase "Nivel01", otra "Nivel02" y así sucesivamente, lo que nos permitiría poder llegar a conseguir que la "lógica" de un nivel fuera totalmente distinta de la de otro (hasta el extremo de que un nivel podría ser de vista lateral, y otro de vista superiior, y otro en 3D...)
En nuestro caso "real", los niveles se diferenciarán unos de otros (por ahora) sólo por el contenido del array que lo describe, y por su nombre. Por ejemplo, el segundo nivel podría ser así:
public class Nivel02 : Nivel { public Nivel02() { nombre = "The Cold Room"; datosNivelIniciales[ 0] = "L LLLLLLLLLLLLL"; datosNivelIniciales[ 1] = "L V V TL"; datosNivelIniciales[ 2] = "L L"; datosNivelIniciales[ 3] = "L FFFF L"; datosNivelIniciales[ 4] = "L L"; datosNivelIniciales[ 5] = "LSSSSSSSSSSSSSSSSSSS L L"; datosNivelIniciales[ 6] = "L SSSSLFFL L"; datosNivelIniciales[ 7] = "LSFFFFF LV L L"; datosNivelIniciales[ 8] = "L LFFL L"; datosNivelIniciales[ 9] = "L V SSSSSSS LFFL L"; datosNivelIniciales[10] = "L FFFF LFFL L"; datosNivelIniciales[11] = "L DDDD LFFL L"; datosNivelIniciales[12] = "L SSSS V LFFL L"; datosNivelIniciales[13] = "L FFFF PPL"; datosNivelIniciales[14] = "L PPL"; datosNivelIniciales[15] = "LSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSL"; Reiniciar(); } } /* fin de la clase Nivel02 */
Y se vería así:
Y el tercer nivel así:
public class Nivel03 : Nivel { public Nivel03() { nombre = "The Menagerie"; datosNivelIniciales[ 0] = "M V T V T V T M"; datosNivelIniciales[ 1] = "M M"; datosNivelIniciales[ 2] = "M M"; datosNivelIniciales[ 3] = "M M"; datosNivelIniciales[ 4] = "M M"; datosNivelIniciales[ 5] = "MNNNNOOOOOOOOOOOOOOOOOOOOOO OOM"; datosNivelIniciales[ 6] = "M V VM"; datosNivelIniciales[ 7] = "MNNNNNN NNNNM"; datosNivelIniciales[ 8] = "MT M"; datosNivelIniciales[ 9] = "M DDDDDD M"; datosNivelIniciales[10] = "M NNNNNNM"; datosNivelIniciales[11] = "M NNNNN PPM"; datosNivelIniciales[12] = "M NNNNNN PPM"; datosNivelIniciales[13] = "M NNNNNNNNNNM"; datosNivelIniciales[14] = "M M"; datosNivelIniciales[15] = "MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM"; Reiniciar(); } } /* fin de la clase Nivel03 */
Que se vería:
Ahora la nueva clase "Nivel" sería la que tendría la lógica común a todos los niveles. Básicamente contendrá todo lo que antes estaba en "Mapa", junto con un nuevo "GetNombre" que permita saber el nombre del nivel actual:
public class Nivel { // Datos del mapa del nivel actual Partida miPartida; private int altoMapa = 16, anchoMapa = 32; private int anchoTile = 24, altoTile = 24; private int margenIzqdo = 20, margenSuperior = 40; private int llavesRestantes = 4; // Valor arbitrario (no 0: final de partida) ElemGrafico arbol, deslizante, ladrillo, ladrilloX, llave, puerta, sueloFino, sueloFragil, sueloFragil2, sueloGrueso, techo; protected string nombre = "(Nonamed)"; string[] datosNivel; // Datos en el momento de juego protected string[] datosNivelIniciales = // Datos para reiniciar {"LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", "L L", "L L", "L L", "L L", "L L", "L L", "L L", "L L", "L L", "L L", "L L", "L L", "L PPL", "L PPL", "LSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSL"}; // Constructor public Nivel() { //miPartida = p; // Para enlazar con el resto de componentes arbol = new ElemGrafico("imagenes/arbol.png"); deslizante = new ElemGrafico("imagenes/deslizante.png"); ladrillo = new ElemGrafico("imagenes/ladrillo.png"); ladrilloX = new ElemGrafico("imagenes/ladrillo2.png"); llave = new ElemGrafico("imagenes/llave.png"); puerta = new ElemGrafico("imagenes/puerta.png"); sueloFino = new ElemGrafico("imagenes/suelo.png"); sueloGrueso = new ElemGrafico("imagenes/suelo2.png"); sueloFragil = new ElemGrafico("imagenes/sueloFragil.png"); sueloFragil2 = new ElemGrafico("imagenes/sueloFragil2.png"); techo = new ElemGrafico("imagenes/techo.png"); datosNivel = new string[altoMapa]; Reiniciar(); } public void Reiniciar() { for (int fila = 0; fila < altoMapa; fila++) datosNivel[fila] = datosNivelIniciales[fila]; } public void DibujarOculta() { // Dibujo el fondo llavesRestantes = 0; for (int fila = 0; fila < altoMapa; fila++) for (int colum = 0; colum < anchoMapa; colum++) { int posX = colum * anchoTile + margenIzqdo; int posY = fila * altoTile + margenSuperior; switch (datosNivel[fila][colum]) { case 'A': arbol.DibujarOculta(posX, posY); break; case 'D': deslizante.DibujarOculta(posX, posY); break; case 'F': sueloFragil.DibujarOculta(posX, posY); break; case 'L': ladrillo.DibujarOculta(posX, posY); break; case 'M': ladrilloX.DibujarOculta(posX, posY); break; case 'N': sueloGrueso.DibujarOculta(posX, posY); break; case 'O': sueloFragil2.DibujarOculta(posX, posY); break; case 'P': puerta.DibujarOculta(posX, posY); break; case 'S': sueloFino.DibujarOculta(posX, posY); break; case 'T': techo.DibujarOculta(posX, posY); break; case 'V': llave.DibujarOculta(posX, posY); llavesRestantes++; break; } } } public bool EsPosibleMover(int x, int y, int xmax, int ymax) { // Compruebo si choca con alguna casilla del fondo for (int fila = 0; fila < altoMapa; fila++) for (int colum = 0; colum < anchoMapa; colum++) { int posX = colum * anchoTile + margenIzqdo; int posY = fila * altoTile + margenSuperior; // Si se solapa con la posic a la que queremos mover if ((posX+anchoTile > x) && (posY+altoTile > y) && (xmax> posX) && (ymax > posY)) // Y no es espacio blanco, llave, puerta o arbol if ((datosNivel[fila][colum] != ' ') && (datosNivel[fila][colum] != 'V') && (datosNivel[fila][colum] != 'P') && (datosNivel[fila][colum] != 'A')) { return false; } } return true; } public int ObtenerPuntosPosicion(int x, int y, int xmax, int ymax) { // Compruebo si choca con alguna casilla del fondo for (int fila = 0; fila < altoMapa; fila++) for (int colum = 0; colum < anchoMapa; colum++) { int posX = colum * anchoTile + margenIzqdo; int posY = fila * altoTile + margenSuperior; // Si choca con la casilla que estoy mirando if ( (posX + anchoTile > x) && (posY + altoTile > y) && (xmax > posX) && (ymax > posY)) { // Si choca con el techo o con un arbol if ((datosNivel[fila][colum] == 'T') || (datosNivel[fila][colum] == 'A')) return -1; // (puntuacion -1: perder vida) // Si toca una llave if (datosNivel[fila][colum] == 'V') { // datosNivel[fila][colum] = ' '; (No valido en C#: 2 pasos) datosNivel[fila] = datosNivel[fila].Remove(colum, 1); datosNivel[fila] = datosNivel[fila].Insert(colum, " "); return 10; } // Si toca la puerta y no quedan llaves, 50 puntos // y (pronto) pasar al nivel siguiente if ((datosNivel[fila][colum] == 'P') && (llavesRestantes == 0)) { return 50; } } // Fin de comprobacion de casilla actual } // Fin de "for" que revisa todas las casillas // Si llego hasta aqui, es que no hay puntos que devolver return 0; } public string LeerNombre() { return nombre; } } /* fin de la clase Mapa */
Y la clase "Mapa" ya no hace casi nada por ella misma: se limita a crear un array de niveles, y a preguntar al nivel actual cada dato que se le pida desde el cuerpo del programa, y también tendrá un método "Avanzar" que pase al siguiente nivel:
public class Mapa { Nivel nivelActual; Nivel[] listaNiveles; const int MAX_NIVELES = 3; int numeroNivelActual = 0; Fuente fuente18; // Tipo de letra para mensajes // Constructor public Mapa(Partida p) { listaNiveles = new Nivel[MAX_NIVELES]; listaNiveles[0] = new Nivel01(); listaNiveles[1] = new Nivel02(); listaNiveles[2] = new Nivel03(); nivelActual = listaNiveles[ numeroNivelActual ]; fuente18 = new Fuente("FreeSansBold.ttf", 18); } public void Reiniciar() { nivelActual.Reiniciar(); } public void DibujarOculta() { nivelActual.DibujarOculta(); } public bool EsPosibleMover(int x, int y, int xmax, int ymax) { return nivelActual.EsPosibleMover(x, y, xmax, ymax); } public int ObtenerPuntosPosicion(int x, int y, int xmax, int ymax) { return nivelActual.ObtenerPuntosPosicion(x, y, xmax, ymax); } public void Avanzar() { numeroNivelActual++; if (numeroNivelActual >= MAX_NIVELES) numeroNivelActual = 0; // Rectángulo de fondo Hardware.RectanguloRellenoRGBA( 200, 100, 600, 300, // Posicion, ancho y alto de la pantalla 200, 200, 200, // Gris claro 200); // Con algo de transparencia // Y texto de aviso Hardware.EscribirTextoOculta( "Pasando al nivel "+(numeroNivelActual+1), 300, 200, 0, 0, 0, fuente18); Hardware.VisualizarOculta(); Hardware.Pausa(2000); nivelActual = listaNiveles[numeroNivelActual]; nivelActual.Reiniciar(); } public string GetNombre() { return nivelActual.LeerNombre(); } } /* fin de la clase Mapa */
Ya teníamos la infraestructura preparada: desde la función de "ComprobarColisiones" de la "Partida" ya mirábamos si se habían recogido todas las llaves y se tocaba la puerta, pero nos limitábamos a dar 50 puntos al jugador. Ahora también avisaremos de que hay que avanzar al siguiente nivel:
// Si ademas es una puerta, avanzamos de nivel if (puntosMovimiento == 50) miPantallaJuego.Avanzar();
Sencillo: una vez que tenemos preparada la orden que nos permite avanzar de nivel, basta comprobar si se pulsa cierta combinación de teclas para activarla:
if ( (Hardware.TeclaPulsada(Hardware.TECLA_T)) && (Hardware.TeclaPulsada(Hardware.TECLA_N)) ) miPantallaJuego.Avanzar();
No necesitamos casi nada más, pero ya que hemos añadido un "nombre" a cada nivel, podemos ampliar un poco la clase Marcador para que lo muestre en pantalla.
Como siempre, puedes encontrar la versión 0.11 completa en la página del proyecto en Google Code.