Versión: 0.47, de 10-Jul-2011


29. La aproximación orientada a objetos (1). MiniMiner 2: Aislando del hardware. (*)

Queremos que nuestro juego siga los principios de la "Programación orientada a objetos": en vez de tratarse de un único "macroprograma", lo plantearemos como una serie de objetos que se pasan mensajes unos a otros. Esto tendrá una serie de ventajas, entre las que podemos destacar:

La primera mejora que vamos a hacer va a estar relacionada con la última ventaja que hemos mencionado: aislaremos bastante el juego de la biblioteca Allegro, ayudándonos de dos primeras clases:

Necesitaremos un fichero de cabecera (.h) y uno de desarrollo (.cpp) para cada clase de objetos que queramos definir. Por tanto, en este momento tendremos 5 ficheros: los dos de la clase Hardware, los dos de la clase ElementoGraf, y el cuerpo del juego.

El fichero de cabecera de Hardware podría ser así:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*   Hardware.h:                */
/*     Clase "Hardware"         */
/*       para Allegro           */
/*     Fichero de cabecera      */
/*                              */
/*     Parte de "MiniMiner"     */
/*                              */
/*  Ejemplo:                    */
/*    Primer acercamiento a     */
/*    "MiniMiner"               */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
#ifndef Hardware_h
#define Hardware_h
 
#include <vector>
#include <string>
using namespace std;
 
#include <allegro.h>
 
 
#include "ElementoGraf.h"
 
#define TECLA_ESC KEY_ESC
#define TECLA_DCHA KEY_RIGHT
#define TECLA_ARRB KEY_UP
#define TECLA_ABAJ KEY_DOWN
#define TECLA_IZQD KEY_LEFT
#define TECLA_ESPACIO KEY_SPACE
#define TECLA_A KEY_a
#define TECLA_B KEY_b
#define TECLA_C KEY_c
#define TECLA_D KEY_d
#define TECLA_E KEY_e
#define TECLA_F KEY_f
#define TECLA_G KEY_g
#define TECLA_H KEY_h
#define TECLA_I KEY_i
#define TECLA_J KEY_j
#define TECLA_K KEY_k
#define TECLA_L KEY_l
#define TECLA_M KEY_m
#define TECLA_N KEY_n
#define TECLA_O KEY_o
#define TECLA_P KEY_p
#define TECLA_Q KEY_q
#define TECLA_R KEY_r
#define TECLA_S KEY_s
#define TECLA_T KEY_t
#define TECLA_U KEY_u
#define TECLA_V KEY_v
#define TECLA_W KEY_w
#define TECLA_X KEY_x
#define TECLA_Y KEY_y
#define TECLA_Z KEY_z
 
#define TECLA_0 KEY_0
#define TECLA_1 KEY_1
#define TECLA_2 KEY_2
#define TECLA_3 KEY_3
#define TECLA_4 KEY_4
#define TECLA_5 KEY_5
#define TECLA_6 KEY_6
#define TECLA_7 KEY_7
#define TECLA_8 KEY_8
#define TECLA_9 KEY_9
 
#define TECLA_F1 KEY_F1
 
class Hardware {
 
 public:
 
    void inicializar(int ancho, int alto);
    bool comprobarTecla();
 
    bool comprobarTecla(int codigoTecla);
    int esperarTecla();
    void vaciarBufferTeclado();
    bool algunaTeclaPulsada();
 
 
    void borrarOculta();
    void dibujarOculta(ElementoGraf e);
 
    void visualizarOculta();
 
    void pausa(long ms);
 
 private:
 
    int anchoPantalla;
    int altoPantalla;
 
    BITMAP *pantallaOculta;
    int maxX;
    int maxY;
    int colores;
    int teclaPulsada;
    int posXRaton;
    int posYRaton;
 
 private:
 
    BITMAP pantallaVisible;
    BITMAP fondo;
 
 
};
#endif
 

Y el desarrollo podría ser así:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*   Hardware.cpp:              */
/*     Clase "Hardware"         */
/*       para Allegro           */
/*     Fichero de desarrollo    */
/*                              */
/*     Parte de "MiniMiner"     */
/*                              */
/*  Ejemplo:                    */
/*    Primer acercamiento a     */
/*    "MiniMiner"               */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
#include "Hardware.h"
#include "ElementoGraf.h"
#include "iostream"
 
