21. Moviendo varios marcianos a la vez. Octavo juego(aproximación "e"): Marciano 5. (*)
Si se ha entendido la aproximación anterior al juego, la versión "completa" no aporta muchas dificultades. Apenas dos importantes:
- Ahora no tendremos un único enemigo, sino varios. Además, no siempre habrá que redibujar todos los enemigos, sino que algunos todavía estarán "vivos", mientras que a otros ya los habremos "matado". El juego terminará cuando matemos a todos los enemigos (en un juego "real" se debería pasar a un nivel de dificultad mayor) o cuando se nos mate a nosotros (en la práctica debería ser cuando agotáramos un cierto número de "vidas", habitualmente 3).
- Además, para que el juego tenga más aliciente, sería deseable que los enemigos también sean capaces de dispararnos a nosotros.
Por eso, haremos nuevamente varias aproximaciones sucesivas hasta tener un juego "razonablemente jugable".
En este primer acercamiento, simplemente mostrará varios enemigos moviéndose a la vez.
Las diferencias entre tener un único enemigo o varios no son muchas, pero hay que tener varias cosas en cuenta:
- Habrá que mover varios enemigos a la vez. Esto se puede hacer guardando la información de todos ellos dentro de un "array", tanto en nuestro caso, en el que todos los enemigos se moverán a la vez, como en el caso de que tuvieran movimientos relativamente independientes. Para cada marciano nos podría interesar (según el caso) guardar información como: posición actual, trayectoria actual, apariencia actual, etc.
- Podemos ir "matando marcianos", de modo que no estarán siempre todos visibles. Podríamos eliminar realmente a los marcianos (pasaríamos de tener "n" a tener "n-1") o simplemente marcarlos como "no activos" (siempre tendríamos "n", pero algunos estarían activos y otros no).
- El espacio que recorre cada marciano no es siempre el mismo: el "bloque" formado por todos los marcianos se mueve todo lo que pueda a la derecha y todo lo que pueda a la izquierda. A medida que desaparezcan los marcianos de los extremos, los centrales podrán avanzar más hacia cada lado.
Vamos poco a poco:
El hecho de dibujar varios marcianos se podría resolver simplemente con algo como
for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) { draw_sprite(screen, marciano1, xMarciano + i*DISTANCIAXMARCIANOS, yMarciano + j*DISTANCIAYMARCIANOS); ...
Donde las constantes son las que indican el número de marcianos en vertical y en horizontal, así como la distancia entre ellos:
#define FILASMARCIANOS 5 #define COLUMNASMARCIANOS 11 #define DISTANCIAXMARCIANOS 20 #define DISTANCIAYMARCIANOS 12
Además, los marcianos deberán avanzar menos hacia la derecha que si estuvieran solos, por ejemplo con:
if ((xMarciano > ANCHOPANTALLA-16-MARGENDCHO-COLUMNASMARCIANOS*DISTANCIAXMARCIANOS) || (xMarciano < MARGENIZQDO)) { desplMarciano = -desplMarciano; // Dirección contraria yMarciano += INCREMY; // Y bajo una línea }
Pero este planteamiento no hace que sea fácil calcular cuando se ha matado a un marciano concreto, ni cuando el bloque de marcianos debe avanzar más hacia la derecha o hacia la izquierda. Una opción más razonable sería crear una "estructura" (nos vamos acercando a los objetos) que representa a cada marciano, con datos como su posición y si está activo:
// Datos de cada marciano typedef struct { int posX; int posY; int activo; } marciano; // El array que controla a todos los marcianos marciano marcianos[COLUMNASMARCIANOS][FILASMARCIANOS];
Y apenas cambia un poco la forma de dibujar a los marcianos, que ahora se hará dentro de un doble bucle "for", para recorrer todos, y comprobando si están activos:
// Dibujo un marciano u otro, alternando if (puedeMoverMarciano) { for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) if (marcianos[i][j].activo) { if (fotograma==1) { draw_sprite(screen, marciano1, marcianos[i][j].posX, marcianos[i][j].posY); fotograma = 2; } else { draw_sprite(screen, marciano2, marcianos[i][j].posX, marcianos[i][j].posY); fotograma = 1; } } }
o la de comprobar si un disparo ha acertado, con los mismos cambios:
for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) if (marcianos[i][j].activo) { if ((xDisparo >= marcianos[i][j].posX) && (xDisparo <= marcianos[i][j].posX + 8) && (yDisparo >= marcianos[i][j].posY) && (yDisparo <= marcianos[i][j].posY + 8)) { textprintf(screen, font, xDisparo, yDisparo, palette_color[11], "Boom!"); marcianos[i][j].activo = 0; disparoActivo = 0; puntos += 10; numMarcianos --; if (numMarcianos < 1) { textprintf(screen, font, 10, 10, palette_color[11], "Partida ganada!"); salir = 1; // Y se acabó } } }
o hasta donde deben moverse los marcianos, porque habrá que calcular la "primera x" (la del marciano más a la izquierda) y la "última x" (la del marciano más a la derecha):
// Calculo nueva posición for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) { marcianos[i][j].posX += desplMarciano; // Y veo si es el más a la derecha o a la izqda if ((marcianos[i][j].activo) && (marcianos[i][j].posX > ultimaX)) ultimaX = marcianos[i][j].posX; if ((marcianos[i][j].activo) && (marcianos[i][j].posX < primeraX)) primeraX = marcianos[i][j].posX; } // Compruebo si debe bajar if ((ultimaX > ANCHOPANTALLA-16-MARGENDCHO) || (primeraX < MARGENIZQDO)) { desplMarciano = -desplMarciano; // Dirección contraria for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) { marcianos[i][j].posY += INCREMY; // Y bajo una línea } }
De paso, he añadido un par de refinamientos: una puntuación
(10 puntos por cada marciano eliminado) y el final de la partida si los
marcianos nos invaden (si alcanzan la parte inferior de la pantalla).
El problema es que dibujamos unas 57 figuras en pantalla cada vez, lo que provoca un serio problema de parpadeo, que corregiremos en el próximo apartado. Aun así, para quien quiera ver cómo sería el fuente completo de esta versión preliminar, aquí está:
/*------------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* ipj21c.c */ /* */ /* Ejemplo: */ /* Varios marcianos y una */ /* nave movindose de */ /* forma independiente; la */ /* nave puede disparar y */ /* "matar" a los marcianos */ /* Problema: parpadeos */ /* */ /* Comprobado con: */ /* - Djgpp 2.03 (gcc 3.2) */ /* y Allegro 4.02 - MsDos */ /* - MinGW 2.0.0-3 (gcc 3.2) */ /* y Allegro 4.02 - WinXP */ /* - DevC++ 4.9.9.2(gcc 3.4.2) */ /* y Allegro 4.03 - WinXP */ /*------------------------------*/ #include <allegro.h> /* -------------- Constantes globales ------------- */ #define ANCHOPANTALLA 320 #define ALTOPANTALLA 200 // Los dos siguientes valores son los retardos // hasta mover la nave y el marciano, pero ambos // en centsimas de segundo #define RETARDOCN 10 #define RETARDOCM 35 // Y tambin el del disparo #define RETARDOCD 15 #define MARGENSUP 20 #define MARGENDCHO 25 #define MARGENIZQDO 25 #define MARGENINF 30 #define INCREMX 3 #define INCREMY 7 #define FILASMARCIANOS 5 #define COLUMNASMARCIANOS 11 #define DISTANCIAXMARCIANOS 20 #define DISTANCIAYMARCIANOS 12 // Datos de cada marciano typedef struct { int posX; int posY; int activo; } marciano; /* -------------- Variables globales -------------- */ PALETTE pal; BITMAP *imagen; BITMAP *nave; BITMAP *marciano1; BITMAP *marciano2; BITMAP *disparo; // El array que controla a todos los marcianos marciano marcianos[COLUMNASMARCIANOS][FILASMARCIANOS]; int fotograma = 1; int xNave = ANCHOPANTALLA / 2; int yNave = ALTOPANTALLA-16-MARGENINF; int xDisparo = 0; int yDisparo = 0; int numMarcianos = FILASMARCIANOS*COLUMNASMARCIANOS; int desplMarciano = INCREMX; int desplNave = INCREMX; int puedeMoverMarciano = 1; int puedeMoverNave = 1; int puedeMoverDisparo = 0; int disparoActivo = 0; int puntos = 0; int tecla; int salir = 0; /* -------------- Rutinas de temporizacin -------- */ // Contadores para temporizacin volatile int contadorN = 0; volatile int contadorM = 0; volatile int contadorD = 0; // La rutina que lo aumenta cada cierto tiempo void aumentaContadores(void) { contadorN++; contadorM++; contadorD++; } END_OF_FUNCTION(aumentaContadores); /* -------------- Rutina de inicializacin -------- */ int inicializa() { int i,j; allegro_init(); // Inicializamos Allegro install_keyboard(); install_timer(); // Intentamos entrar a modo grafico if (set_gfx_mode(GFX_SAFE, ANCHOPANTALLA, ALTOPANTALLA, 0, 0) != 0) { set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); allegro_message( "Incapaz de entrar a modo grafico\n%s\n", allegro_error); return 1; } // e intentamos abrir imgenes imagen = load_pcx("spr_inv.pcx", pal); if (!imagen) { set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); allegro_message("No se ha podido abrir la imagen\n"); return 1; } set_palette(pal); // Ahora reservo espacio para los otros sprites nave = create_bitmap(16, 16); marciano1 = create_bitmap(16, 16); marciano2 = create_bitmap(16, 16); disparo = create_bitmap(8, 8); // Y los extraigo de la imagen "grande" blit(imagen, nave // bitmaps de origen y destino , 32, 32 // coordenadas de origen , 0, 0 // posicin de destino , 16, 16); // anchura y altura blit(imagen, marciano1, 0, 32, 0, 0, 16, 16); blit(imagen, marciano2, 16, 32, 0, 0, 16, 16); blit(imagen, disparo, 72, 32, 0, 0, 8, 8); // Valores iniciales de los marcianos for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) { marcianos[i][j].posX = MARGENIZQDO + i*DISTANCIAXMARCIANOS; marcianos[i][j].posY = MARGENSUP + j*DISTANCIAYMARCIANOS; marcianos[i][j].activo = 1; } // Rutinas de temporizacin // Bloqueamos las variables y la funcin del temporizador LOCK_VARIABLE( contadorN ); LOCK_VARIABLE( contadorM ); LOCK_FUNCTION( aumentaContadores ); // Y ponemos el temporizador en marcha: cada 10 milisegundos // (cada centsima de segundo) install_int(aumentaContadores, 10); // Y termino indicando que no ha habido errores return 0; } /* -------------- Rutina de mover marciano -------- */ int mueveMarciano() { int primeraX = ANCHOPANTALLA; int ultimaX = 0; int i,j; if (fotograma==1) fotograma = 2; else fotograma = 1; // Calculo nueva posicin for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) { marcianos[i][j].posX += desplMarciano; // Y veo si es el ms a la derecha o a la izqda if ((marcianos[i][j].activo) && (marcianos[i][j].posX > ultimaX)) ultimaX = marcianos[i][j].posX; if ((marcianos[i][j].activo) && (marcianos[i][j].posX < primeraX)) primeraX = marcianos[i][j].posX; } // Compruebo si debe bajar if ((ultimaX > ANCHOPANTALLA-16-MARGENDCHO) || (primeraX < MARGENIZQDO)) { desplMarciano = -desplMarciano; // Direccin contraria for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) { marcianos[i][j].posY += INCREMY; // Y bajo una lnea } } // O si se ha "invadido", partida acabada) for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) { if ((marcianos[i][j].activo) && (marcianos[i][j].posY > ALTOPANTALLA-20-MARGENINF)) { textprintf(screen, font, 10, 10, palette_color[11], "Invadido!"); salir = 1; // Y se acab } } } /* -------------- Rutina de mover disparo -------- */ int mueveDisparo() { int i,j; // Calculo nueva posicion yDisparo -= 4; // Compruebo si ha chocado for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) if (marcianos[i][j].activo) { if ((xDisparo >= marcianos[i][j].posX) && (xDisparo <= marcianos[i][j].posX + 8) && (yDisparo >= marcianos[i][j].posY) && (yDisparo <= marcianos[i][j].posY + 8)) { textprintf(screen, font, xDisparo, yDisparo, palette_color[11], "Boom!"); marcianos[i][j].activo = 0; disparoActivo = 0; puntos += 10; numMarcianos --; if (numMarcianos < 1) { textprintf(screen, font, 10, 10, palette_color[11], "Partida ganada!"); salir = 1; // Y se acab } } } // O si ha llegado arriba if (yDisparo < MARGENSUP-8) { xDisparo = 0; yDisparo = 0; puedeMoverDisparo = 0; disparoActivo = 0; } } /* ------------------------------------------------ */ /* */ /* -------------- Cuerpo del programa ------------- */ int main() { int i,j; // Intento inicializar if (inicializa() != 0) exit(1); do { // Parte que se repite hasta pulsar tecla // Slo se pueden mover tras un cierto tiempo if (contadorM >= RETARDOCM) { puedeMoverMarciano = 1; contadorM = 0; } if (contadorN >= RETARDOCN) { puedeMoverNave = 1; contadorN = 0; } if ((contadorD >= RETARDOCD) && (disparoActivo)) { puedeMoverDisparo = 1; contadorD = 0; } // Compruebo teclas pulsadas para salir // o mover la nave if (keypressed()) { tecla = readkey() >> 8; if ( tecla == KEY_ESC) salir = 1; if (puedeMoverNave) { if (( tecla == KEY_RIGHT) && (xNave < ANCHOPANTALLA-16-MARGENDCHO)) { xNave += INCREMX; puedeMoverNave = 0; } if (( tecla == KEY_LEFT) && (xNave > MARGENIZQDO+16)) { xNave -= INCREMX; puedeMoverNave = 0; } if (( tecla == KEY_SPACE) && (!disparoActivo)) { disparoActivo = 1; puedeMoverDisparo = 1; contadorD = 0; xDisparo = xNave; yDisparo = yNave-2; } clear_keybuf(); } } if (puedeMoverMarciano || puedeMoverNave || puedeMoverDisparo) { // Sincronizo con el barrido para evitar // parpadeos, borro la pantalla y dibujo la nave vsync(); clear_bitmap(screen); draw_sprite(screen, nave, xNave, yNave); // Dibujo un marciano u otro, alternando if (puedeMoverMarciano) { for (i=0; i<COLUMNASMARCIANOS; i++) for (j=0; j<FILASMARCIANOS; j++) if (marcianos[i][j].activo) { if (fotograma==1) draw_sprite(screen, marciano1, marcianos[i][j].posX, marcianos[i][j].posY); else draw_sprite(screen, marciano2, marcianos[i][j].posX, marcianos[i][j].posY); } } // Y calculo su nueva posicin si corresponde if (puedeMoverMarciano) { mueveMarciano(); puedeMoverMarciano = 0; } // Y anlogo para el disparo if (puedeMoverDisparo) { mueveDisparo(); puedeMoverDisparo = 0; } if (disparoActivo) draw_sprite(screen, disparo, xDisparo, yDisparo); textprintf(screen, font, 100, 1, palette_color[11], "Puntos: %d",puntos); } } while (!salir); textprintf(screen, font, 1, 1, palette_color[11], "Partida terminada."); destroy_bitmap(imagen); destroy_bitmap(nave); destroy_bitmap(marciano1); destroy_bitmap(marciano2); destroy_bitmap(disparo); readkey(); rest(2000); return 0; } /* Termino con la "macro" que me pide Allegro */ END_OF_MAIN();