28. La aproximación orientada a objetos (1). Toma de contacto con un primer "arcade": MiniMiner (1) (*)

Vamos a hacer buena parte de la estructura de un juego "arcade". Imitaremos un clásico de principio de los 80, en el que el minero Willy tenía que ir recogiendo llaves hasta abrir la puerta que le permitía pasar al siguiente nivel:

En este primer acercamiento partiremos del esqueleto de juego que desarrollamos en el tema 24, con un "bucle de juego" que se encarga de repetir las tareas habituales en un juego de este tipo. Ampliaremos un poco el esqueleto, de forma que muestre un personaje que podremos mover a derecha e izquierda, un enemigo que se moverá por sí mismo (también a derecha e izquierda) y un fragmento de suelo. Será algo tan sencillo como esto:

Añadiremos también una "pantalla de presentación" básica, que mostrará la imagen original del juego hasta que pulsemos una tecla. Pero apenas con estos pocos cambios, ya empezaremos a notar cosas "mejorables" en nuestra estructura.

Los cambios van a ser:

  • Este juego cargará cuatro imágenes, con lo que la función "inicializa" será mucho más repetitiva que antes.
  • La función "dibujarElementos" dibujará también el fondo (que es algo que dentro de poco ya no será tan trivial, así que crearemos una función específica) y el enemigo, lo que no supone grandes cambios.
  • Ahora hay un enemigo que mover, así que "moverElementos" llamará ahora a un "moverEnemigo", que cambiará su X de modo que se mueva de un lado a otro de la pantalla.
  • El enemigo, como es de esperar, tendrá su propia X y su propia Y, por lo que necesitamos otras dos variables xEnemigo e "yEnemigo" (realmente, también un incremento de X, que equivaldrá a su velocidad). Esto empieza a hacer el fuente menos legible. Una alternativa ligeramente más legible sería que el enemigo fuera un "struct" que contuviera su imagen, su "x" y su "y", etc. Eso se parece más a lo que haremos, pero realmente no usaremos "structs" sino "clases", ya veremos por qué.
  • Además, añadiremos una función "lanzarPresentación", que será la que muestre la imagen que por ahora va a ser nuestra presentación rudimentaria.

El fuente podría quedar así:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    miner01.cpp               */
/*                              */
/*  Ejemplo:                    */
/*    Primer acercamiento a     */
/*    "MiniMiner"               */
/*                              */
/*  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
 
/* -------------- Variables globales -------------- */
PALETTE pal;
BITMAP *personaje;
BITMAP *enemigo;
BITMAP *presentacion;
BITMAP *fragmentoSuelo;
BITMAP *pantallaOculta;
 
int partidaTerminada;
int x = 200;
int y = 200;
int incrX = 4;
int incrY = 4;
int tecla;
int xEnemigo = 500;
int incrXEnemigo = 2;
int yEnemigo = 200;
int ySuelo = 232;
 
 
// Prototipos de las funciones que usaremos
void comprobarTeclas();
void moverElementos();
void comprobarColisiones();
void dibujarElementos();
void pausaFotograma();
void moverDerecha();
void moverIzquierda();
void lanzarPresentacion();
void moverEnemigo();
void dibujarFondo();
 
 
 
// --- 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 )
       moverDerecha();   
    if ( tecla == KEY_LEFT )
       moverIzquierda();   
     clear_keybuf();
   }
}
 
 
// -- Intenta mover el personaje hacia la derecha
void moverDerecha() {
  x += incrX;
}
 
 
// -- Intenta mover el personaje hacia la izquierda
void moverIzquierda() {
  x -= incrX;
}
 
 
 
// -- Mover otros elementos del juego 
void moverElementos() {
  moverEnemigo();
}
 
 
// -- 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
  clear_bitmap(pantallaOculta);
 
  // Dibujo el "fondo", con los trozos de piezas anteriores
  dibujarFondo();
 
  // Dibujo el personaje y el enemigo
  draw_sprite(pantallaOculta, personaje, x, y);
  draw_sprite(pantallaOculta, enemigo, xEnemigo, yEnemigo);
 
  // 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);
}
 
 
// -- Funciones que no son de la logica juego, sino de 
// funcionamiento interno de otros componentes
 
// -- Pantalla de presentacion
void lanzarPresentacion() {
  draw_sprite(pantallaOculta, presentacion, 0, 0);
  blit(pantallaOculta, screen, 0, 0, 0, 0,
    ANCHOPANTALLA, ALTOPANTALLA);
  readkey();
}
 
// -- Mover el enemigo a su siguiente posicion
void moverEnemigo() {
  xEnemigo += incrXEnemigo;
  // Da la vuelta si llega a un extremo
  if ((xEnemigo > ANCHOPANTALLA-30) || (xEnemigo < 30))
    incrXEnemigo = -incrXEnemigo;
}
 
// -- Dibuja el fondo (por ahora, apenas un fragmento de suelo)
void dibujarFondo() {
  int i;
  int anchoImagen = 16;
  for (i=0; i<15; i++)
      draw_sprite(pantallaOculta, fragmentoSuelo, 
          i*anchoImagen, ySuelo);
}
 
/* -------------- 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
    personaje = load_bmp("personaje.bmp", pal);
    if (!personaje) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("No se ha podido abrir la imagen\n");
        return 1;
    }
 
    enemigo = load_bmp("enemigo.bmp", pal);
    if (!enemigo) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("No se ha podido abrir la imagen\n");
        return 1;
    }
 
    fragmentoSuelo = load_bmp("suelo.bmp", pal);
    if (!fragmentoSuelo) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("No se ha podido abrir la imagen\n");
        return 1;
    }
 
    presentacion = load_bmp("miner.bmp", pal);
    if (!presentacion) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("No se ha podido abrir la imagen\n");
        return 1;
    }
 
    // 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);
 
    lanzarPresentacion();
    buclePrincipal();
 
    destroy_bitmap(personaje);
    destroy_bitmap(enemigo);
    destroy_bitmap(fragmentoSuelo);
    destroy_bitmap(presentacion);
    destroy_bitmap(pantallaOculta);
 
    rest(1000);
    return 0;
 
}
 
            /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();
 

A pesar de que el juego todavía no hace prácticamente nada, ya ocupa 254 líneas, ya empieza a tener fragmentos repetitivos, y además está lleno por todos lados de órdenes que son propias de Allegro, por lo que migrar el programa para que use otra biblioteca gráfica sería costoso... en la próxima entrega empezaremos a solucionar estos problemas...