#ifdef __cplusplus
    #include <cstdlib>
#else
    #include <stdlib.h>
#endif
 
#include <math.h>
#include <string>
 
#include <allegro.h>
 
 
void Hardware::inicializar(int ancho, int alto)
{
 
    cout << "hard-ctor";
    allegro_init();        // Inicializamos Allegro
    install_keyboard();
    install_timer();
 
    anchoPantalla = ancho;
    altoPantalla = alto;
 
                           // Intentamos entrar a modo grafico
    set_color_depth(32);
    if (set_gfx_mode(GFX_SAFE, ancho, alto, 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);
        exit( 1 );
    }
 
    // Pantalla oculta para evitar parpadeos
    // (doble buffer)
    pantallaOculta = create_bitmap(ancho, alto);
 
 
}
 
 
/** teclaPulsada: devuelve TRUE si alguna tecla
 *  ha sido pulsada
 */
bool Hardware::algunaTeclaPulsada()
{
    return keypressed;
}
 
 
/** comprobarTecla: devuelve TRUE si una cierta tecla
 *  ha sido pulsada
 */
bool Hardware::comprobarTecla(int codigoTecla)
{
      return key[codigoTecla];
}
 
 
/** esperarTecla: pausa hasta que se pulse una tecla,
 *  devuelve el codigo de tecla pulsada
 */
int Hardware::esperarTecla()
{
    return readkey() >> 8;
}
 
 
 
 
void Hardware::borrarOculta()
{
    // Borrar pantalla de fondo
    clear_bitmap(pantallaOculta);
}
 
 
void Hardware::dibujarOculta(ElementoGraf e)
{
    draw_sprite( pantallaOculta, e.leerImagen(), e.leerX(), e.leerY() );
}
 
 
 
void Hardware::visualizarOculta()
{
    // Sincronizo con el barrido para evitar parpadeos
    // y vuelco la pantalla oculta
    vsync();
    blit(pantallaOculta, screen, 0, 0, 0, 0,
      anchoPantalla, altoPantalla);
}
 
 
void Hardware::pausa(long ms)
{
    rest(ms);
}
 
 

Del mismo modo, el fichero de cabecera de ElementoGraf podría ser:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*   ElementoGraf.h:            */
/*     Clase "elemento gráfico" */
/*       para Allegro           */
/*     Fichero de cabecera      */
/*                              */
/*     Parte de "MiniMiner"     */
/*                              */
/*  Ejemplo:                    */
/*    Primer acercamiento a     */
/*    "MiniMiner"               */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
#ifndef ElementoGraf_h
#define ElementoGraf_h
 
#include <allegro.h>
 
class ElementoGraf {
 
 public:
 
    void moverA(int x, int y);
    void indicarAnchoAlto(int an, int al);
    void crearDesdeFichero(char *nombre);
    BITMAP* leerImagen();
    int leerX();
    int leerY();
    int leerAnchura();
    int leerAltura();
 
    bool colisionCon(ElementoGraf e2);
    bool colisionCon(int x, int y, int ancho, int alto);
 
 protected:
 
    int posX;
    int posY;
    int anchura, altura;
    int anchuraOrig, alturaOrig;
    int colorTransp;
 
 private:
 
    BITMAP* imagen;
 
};
#endif
 

Y su desarrollo:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*   ElementoGraf.cpp:          */
/*     Clase "elemento gráfico" */
/*       para Allegro           */
/*     Fichero de desarrollo    */
/*                              */
/*     Parte de "MiniMiner"     */
/*                              */
/*  Ejemplo:                    */
/*    Primer acercamiento a     */
/*    "MiniMiner"               */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
#include "ElementoGraf.h"
 
#ifdef __cplusplus
    #include <cstdlib>
#else
    #include <stdlib.h>
#endif
 
#include <allegro.h>
 
/** moverA: cambia la posicion del elemento grafico
 *  (actualiza tambien las coordenadas del centro)
 */
void ElementoGraf::moverA(int x, int y)
{
    posX = x;
    posY = y;
}
 
/** indicarAnchoAlto: indica el ancho y el alto del
 *  elemento gráfico, para que se pueda calcular colisiones
 */
void ElementoGraf::indicarAnchoAlto(int an, int al)
{
    anchura = an;
    altura = al;
}
 
/** leerX: devuelve la coordenada X de la posicion
 */
