30. Un primer juego completo en modo gráfico.
Tenemos ya un esqueleto de juego que hace un poco de todo, pero que todavía no es muy jugable: es "demasiado difícil" y "demasiado repetitivo", además de que tiene otras muchas carencias que de momento no nos van a preocupar:
- Sólo hay una "pantalla" que recorrer.
- Sólo hay un tipo de enemigos, un tipo de obstáculos, un tipo de premios...
- No hay ningún movimiento "avanzado" (no podemos saltar, ni caer de las plataformas).
- No hay animaciones en los movimientos.
- No tiene sonido.
- No se puede jugar con joystick ni gamepad.
- ...
Vamos a ignorar esas carencias por ahora, porque las iremos solucionando más adelante, y a juntar todo lo que hemos visto, para crear un juego sencillo pero "real", con un nivel de dificultad creciente. El funcionamiento será el siguiente:
- Al comenzar el juego, se nos mostrará una pantalla de presentación, que nos permitirá ver una pequeña ayuda, una pantalla de créditos o comenzar una nueva partida.
- En la partida, nuestro personaje aparecerá en una primera pantalla, en la que habrá paredes que no podremos atravesar, un premio que podremos recoger, pero no habrá ningún obstáculo ni ningún enemigo.
- Cuando recojamos ese premio, pasaremos a un segundo nivel, que tendrá las mismas paredes, pero dos premios en (nuevas) posiciones al azar, un obstáculo fijo y un enemigo móvil.
- Cuando recojamos esos dos premios, pasaremos a un tercer nivel, también con las mismas paredes, pero tres premios, dos obstáculos fijo y dos enemigos móviles.
- En general, cuando recojamos todos los premios de un nivel, pasaremos a un siguiente nivel, que tendrá un premio más que el anterior (hasta un máximo de 20), un enemigo móvil más que el anterior (hasta un máximo de 15) y un obstáculo fijo más que el anterior (hasta un máximo de 20).
- Obtendremos 10 puntos por cada premio recogido.
- Si tocamos un obstáculo o un enemigo, perderemos una vida. Cuando perdamos las tres vidas iniciales, terminará la partida y volveremos a la pantalla de presentación.
El fuente completo podría ser algo como:
// Primer mini-juego en modo gráfico: Freddy One using System; using System.Threading; // Para Thread.Sleep public class FreddyOne { 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 = 24; static byte altoFondo = 17; static short margenXFondo = 10; static byte margenYFondo = 30; static byte anchoCasillaFondo = 32; static byte altoCasillaFondo = 32; static Imagen imgPared; static public byte[,] fondo = { {1,1,1,1,1,1,1,1,1,1,1,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,1,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,1,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,0,0,0,0,1}, {1,0,0,0,0,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,0,0,0,0,1}, {1,0,0,0,0,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,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,1}, {1,0,0,0,0,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,0,0,0,0,1}, {1,0,0,0,0,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,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,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; static Imagen logoPresentacion; // Cantidad de elementos que se ven en pantalla en el nivel actual static int premiosVisibles; static int enemigosVisibles; static int obstaculosVisibles; // Cantidad de premios que quedan para cambiar de nivel static int premiosRestantes; 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 = 20; numEnemigos = 12; 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"); logoPresentacion = new Imagen("freddyRotulo.png"); fondoAyuda = new Imagen("ayuda.jpg"); fondoCreditos = new Imagen("creditos.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 = false; 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 = false; 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 do { premios[i].x = generador.Next(50, 700); premios[i].y = generador.Next(30, 550); premios[i].visible = false; premios[i].ancho = 34; premios[i].alto = 18; } while (! EsPosibleMover(premios[i].x, premios[i].y, premios[i].x+premios[i].ancho, premios[i].y+premios[i].alto)); premiosVisibles = 1; premios[0].visible = true; enemigosVisibles = 0; obstaculosVisibles = 0; premiosRestantes = 1; } 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); logoPresentacion.DibujarOculta(60, 100); Hardware.EscribirTextoOculta("Escoja una opción:", 310, 300, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("J.- Jugar una partida", 250, 390, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("A.- Ayuda", 250, 430, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("C.- Créditos", 250, 470, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("S.- Salir", 250, 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", "Cuando recojas todos los premios", " avanzarás de nivel.", "Comienzas con 3 vidas.", "Si tocas un obstáculo o un enemigo,.", " perderás una de ellas." }; // ---- Pantalla de presentación -- Hardware.BorrarPantallaOculta(0, 0, 0); // Fondo de la presentación fondoAyuda.DibujarOculta(0, 0); // Marcador Hardware.EscribirTextoOculta("Freddy One - Ayuda", 300, 100, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); // Textos repetitivos short posicYtexto = 250; 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); // También muestro imágenes aclaradoras premios[0].imagen.DibujarOculta(350, 252); obstaculos[0].imagen.DibujarOculta(510, 280); enemigos[0].imagen.DibujarOculta(560, 260); 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("Freddy One - Créditos", 300, 100, // Coordenadas 255, 255, 255, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Por Nacho Cabanes, 2011", 150, 200, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("(Curso 2011-2012, primer juego de ejemplo)", 150, 250, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Imágenes \"in-game\", de Electro Freddy, (c) SoftSpot 1984", 150, 300, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Resto de imágenes por Nacho Cabanes", 150, 350, // Coordenadas 200, 200, 200, // Colores tipoDeLetra); Hardware.EscribirTextoOculta("Se puede distribuir libremente", 150, 400, // 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 if (obstaculos[i].visible) obstaculos[i].imagen.DibujarOculta(obstaculos[i].x, obstaculos[i].y); for (int i = 0; i < numEnemigos; i++) // Enemigos if (enemigos[i].visible) 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; premiosRestantes--; if (premiosRestantes == 0) AvanzarNivel(); } } 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 AvanzarNivel() { // Borro la pantalla y aviso del nuevo nivel Hardware.Pausa(200); Hardware.BorrarPantallaOculta(); Hardware.EscribirTextoOculta("Nivel: "+Convert.ToString(premiosVisibles+1), 350, 300, /* Coordenadas */ 255, 255, 255, /* Colores */ tipoDeLetra); Hardware.VisualizarOculta(); Hardware.Pausa(1000); // Activo un enemigo mas if (enemigosVisibles < numEnemigos) { enemigosVisibles++; for (int i = 0; i < enemigosVisibles; i++) enemigos[i].visible = true; } // Y un obstaculo mas if (obstaculosVisibles < numObstaculos) { obstaculosVisibles++; for (int i = 0; i < obstaculosVisibles; i++) obstaculos[i].visible = true; } // Y un premio mas, y los recoloco if (premiosVisibles < numPremios) { premiosVisibles++; for (int i = 0; i < premiosVisibles; i++) do { 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; } while (!EsPosibleMover(premios[i].x, premios[i].y, premios[i].x + premios[i].ancho, premios[i].y + premios[i].alto)); } premiosRestantes = premiosVisibles; personaje.x = personaje.xInicial; personaje.y = personaje.yInicial; } 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 }