29. Paredes que no se pueden atravesar
Hemos visto como dibujar un fondo formado a partir de "tiles" (casillas repetitivas). Ahora vamos a ver cómo comprobar colisiones con los elementos que forman ese fondo, de modo que no se puedan atravesar las paredes.
La primera idea es que tendremos que comprobar si es posible movernos a una cierta posición antes de desplazarnos a ella. Esa posición será la misma que ocupa el personaje, pero ligeramente desplazada. Por ejemplo, si nuestro personaje ocupa un rectángulo que empieza en las coordenadas (x,y) y termina en (x+ancho,y+alto), cuando se mueva hacia la derecha pasará a ocupar un rectángulo que empezará en (x+incrX,y) y termina en (x+ancho+incrX,y+alto). Del mismo modo, podemos saber qué rectángulo ocupará si se mueve a izquierda (-incrX), hacia arriba (-incrY) o hacia abajo (+incrY).
Así, podemos crear una función "EsPosibleMover", que reciba esas coordenadas mínimas y máximas del rectángulo, y compruebe si esa es una posición válida para nuestro personaje.
Esa función "EsPosibleMover" se limitaría a comprobar si chocamos con alguno de los elementos del fondo (y en ese caso no podremos mover, luego "EsPosibleMover" devolverá "false") o si no chocamos con nada (y entonces sí podremos mover, por lo que devolverá "true"):
public static bool EsPosibleMover(int x, int y, int xFin, int yFin) { // Veo si choca con algún elemento del fondo for (int fila = 0; fila < altoFondo; fila++) // Fondo for (int col = 0; col < anchoFondo; col++) if ((fondo[fila, col] == 1) && Colision( x,y,xFin,yFin, // Posicion del personaje margenXFondo + col * anchoCasillaFondo, // x inicial de casilla actual de fondo margenYFondo + fila * altoCasillaFondo, // y inicial margenXFondo + (col+1) * anchoCasillaFondo, // x final margenYFondo + (fila+1) * altoCasillaFondo)) // y final return false; // Si no ha chocado con ninguno, es posible moverse a esa posición return true; }
Hasta ahora sólo teníamos una función "Colision" que comprobaba si chocaban dos "elementos gráficos"; como nuestro fondo no está formado por elementos gráficos, sino que nos limitamos a dibujar una imagen de forma repetitiva, necesitaríamos una variante de la función "Colisión" que sea capaz de comprobar si chocan dos rectángulos, sean elementos gráficos o no. Podría ser así:
public static bool Colision( int x1, int y1, int xFin1, int yFin1, int x2, int y2, int xFin2, int yFin2) { if ((xFin2 > x1) && (x2 < xFin1) && (yFin2 > y1) && (y2 < yFin1)) return true; else return false; }
Como se puede ver, en C# (y en otros muchos lenguajes modernos) podemos tener dos funciones que se llaman igual pero que reciben distintos parámetros. No es problema, el compilador será capaz de deducir a cual de ellas llamamos en cada caso.
Finalmente, como ya habíamos anticipado, a la hora de comprobar teclas, no cambiaremos el valor de X o Y de nuestro personaje hasta que no veamos si realmente podemos mover a una cierta posición:
if (Hardware.TeclaPulsada(Hardware.TECLA_DER) && EsPosibleMover(personaje.x + personaje.incrX, personaje.y, personaje.x + personaje.ancho + personaje.incrX, personaje.y + personaje.alto)) personaje.x += personaje.incrX; if (Hardware.TeclaPulsada(Hardware.TECLA_IZQ) && EsPosibleMover(personaje.x - personaje.incrX, personaje.y, personaje.x + personaje.ancho - personaje.incrX, personaje.y + personaje.alto)) personaje.x -= personaje.incrX; ...
Con esos cambios, el fuente completo quedaría así:
// Primer mini-esqueleto de juego en modo gráfico // Versión "h1" using System; using System.Threading; // Para Thread.Sleep public class Juego05h1 { public struct ElemGrafico { public int x; public int y; public int xInicial; public int yInicial; public int ancho; public int alto; public int incrX; public int incrY; public Imagen imagen; public bool visible; } static byte anchoFondo = 20; static byte altoFondo = 16; static short margenXFondo = 80; static byte margenYFondo = 30; static byte anchoCasillaFondo = 32; static byte altoCasillaFondo = 32; static Imagen imgPared; static public byte[,] fondo = { {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} }; static ElemGrafico personaje; static Fuente tipoDeLetra; static int numPremios, numEnemigos, numObstaculos; static ElemGrafico[] obstaculos; static ElemGrafico[] enemigos; static ElemGrafico[] premios; static bool juegoTerminado; static int vidas; static int puntos; static bool partidaTerminada; static Random generador; static Imagen fondoPresentacion; static Imagen fondoAyuda; static Imagen fondoCreditos; public static void InicializarJuego() { // Entrar a modo grafico 800x600 bool pantallaCompleta = false; Hardware.Inicializar(800, 600, 24, pantallaCompleta); // Resto de inicializacion tipoDeLetra = new Fuente("FreeSansBold.ttf", 18); juegoTerminado = false; numPremios = 10; numEnemigos = 10; numObstaculos = 20; obstaculos = new ElemGrafico[numObstaculos]; enemigos = new ElemGrafico[numEnemigos]; premios = new ElemGrafico[numPremios]; generador = new Random(); // Cargo imágenes de elementos personaje.imagen = new Imagen("personaje.png"); for (int i = 0; i < numObstaculos; i++) // Obstaculos obstaculos[i].imagen = new Imagen("obstaculo.png"); for (int i = 0; i < numEnemigos; i++) // Enemigos enemigos[i].imagen = new Imagen("enemigo.png"); for (int i = 0; i < numPremios; i++) // Premios premios[i].imagen = new Imagen("premio.png"); imgPared = new Imagen("pared.png"); // Y cargo las imagenes de la presentación, ayuda y créditos fondoPresentacion = new Imagen("present.jpg"); fondoAyuda = new Imagen("present.jpg"); fondoCreditos = new Imagen("present.jpg"); } public static void InicializarPartida() { // En cada partida, hay que reiniciar ciertas variables vidas = 3; puntos = 0; partidaTerminada = false; personaje.xInicial = 400; personaje.yInicial = 300; personaje.x = personaje.xInicial; personaje.y = personaje.yInicial; personaje.visible = true; personaje.ancho = 32; personaje.alto = 30; personaje.incrX = 10; personaje.incrY = 10; // Genero las posiciones de los elementos al azar for (int i = 0; i < numObstaculos; i++) // Obstaculos { obstaculos[i].visible = true; obstaculos[i].ancho = 38; obstaculos[i].alto = 22; // Al colocar un obstáculo, compruebo que no choque // con el personaje, para que la partida // no acabe nada más empezar do { obstaculos[i].x = generador.Next(50, 700); obstaculos[i].y = generador.Next(30, 550); } while (Colision(obstaculos[i], personaje)); } for (int i = 0; i < numEnemigos; i++) // Enemigos { enemigos[i].incrX = 5; enemigos[i].visible = true; enemigos[i].ancho = 36; enemigos[i].alto = 42; enemigos[i].x = generador.Next(50, 700); // Para la Y, compruebo que no sea del rango de // la del personaje, para que la partida // no acabe nada más empezar do { enemigos[i].y = generador.Next(30, 550); } while ((enemigos[i].y + enemigos[i].alto > personaje.y) && (enemigos[i].y < personaje.y + personaje.alto)); } for (int i = 0; i < numPremios; i++) // Premios { premios[i].x = generador.Next(50, 700); premios[i].y = generador.Next(30, 550); premios[i].visible = true; premios[i].ancho = 34; premios[i].alto = 18; } } public static void MostrarPresentacion() { bool finPresentacion = false; do { // ---- Pantalla de presentación -- Hardware.BorrarPantallaOculta(0, 0, 0); // Fondo de la presentación fondoPresentacion.DibujarOculta(0, 0); // Marcador Hardware.EscribirTextoOculta("Jueguecillo", 340, 200, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Escoja una opción:", 310, 300, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("J.- Jugar una partida", 150, 390, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("A.- Ayuda", 150, 430, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("C.- Créditos", 150, 470, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("S.- Salir", 150, 510, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.VisualizarOculta(); Hardware.Pausa(20); if (Hardware.TeclaPulsada(Hardware.TECLA_A)) MostrarAyuda(); if (Hardware.TeclaPulsada(Hardware.TECLA_C)) MostrarCreditos(); if (Hardware.TeclaPulsada(Hardware.TECLA_J)) finPresentacion = true; if (Hardware.TeclaPulsada(Hardware.TECLA_S)) { finPresentacion = true; partidaTerminada = true; juegoTerminado = true; } } while (!finPresentacion); } public static void MostrarAyuda() { string[] textosAyuda = { "Recoge los premios", "Evita los obstáculos y los enemigos", "Usa las flechas de cursor para mover" }; // ---- Pantalla de presentación -- Hardware.BorrarPantallaOculta(0, 0, 0); // Fondo de la presentación fondoAyuda.DibujarOculta(0, 0); // Marcador Hardware.EscribirTextoOculta("Ayuda", 340, 200, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); // Textos repetitivos short posicYtexto = 280; foreach (string texto in textosAyuda) { Hardware.EscribirTextoOculta(texto, 150, posicYtexto, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); posicYtexto += 30; } Hardware.EscribirTextoOculta("ESC- volver", 650, 530, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.VisualizarOculta(); do { Hardware.Pausa(20); } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC)); } public static void MostrarCreditos() { // ---- Pantalla de presentación -- Hardware.BorrarPantallaOculta(0, 0, 0); // Fondo de la presentación fondoCreditos.DibujarOculta(0, 0); // Marcador Hardware.EscribirTextoOculta("Creditos", 250, 200, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Por Nacho Cabanes, 2011", 250, 300, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("ESC- volver", 650, 530, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.VisualizarOculta(); do { Hardware.Pausa(20); } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC)); } public static void Dibujar() { // -- Dibujar -- Hardware.BorrarPantallaOculta(0, 0, 0); // Marcador Hardware.EscribirTextoOculta("Vidas Puntos", 0, 0, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); Hardware.EscribirTextoOculta(Convert.ToString(vidas), 70, 0, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta(Convert.ToString(puntos), 190, 0, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); // Pared de fondo for (int fila = 0; fila < altoFondo; fila++) // Fondo for (int col = 0; col < anchoFondo; col++) if (fondo[fila, col] == 1) imgPared.DibujarOculta( margenXFondo + col * anchoCasillaFondo, margenYFondo + fila * altoCasillaFondo); for (int i = 0; i < numObstaculos; i++) // Obstáculos { obstaculos[i].imagen.DibujarOculta( (int)obstaculos[i].x, (int)obstaculos[i].y); } for (int i = 0; i < numEnemigos; i++) // Enemigos { enemigos[i].imagen.DibujarOculta( (int)enemigos[i].x, (int)enemigos[i].y); } for (int i = 0; i < numPremios; i++) // Premios { if (premios[i].visible) { premios[i].imagen.DibujarOculta( premios[i].x, premios[i].y); } } personaje.imagen.DibujarOculta( personaje.x, personaje.y); // Finalmente, muestro en pantalla Hardware.VisualizarOculta(); } public static void ComprobarTeclas() { // -- Leer teclas y calcular nueva posición -- if (Hardware.TeclaPulsada(Hardware.TECLA_ESC)) partidaTerminada = true; if (Hardware.TeclaPulsada(Hardware.TECLA_DER) && EsPosibleMover(personaje.x + personaje.incrX, personaje.y, personaje.x + personaje.ancho + personaje.incrX, personaje.y + personaje.alto)) personaje.x += personaje.incrX; if (Hardware.TeclaPulsada(Hardware.TECLA_IZQ) && EsPosibleMover(personaje.x - personaje.incrX, personaje.y, personaje.x + personaje.ancho - personaje.incrX, personaje.y + personaje.alto)) personaje.x -= personaje.incrX; if (Hardware.TeclaPulsada(Hardware.TECLA_ARR) && EsPosibleMover(personaje.x, personaje.y - personaje.incrY, personaje.x + personaje.ancho, personaje.y + personaje.alto - personaje.incrY)) personaje.y -= personaje.incrY; if (Hardware.TeclaPulsada(Hardware.TECLA_ABA) && EsPosibleMover(personaje.x, personaje.y + personaje.incrY, personaje.x + personaje.ancho, personaje.y + personaje.alto + personaje.incrY)) personaje.y += personaje.incrY; } public static void MoverElementos() { // -- Mover enemigos, entorno -- for (int i = 0; i < numEnemigos; i++) // Enemigos { enemigos[i].x = enemigos[i].x + enemigos[i].incrX; if (((int)enemigos[i].x <= 50) || ((int)enemigos[i].x >= 700)) enemigos[i].incrX = -enemigos[i].incrX; } } public static void ComprobarColisiones() { // -- Colisiones, perder vidas, etc -- for (int i = 0; i < numObstaculos; i++) // Obstáculos { if (Colision(obstaculos[i], personaje)) { vidas--; if (vidas == 0) partidaTerminada = true; personaje.x = personaje.xInicial; personaje.y = personaje.yInicial; } } for (int i = 0; i < numPremios; i++) // Premios { if (Colision(premios[i], personaje)) { puntos += 10; premios[i].visible = false; } } for (int i = 0; i < numEnemigos; i++) // Enemigos { if (Colision(enemigos[i], personaje)) { vidas--; if (vidas == 0) partidaTerminada = true; personaje.x = personaje.xInicial; personaje.y = personaje.yInicial; } } } public static void PausaFotograma() { // -- Pausa hasta el siguiente "fotograma" del juego -- Hardware.Pausa(20); } public static bool Colision(ElemGrafico e1, ElemGrafico e2) { // No se debe chocar con un elemento oculto if ((e1.visible == false) || (e2.visible == false)) return false; // Ahora ya compruebo coordenadas if ((e1.x + e1.ancho > e2.x) && (e1.x < e2.x + e2.ancho) && (e1.y + e1.alto > e2.y) && (e1.y < e2.y + e2.alto)) return true; else return false; } public static bool Colision( int x1, int y1, int xFin1, int yFin1, int x2, int y2, int xFin2, int yFin2) { if ((xFin2 > x1) && (x2 < xFin1) && (yFin2 > y1) && (y2 < yFin1)) return true; else return false; } public static bool EsPosibleMover(int x, int y, int xFin, int yFin) { // Veo si choca con algún elemento del fondo for (int fila = 0; fila < altoFondo; fila++) // Fondo for (int col = 0; col < anchoFondo; col++) if ((fondo[fila, col] == 1) && Colision( x,y,xFin,yFin, // Posicion del personaje margenXFondo + col * anchoCasillaFondo, // x inicial de casilla actual de fondo margenYFondo + fila * altoCasillaFondo, // y inicial margenXFondo + (col+1) * anchoCasillaFondo, // x final margenYFondo + (fila+1) * altoCasillaFondo)) // y final return false; // Si no ha chocado con ninguno, es posible moverse a esa posición return true; } public static void Main() { InicializarJuego(); while (!juegoTerminado) { InicializarPartida(); MostrarPresentacion(); // ------ Bucle de juego ------ while (!partidaTerminada) { Dibujar(); ComprobarTeclas(); MoverElementos(); ComprobarColisiones(); PausaFotograma(); } // Fin del bucle de juego } // Fin de partida } // Fin de Main }
Este planteamiento permite gastar muy poca memoria, porque la imagen repetitiva del fondo se carga sólo una vez. A cambio, complica otras posibilidades, como la de incluir los premios y obstáculos mortales como parte del "mapa" del fondo. Por eso, también podemos usar un planteamiento alternativo: a partir del "mapa" del fondo, crear un array de elementos gráficos, y comprobar colisiones directamente con todos esos elementos gráficos.
Si lo planteamos así, la función "ComprobarTeclas" no cambiará, pero sí lo hará "EsPosibleMover", que comprobará colisiones y también ampliaremos "InicializarJuego" para preparar ese nuevo array de elementos gráficos.
Vamos a empezar por ver los cambios en InicializarJuego: crearíamos un nuevo array, que ni siquiera hace falta que sea tan grande como el fondo, sino que puede estar formado por las casillas que no estén vacías:
// Cuento la cantidad de elementos reales en el fondo elementosFondo = 0; for (int fila = 0; fila < altoFondo; fila++) // Fondo for (int col = 0; col < anchoFondo; col++) if (fondo[fila, col] != 0) elementosFondo++; // Y reservo espacio para el array que los contiene fondos = new ElemGrafico[elementosFondo]; // y para cada uno de uno de ellos int posicFondo = 0; for (int fila = 0; fila < altoFondo; fila++) // Fondo for (int col = 0; col < anchoFondo; col++) if (fondo[fila, col] != 0) { fondos[posicFondo].x = margenXFondo + col * anchoCasillaFondo; fondos[posicFondo].y = margenYFondo + fila * altoCasillaFondo; fondos[posicFondo].imagen = new Imagen("pared.png"); fondos[posicFondo].ancho = anchoCasillaFondo; fondos[posicFondo].alto = altoCasillaFondo; fondos[posicFondo].visible = true; posicFondo++; }
En vez de crear una nueva función "Colision" alternativa, podemos hacer que "EsPosibleMover" sea la que compruebe si los rectángulos de esos nuevos elementos gráficos se solapan con las coordenadas que le indiquemos:
public static bool EsPosibleMover(int x, int y, int xFin, int yFin) { // Veo si choca con algún elemento del fondo for (int i = 0; i < elementosFondo; i++) if ((fondos[i].x + fondos[i].ancho > x) && (fondos[i].x < xFin) && (fondos[i].y + fondos[i].alto > y) && (fondos[i].y < yFin)) return false; // Si no ha chocado con ninguno, es posible moverse a esa posición return true; }
Y ya que estamos, como tenemos un array de elementos gráficos, podemos simplificar la parte de "Dibujar" que se encarga de mostrar el fondo:
for (int i = 0; i < elementosFondo; i++) // Imágenes del fondo fondos[i].imagen.DibujarOculta(fondos[i].x, fondos[i].y);
Con estos cambios, esta versión alternativa del fuente sería:
// Primer mini-esqueleto de juego en modo gráfico // Versión "h" using System; using System.Threading; // Para Thread.Sleep public class Juego05h { public struct ElemGrafico { public int x; public int y; public int xInicial; public int yInicial; public int ancho; public int alto; public int incrX; public int incrY; public Imagen imagen; public bool visible; } static byte anchoFondo = 20; static byte altoFondo = 16; static short margenXFondo = 80; static byte margenYFondo = 30; static byte anchoCasillaFondo = 32; static byte altoCasillaFondo = 32; static Imagen imgPared; static public byte[,] fondo = { {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} }; static ElemGrafico personaje; static Fuente tipoDeLetra; static int numPremios, numEnemigos, numObstaculos; static ElemGrafico[] obstaculos; static ElemGrafico[] enemigos; static ElemGrafico[] premios; static ElemGrafico[] fondos; static int elementosFondo; static bool juegoTerminado; static int vidas; static int puntos; static bool partidaTerminada; static Random generador; static Imagen fondoPresentacion; static Imagen fondoAyuda; static Imagen fondoCreditos; public static void InicializarJuego() { // Entrar a modo grafico 800x600 bool pantallaCompleta = false; Hardware.Inicializar(800, 600, 24, pantallaCompleta); // Resto de inicializacion tipoDeLetra = new Fuente("FreeSansBold.ttf", 18); juegoTerminado = false; numPremios = 10; numEnemigos = 10; numObstaculos = 20; obstaculos = new ElemGrafico[numObstaculos]; enemigos = new ElemGrafico[numEnemigos]; premios = new ElemGrafico[numPremios]; generador = new Random(); // Cuento la cantidad de elementos reales en el fondo elementosFondo = 0; for (int fila = 0; fila < altoFondo; fila++) // Fondo for (int col = 0; col < anchoFondo; col++) if (fondo[fila, col] != 0) elementosFondo++; // Y reservo espacio para el array que los contiene fondos = new ElemGrafico[elementosFondo]; // y para cada uno de uno de ellos int posicFondo = 0; for (int fila = 0; fila < altoFondo; fila++) // Fondo for (int col = 0; col < anchoFondo; col++) if (fondo[fila, col] != 0) { fondos[posicFondo].x = margenXFondo + col * anchoCasillaFondo; fondos[posicFondo].y = margenYFondo + fila * altoCasillaFondo; fondos[posicFondo].imagen = new Imagen("pared.png"); fondos[posicFondo].ancho = anchoCasillaFondo; fondos[posicFondo].alto = altoCasillaFondo; fondos[posicFondo].visible = true; posicFondo++; } // Cargo imágenes de elementos personaje.imagen = new Imagen("personaje.png"); for (int i = 0; i < numObstaculos; i++) // Obstaculos obstaculos[i].imagen = new Imagen("obstaculo.png"); for (int i = 0; i < numEnemigos; i++) // Enemigos enemigos[i].imagen = new Imagen("enemigo.png"); for (int i = 0; i < numPremios; i++) // Premios premios[i].imagen = new Imagen("premio.png"); imgPared = new Imagen("pared.png"); // Y cargo las imagenes de la presentación, ayuda y créditos fondoPresentacion = new Imagen("present.jpg"); fondoAyuda = new Imagen("present.jpg"); fondoCreditos = new Imagen("present.jpg"); } public static void InicializarPartida() { // En cada partida, hay que reiniciar ciertas variables vidas = 3; puntos = 0; partidaTerminada = false; personaje.xInicial = 400; personaje.yInicial = 300; personaje.x = personaje.xInicial; personaje.y = personaje.yInicial; personaje.visible = true; personaje.ancho = 32; personaje.alto = 30; personaje.incrX = 10; personaje.incrY = 10; // Genero las posiciones de los elementos al azar for (int i = 0; i < numObstaculos; i++) // Obstaculos { obstaculos[i].visible = true; obstaculos[i].ancho = 38; obstaculos[i].alto = 22; // Al colocar un obstáculo, compruebo que no choque // con el personaje, para que la partida // no acabe nada más empezar do { obstaculos[i].x = generador.Next(50, 700); obstaculos[i].y = generador.Next(30, 550); } while (Colision(obstaculos[i], personaje)); } for (int i = 0; i < numEnemigos; i++) // Enemigos { enemigos[i].incrX = 5; enemigos[i].visible = true; enemigos[i].ancho = 36; enemigos[i].alto = 42; enemigos[i].x = generador.Next(50, 700); // Para la Y, compruebo que no sea del rango de // la del personaje, para que la partida // no acabe nada más empezar do { enemigos[i].y = generador.Next(30, 550); } while ((enemigos[i].y + enemigos[i].alto > personaje.y) && (enemigos[i].y < personaje.y + personaje.alto)); } for (int i = 0; i < numPremios; i++) // Premios { premios[i].x = generador.Next(50, 700); premios[i].y = generador.Next(30, 550); premios[i].visible = true; premios[i].ancho = 34; premios[i].alto = 18; } } public static void MostrarPresentacion() { bool finPresentacion = false; do { // ---- Pantalla de presentación -- Hardware.BorrarPantallaOculta(0, 0, 0); // Fondo de la presentación fondoPresentacion.DibujarOculta(0, 0); // Marcador Hardware.EscribirTextoOculta("Jueguecillo", 340, 200, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Escoja una opción:", 310, 300, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("J.- Jugar una partida", 150, 390, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("A.- Ayuda", 150, 430, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("C.- Créditos", 150, 470, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("S.- Salir", 150, 510, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.VisualizarOculta(); Hardware.Pausa(20); if (Hardware.TeclaPulsada(Hardware.TECLA_A)) MostrarAyuda(); if (Hardware.TeclaPulsada(Hardware.TECLA_C)) MostrarCreditos(); if (Hardware.TeclaPulsada(Hardware.TECLA_J)) finPresentacion = true; if (Hardware.TeclaPulsada(Hardware.TECLA_S)) { finPresentacion = true; partidaTerminada = true; juegoTerminado = true; } } while (!finPresentacion); } public static void MostrarAyuda() { string[] textosAyuda = { "Recoge los premios", "Evita los obstáculos y los enemigos", "Usa las flechas de cursor para mover" }; // ---- Pantalla de presentación -- Hardware.BorrarPantallaOculta(0, 0, 0); // Fondo de la presentación fondoAyuda.DibujarOculta(0, 0); // Marcador Hardware.EscribirTextoOculta("Ayuda", 340, 200, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); // Textos repetitivos short posicYtexto = 280; foreach (string texto in textosAyuda) { Hardware.EscribirTextoOculta(texto, 150, posicYtexto, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); posicYtexto += 30; } Hardware.EscribirTextoOculta("ESC- volver", 650, 530, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.VisualizarOculta(); do { Hardware.Pausa(20); } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC)); } public static void MostrarCreditos() { // ---- Pantalla de presentación -- Hardware.BorrarPantallaOculta(0, 0, 0); // Fondo de la presentación fondoCreditos.DibujarOculta(0, 0); // Marcador Hardware.EscribirTextoOculta("Creditos", 250, 200, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Por Nacho Cabanes, 2011", 250, 300, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("ESC- volver", 650, 530, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.VisualizarOculta(); do { Hardware.Pausa(20); } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC)); } public static void Dibujar() { // -- Dibujar -- Hardware.BorrarPantallaOculta(0, 0, 0); // Marcador Hardware.EscribirTextoOculta("Vidas Puntos", 0, 0, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); Hardware.EscribirTextoOculta(Convert.ToString(vidas), 70, 0, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta(Convert.ToString(puntos), 190, 0, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); for (int i = 0; i < elementosFondo; i++) // Imágenes del fondo fondos[i].imagen.DibujarOculta(fondos[i].x, fondos[i].y); for (int i = 0; i < numObstaculos; i++) // Obstáculos obstaculos[i].imagen.DibujarOculta(obstaculos[i].x, obstaculos[i].y); for (int i = 0; i < numEnemigos; i++) // Enemigos enemigos[i].imagen.DibujarOculta(enemigos[i].x, enemigos[i].y); for (int i = 0; i < numPremios; i++) // Premios if (premios[i].visible) premios[i].imagen.DibujarOculta(premios[i].x, premios[i].y); personaje.imagen.DibujarOculta( personaje.x, personaje.y); // Finalmente, muestro en pantalla Hardware.VisualizarOculta(); } public static void ComprobarTeclas() { // -- Leer teclas y calcular nueva posición -- if (Hardware.TeclaPulsada(Hardware.TECLA_ESC)) partidaTerminada = true; if (Hardware.TeclaPulsada(Hardware.TECLA_DER) && EsPosibleMover(personaje.x + personaje.incrX, personaje.y, personaje.x + personaje.ancho + personaje.incrX, personaje.y + personaje.alto)) personaje.x += personaje.incrX; if (Hardware.TeclaPulsada(Hardware.TECLA_IZQ) && EsPosibleMover(personaje.x - personaje.incrX, personaje.y, personaje.x + personaje.ancho - personaje.incrX, personaje.y + personaje.alto)) personaje.x -= personaje.incrX; if (Hardware.TeclaPulsada(Hardware.TECLA_ARR) && EsPosibleMover(personaje.x, personaje.y - personaje.incrY, personaje.x + personaje.ancho, personaje.y + personaje.alto - personaje.incrY)) personaje.y -= personaje.incrY; if (Hardware.TeclaPulsada(Hardware.TECLA_ABA) && EsPosibleMover(personaje.x, personaje.y + personaje.incrY, personaje.x + personaje.ancho, personaje.y + personaje.alto + personaje.incrY)) personaje.y += personaje.incrY; } public static void MoverElementos() { // -- Mover enemigos, entorno -- for (int i = 0; i < numEnemigos; i++) // Enemigos { enemigos[i].x = enemigos[i].x + enemigos[i].incrX; if (((int)enemigos[i].x <= 50) || ((int)enemigos[i].x >= 700)) enemigos[i].incrX = -enemigos[i].incrX; } } public static void ComprobarColisiones() { // -- Colisiones, perder vidas, etc -- for (int i = 0; i < numObstaculos; i++) // Obstáculos { if (Colision(obstaculos[i], personaje)) { vidas--; if (vidas == 0) partidaTerminada = true; personaje.x = personaje.xInicial; personaje.y = personaje.yInicial; } } for (int i = 0; i < numPremios; i++) // Premios { if (Colision(premios[i], personaje)) { puntos += 10; premios[i].visible = false; } } for (int i = 0; i < numEnemigos; i++) // Enemigos { if (Colision(enemigos[i], personaje)) { vidas--; if (vidas == 0) partidaTerminada = true; personaje.x = personaje.xInicial; personaje.y = personaje.yInicial; } } } public static void PausaFotograma() { // -- Pausa hasta el siguiente "fotograma" del juego -- Hardware.Pausa(20); } public static bool Colision(ElemGrafico e1, ElemGrafico e2) { // No se debe chocar con un elemento oculto if ((e1.visible == false) || (e2.visible == false)) return false; // Ahora ya compruebo coordenadas if ((e1.x + e1.ancho > e2.x) && (e1.x < e2.x + e2.ancho) && (e1.y + e1.alto > e2.y) && (e1.y < e2.y + e2.alto)) return true; else return false; } public static bool EsPosibleMover(int x, int y, int xFin, int yFin) { // Veo si choca con algún elemento del fondo for (int i = 0; i < elementosFondo; i++) if ((fondos[i].x + fondos[i].ancho > x) && (fondos[i].x < xFin) && (fondos[i].y + fondos[i].alto > y) && (fondos[i].y < yFin)) return false; // Si no ha chocado con ninguno, es posible moverse a esa posición return true; } public static void Main() { InicializarJuego(); while (!juegoTerminado) { InicializarPartida(); MostrarPresentacion(); // ------ Bucle de juego ------ while (!partidaTerminada) { Dibujar(); ComprobarTeclas(); MoverElementos(); ComprobarColisiones(); PausaFotograma(); } // Fin del bucle de juego } // Fin de partida } // Fin de Main }
Ejercicio propuesto (1): Mejora esta versión del juego, para que el fondo no esté formado por un único tipo de casilla, sino por dos o más.
Ejercicio propuesto (2): Haz que los premios que se pueden recoger no aparezcan en posiciones al azar, sino que sean parte también del mapa del nivel, además de los ladrillos del fondo. (Si no te sale, no te preocupes, pronto veremos cómo hacerlo)