int ElementoGraf::leerX()
{
    return posX;
}
 
/** leerY: devuelve la coordenada Y de la posicion
 */
int ElementoGraf::leerY()
{
    return posY;
}
 
/** leerAnchura: devuelve la anchura del elemento grafico
 */
int ElementoGraf::leerAnchura()
{
    return anchura;
}
 
/** leerAltura: devuelve la altura del elemento grafico
 */
int ElementoGraf::leerAltura()
{
    return altura;
}
 
 
/** leerImagen: devuelve la imagen (bitmap) del elemento grafico
 */
BITMAP* ElementoGraf::leerImagen()
{
    return imagen;
}
 
/** crearDesdeFichero: lee desde fichero el bitmap, y actualiza
 *  su anchura y altura
 */
void ElementoGraf::crearDesdeFichero(char *nombre)
{
    imagen = load_bmp(nombre, NULL);
    if (!imagen) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("No se ha podido abrir la imagen\n");
        exit( 1 );
    }
}
 
 
 
/** colisionCon: devuelve si hay colision del ElementoGraf con otro
 */
bool ElementoGraf::colisionCon(ElementoGraf e2)
{
    return colisionCon(e2.posX, e2.posY, e2.anchura, e2.altura);
}
 
 
/** colisionCon: devuelve si hay colision del ElementoGraf con
 *  un rectangulo dado por sus coordenadas
 */
bool ElementoGraf::colisionCon(int x, int y, int ancho, int alto)
{
    if ((this->posX+this->anchura > x)
        && (this->posY+this->altura > y)
        && (x+ancho > this->posX)
        && (y+alto > this->posY))
      return true;
    else
      return false;
}
 

Y el cuerpo del programa, a pesar de que todavía tiene partes repetitivas (como eso de que haya una variable x para el personaje, pero también otra xEnemigo y otra xSuelo), ya ocupa unas 60 líneas menos:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    miner02.cpp               */
/*                              */
/*  Ejemplo:                    */
/*    Primer acercamiento a     */
/*    "MiniMiner"               */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
#include "Hardware.h"
#include "ElementoGraf.h"
 
/* -------------- Constantes globales ------------- */
#define ANCHOPANTALLA 640
#define ALTOPANTALLA 480
 
/* -------------- Variables globales -------------- */
PALETTE pal;
/*BITMAP *personaje;
BITMAP *enemigo;
BITMAP *presentacion;
BITMAP *fragmentoSuelo;
BITMAP *pantallaOculta;*/
Hardware hard;
ElementoGraf personaje, enemigo, presentacion, fragmentoSuelo;
 
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 (hard.comprobarTecla(TECLA_ESC))
     partidaTerminada = true;
 
  if (hard.comprobarTecla(TECLA_DCHA))
     moverDerecha();
  else if (hard.comprobarTecla(TECLA_IZQD))
     moverIzquierda();
}
 
 
// -- 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() {
 
  hard.borrarOculta();
  dibujarFondo();
  enemigo.moverA(xEnemigo, yEnemigo);
  hard.dibujarOculta(enemigo);
  personaje.moverA(x, y);
  hard.dibujarOculta(personaje);
  hard.visualizarOculta();
}
 
 
// -- Pausa hasta el siguiente fotograma
void pausaFotograma() {
  // Para 25 fps: 1000/25 = 40 milisegundos de pausa
  hard.pausa(40);
}
 
 
// -- Funciones que no son de la logica juego, sino de 
// funcionamiento interno de otros componentes
 
// -- Pantalla de presentacion
void lanzarPresentacion() {
  presentacion.moverA(0,0);
  hard.dibujarOculta(presentacion);
  hard.visualizarOculta();
  hard.esperarTecla();
}
 
// -- 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++)
  {
      fragmentoSuelo.moverA(i*anchoImagen, ySuelo);
      hard.dibujarOculta(fragmentoSuelo);
  }
}
 
/* -------------- Rutina de inicialización -------- */
int inicializa()
{
    hard.inicializar(640,480);
 
    personaje.crearDesdeFichero("personaje.bmp");
    enemigo.crearDesdeFichero("enemigo.bmp");
    fragmentoSuelo.crearDesdeFichero("suelo.bmp");
    presentacion.crearDesdeFichero("miner.bmp");
 
   // 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();
 
    hard.pausa(1000);
    return 0;
}
            /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();
 

Anterior Siguiente