24. Un "bucle de juego" clásico: aproximación a Columnas.(*)
Vamos a imitar un juego clásico, muy entretenido y razonablemente fácil. Lo llamaremos Columnas, para evitar problemas de Copyright. La apariencia del original es ésta:
Para conseguirlo, en vez de crear un "main" cada vez más grande, como pasaba con el "matamarcianos", lo haremos más modular desde un principio. Seguiremos la apariencia de un "bucle de juego" clásico, que podría ser algo así:
repetir
comprobar teclas
calcular siguiente posición de elementos
comprobar colisiones entre elementos
dibujar elementos en pantalla
pausa hasta tiempo final de fotograma
hasta final de partida
Si nos ayudamos de funciones auxiliares, esto se convertiría simplemente en algo como:
void buclePrincipal() { partidaTerminada = FALSE; do { comprobarTeclas(); moverElementos(); comprobarColisiones(); dibujarElementos(); pausaFotograma(); } while (partidaTerminada != TRUE); }
Donde, a su vez "comprobarTeclas" sería algo tan sencillo como:
void comprobarTeclas() { if (keypressed()) { tecla = readkey() >> 8; if ( tecla == KEY_ESC ) partidaTerminada = TRUE; if ( tecla == KEY_RIGHT ) intentarMoverDerecha(); if ( tecla == KEY_LEFT ) intentarMoverIzquierda(); if ( tecla == KEY_SPACE ) rotarColores(); clear_keybuf(); } }
Mientras que "moverElementos" no haría casi nada en un juego como éste, al no existir enemigos que se muevan solos. Se limitaría a encargarse del movimiento "automático" de nuestro personaje, que baja automáticamente poco a poco, pero eso lo haremos en la próxima entrega; por ahora estará vacío:
void moverElementos() { // Ningun elemento extra que mover por ahora }
De igual modo, "comprobarColisiones" tampoco haría nada en este juego, al no existir "disparos" ni nada parecido. En la siguiente entrega sí deberemos ver si nuestra ficha realmente se puede mover a la derecha o existe algún obstáculo, pero eso quizá sea más razonable hacerlo desde el "intentarMoverDerecha", cuando el jugador indica que se quiere mover en cierta dirección.
void comprobarColisiones() { // Por ahora, no hay colisiones que comprobar }
"dibujarElementos" se limitará a dibujar el fondo (en la siguiente entrega) y la pieza (ya en esta entrega) en la pantalla oculta, y luego volcar esa pantalla oculta a la visible para evitar parpadeos (la técnica del "doble buffer" que ya habíamos mencionado).
void dibujarElementos() { // Borro la pantalla y dibujo la nave clear_bitmap(pantallaOculta); // Dibujo el "fondo", con los trozos de piezas anteriores // (aun no) // Dibujo la "pieza" draw_sprite(pantallaOculta, pieza, x, y); // Sincronizo con el barrido para evitar parpadeos // y vuelco la pantalla oculta vsync(); blit(pantallaOculta, screen, 0, 0, 0, 0, ANCHOPANTALLA, ALTOPANTALLA); }
Finalmente, "pausaFotograma" esperará el tiempo que sea neceasrio hasta el momento de dibujar el siguiente fotograma en pantalla. Si queremos 25 fotogramas por segundo, como en la televisión, para lograr una sensación de movimiento continuo, deberíamos hacer una pausa de 40 milisegundos entre un fotograma y otro (1000 / 25 = 40). Estamos suponiendo que el tiempo de cálculo y dibujado es despreciable, pero esto es verdad en un juego tan sencillo como éste. En un juego "más real", calcularíamos cuanto se tarda en calcular posiciones, colisiones, dibujar elementos, etc., y lo restaríamos. Por ejemplo, si se tarda 10 milisegundos en esas tareas, la espera que realmente haríamos sería 40-10 = 30 milésimas de segundo. Esas mejoras las dejamos para más adelante. De momento, la pausa de 40 ms la haremos así:
void pausaFotograma() { // Para 25 fps: 1000/25 = 40 milisegundos de pausa rest(40); }
De momento usaremos esta imagen: columnas_pieza0.bmp
Y el fuente completo sería por ahora poco más que juntar esas rutinas, una función de inicializar que entrase a modo gráfico y un "main" que cargase e bucle principal del juego, de modo que podría quedar así
/*------------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* ipj24c.c */ /* */ /* Ejemplo: */ /* Primer acercamiento a */ /* Columnas */ /* */ /* Comprobado con: */ /* - DevC++ 4.9.9.2(gcc 3.4.2) */ /* y Allegro 4.03 - WinXP */ /*------------------------------*/ #include <allegro.h> /* -------------- Constantes globales ------------- */ #define ANCHOPANTALLA 640 #define ALTOPANTALLA 480 #define MARGENSUP 20 #define MARGENDCHO 25 #define MARGENIZQDO 25 #define MARGENINF 30 /* -------------- Variables globales -------------- */ PALETTE pal; BITMAP *pieza; BITMAP *pantallaOculta; int partidaTerminada; int x = 300; int y = 200; int incrX = 4; int incrY = 4; int tecla; // Prototipos de las funciones que usaremos void comprobarTeclas(); void moverElementos(); void comprobarColisiones(); void dibujarElementos(); void pausaFotograma(); void intentarMoverDerecha(); void intentarMoverIzquierda(); void rotarColores(); // --- 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 (keypressed()) { tecla = readkey() >> 8; if ( tecla == KEY_ESC ) partidaTerminada = TRUE; if ( tecla == KEY_RIGHT ) intentarMoverDerecha(); if ( tecla == KEY_LEFT ) intentarMoverIzquierda(); if ( tecla == KEY_SPACE ) rotarColores(); clear_keybuf(); } } // -- Intenta mover la "pieza" hacia la derecha void intentarMoverDerecha() { x += incrX; } // -- Intenta mover la "pieza" hacia la izquierda void intentarMoverIzquierda() { x -= incrX; } // -- Rotar los colores de la "pieza" void rotarColores() { // (Todavia no) } // -- Mover otros elementos del juego void moverElementos() { // Ningun elemento extra que mover por ahora } // -- 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() { // Borro la pantalla y dibujo la nave clear_bitmap(pantallaOculta); // Dibujo el "fondo", con los trozos de piezas anteriores // (aun no) // Dibujo la "pieza" draw_sprite(pantallaOculta, pieza, x, y); // Sincronizo con el barrido para evitar parpadeos // y vuelco la pantalla oculta vsync(); blit(pantallaOculta, screen, 0, 0, 0, 0, ANCHOPANTALLA, ALTOPANTALLA); } // -- Pausa hasta el siguiente fotograma void pausaFotograma() { // Para 25 fps: 1000/25 = 40 milisegundos de pausa rest(40); } /* -------------- Rutina de inicializacin -------- */ int inicializa() { int i,j; allegro_init(); // Inicializamos Allegro install_keyboard(); install_timer(); // Intentamos entrar a modo grafico set_color_depth(32); 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 pieza = load_bmp("columnas_pieza0.bmp", pal); if (!pieza) { set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); allegro_message("No se ha podido abrir la imagen\n"); return 1; } set_palette(pal); // Pantalla oculta para evitar parpadeos // (doble buffer) pantallaOculta = create_bitmap(ANCHOPANTALLA, ALTOPANTALLA); // 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); buclePrincipal(); destroy_bitmap(pieza); destroy_bitmap(pantallaOculta); rest(2000); return 0; } /* Termino con la "macro" que me pide Allegro */ END_OF_MAIN();