domingo, 30 de agosto de 2015

Crear juego RPG en C++ y Allegro 4 (4) FPS

Aquí esta la cuarta entrega, en ella se añadirá el primer escenario, y algunos cambios en el código para mejorar la jugabilidad, controlando los FPS.



Si quieres ver alguna de las anteriores entregas entra en el Contenido del Blog

Recuerda, si tienes algún problema con los ejemplos de la página, o alguna duda. Puedes plantear tu pregunta en el foro de programación:



Hasta el momento solo se tiene un personaje andando, cuya animación de movimiento es muy variable según el valor que se le haya dado a la función rest(). Algunos que tengan ordenadores mas nuevos y potentes le habrán tenido que poner un número mas alto, y los que tengan ordenadores antiguos y lentos le habrán puesto un valor mas pequeño.  Esto no es mas que un apaño, es decir, que solo nos vale a quienes lo estamos programando y probando, ya que se ha adaptado dicho valor a nuestro ordenador. Se debe programar algo mas genérico que pueda funcionar en todos por igual.

Para ello, se debe de controlar los Fotogramas por Segundo (FPS).


Que son los FPS ?

Las imágenes por segundo (fotogramas por segundo o cuadros por segundo, en inglés frames per second o FPS) es la medida de la frecuencia a la cual se muestran distintos fotogramas (frames). En informática estos fotogramas están constituidos por un número determinado de píxeles que se distribuyen a lo largo de una red de texturas. La frecuencia de los fotogramas es proporcional al número de píxeles que deben generarse, incidiendo en el rendimiento del ordenador que los reproduce. El número de FPS que el ojo humano necesita para ver una imagen con fluidez es de 24.

Aparte del control de los FPS, también se añade el primer escenario. Esto conlleva a el control de colisiones, y superposiciones de objetos.


Programación

Se realizarán los siguientes cambios:

En global.h se añade
// controla el bucle principal
bool salir;

// Variable usada para la velocidad
volatile unsigned int contador_tiempo_juego = 0;

// Indica los FPS
const int FRAME_RATE = 30;

// Función para controlar la velocidad
void inc_contador_tiempo_juego()
{
    contador_tiempo_juego++;
}
END_OF_FUNCTION(inc_contador_tiempo_juego)

En el programa principal vuelve a cambiar, quedando de la siguiente forma:

/*
  Name:     RPG
  Author:   Yadok - KODAYGAMES
  Date:     27/08/15 
  Web:      http://devcpp-allegro.blogspot.com/
  Description: 
         Creacion de un juego al estilo RPG
         mas informacion en la web        
 Version: curso 4
*/

 
#include < allegro .h > 
#include "global.h"
#include "players.h"
#include "mijuego.h"


   
void inicia_allegro()
{
 allegro_init();
 install_keyboard();
 
 set_color_depth(32);
 set_gfx_mode(GFX_AUTODETECT_WINDOWED, PANTALLA_ANCHO, PANTALLA_ALTO, 0, 0);
 
 buffer = create_bitmap(PANTALLA_ANCHO, PANTALLA_ALTO);    
    
    LOCK_VARIABLE(contador_tiempo_juego);
    LOCK_FUNCTION(inc_contador_tiempo_juego); 
    
    // Iniciamos el limitador de FPS
    install_int_ex(inc_contador_tiempo_juego, BPS_TO_TIMER( FRAME_RATE ));    
}
         
 // programa principal 
int main()  
{ 
        inicia_allegro();
    
        carga_juego();
          
 salir = false;
  
    
 while ( !salir )
 {   
          if ( contador_tiempo_juego )
          {
                while ( contador_tiempo_juego )
                {
                    actualiza_juego();
    
                    contador_tiempo_juego--;
                }  
                
                clear_to_color(buffer, 0x00000);    
                
                pinta_juego();
                
                pintar_pantalla();         
                
          }else{
                                    
               rest(1);  
          }
       // tecla de salida
       if ( key[KEY_ESC] ) salir = true;
    }   
  
 destroy_bitmap(buffer);
 
 return 0;
}
END_OF_MAIN();

En el curso anterior se comentó de poner las funciones del jugador en un archivo aparte, aqui se muestra el contenido de ese archivo que he llamado players.h


// players.h


// Esta clase se encarga del manejo del jugador
class player
{
    BITMAP *prota;  
 int x,y;
 int direccion;
 int animacion; 
          
    public:  
       void inicia();    
       void pinta(); 
       void teclado(); 
       int getx(){ return x; };
       int gety(){ return y; };
       void posiciona( int _x, int _y);
};


void player::inicia()
{
    prota  = load_bmp("personaje.bmp",NULL); 
 // inicializar vbles
 direccion = 0;
 animacion = 0;
 x = 540;
 y = 280;        
}


void player::pinta()
{
    masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32);  
} 

void player::teclado()
{
      int ax = x;
      int ay = y;
      // teclas control usuario
      if ( key[KEY_UP] )
      {
           y-=desplazamiento; 
           direccion = 3;              
      }
      if ( key[KEY_DOWN] )
      {
           y+=desplazamiento;
           direccion = 0;
      }
      if ( key[KEY_LEFT] )
      {
           x-=desplazamiento;
           direccion = 1;
      }
      if ( key[KEY_RIGHT] )
      {
           x+=desplazamiento;
           direccion = 2;
      }  
      if ( ax != x || ay != y )
      {
           // entra si a cambiado alguna de las variables x,y
           animacion++;
           if ( animacion > 2 ) animacion = 0;
      }                            
      
      // limites globales
      if ( x < 0 ) x = 0;
      if ( x > PANTALLA_ANCHO ) x = PANTALLA_ANCHO;
      if ( y < 0 ) y = 0;
      if ( y > PANTALLA_ALTO )  y = PANTALLA_ALTO;      
}

void player::posiciona( int _x, int _y)
{
     x=_x;
     y=_y;
}



Y tambien se a añadido otro archivo en el que se pondrá lo que es la base de nuestro juego. A este archivo lo he llamado mijuego.h


/*
   mijuego.h
   
*/

BITMAP *fondo;
BITMAP *choque;
BITMAP *alto;

player jugador;


// carga todo lo necesario antes de empezar el juego
void carga_juego()
{
    jugador.inicia();
    // cargamos imagenes del primer escenario
    fondo  = load_bmp("casa.bmp",NULL);
    choque = load_bmp("casa-choque.bmp",NULL);
    alto   = load_bmp("casa-sup.bmp",NULL);     
}


// actualiza el estado del juego
void actualiza_juego()
{
    int ax,ay;
    ax = jugador.getx();
    ay = jugador.gety(); 
    jugador.teclado(); 
    
    // comprobar si colisiona con el mapa
    bool choca = false;
    int px = jugador.getx()-160;
    int py = jugador.gety()-160+16;    
    for ( int ci=0; ci  < 32; ci++)
    {
        for (int cj=0; cj < 16; cj++)
        {

            if ( getpixel( choque, px+ci, py+cj) == 0xff0000 ){
                 choca = true;
                 ci = 32;
                 cj = 16;
            }
            if ( getpixel( choque, px+ci, py+cj) == 0x00ff00 ) salir = true;
        }
    }    
    if ( choca ){
         // vuelve al estado anterior
         jugador.posiciona( ax,ay );
    }
        
}


// Se encarga de pintar todo sobre el buffer   
void pinta_juego()
{
    blit( fondo, buffer, 0,0, 160, 160, 480,325);  
    jugador.pinta();     
    masked_blit( alto, buffer, 0,0, 160, 160, 480,325); 
}   


Paso a Paso 

Para el control de los FPS se ha creado una función llamada inc_contador_tiempo_juego, esta función solo se encarga de incrementar el valor de la variable contador_tiempo_juego un determinado numero de veces por segundo.

install_int_ex(inc_contador_tiempo_juego, BPS_TO_TIMER( FRAME_RATE ));

Esta es la función que se encarga de que se llame según la variable FRAME_RATE que en el ejemplo tiene un valor de 30.

El bucle principal tiene una primera condición que se cumplirá siempre que contador_tiempo_juego tenga un valor distinto a 0. Dentro de esta condición tiene un bucle que se estará repitiendo hasta que el valor de contador_tiempo_juego sea 0, mientras se estará actualizando nuestro juego. En el momento en el que llega al valor 0 sale del bucle y continua pintando por pantalla. De este modo como el contador_tiempo_juego se incrementará 30 veces en un segundo, en teoría, deberá pintarlo 30, ya que mientras que el valor de la variable sea 0 no hará nada.

En player.h se han añadido algunas nuevas funciones que hacia falta, como son las que permite obtener la posición actual getx(), gety() y la función que sitúa al personaje en una posición indicada ( posiciona ). 

En mijuego.h se tiene lo siguiente:

  • carga_juego: se encarga de carga todos los datos y archivos que se necesiten antes de iniciar el juego.
  • actualiza_juego: se encarga del manejo interno del juego, aquí es donde se controla todo, ya sea el movimiento del jugador, como la colisión con los objetos.
  • pinta_juego: se encarga de volcar todas las imagenes sobre el buffer, para montar la imagen que se mostrará por pantalla.

Esta es la imagen de choque, en esta imagen se marca con el color rojo por donde no queremos que pueda pasar el personaje, y el color verde para indicar la salida.

La función utilizada para controlar la colisión del personaje con el escenario se encarga de comparar la mitad del espacio que ocupa el personaje, con la imagen choque. Con el comando getpixel va comprobando pixel a pixel si donde se va a situar la imagen del personaje existe algún pixel de color rojo, en este caso existe colisión y por tanto vuelve a su posición anterior.  Y en el caso de que detecte algún pixel de color verde este provocará la finalización del programa. 

    bool choca = false;
    int px = jugador.getx()-160;
    int py = jugador.gety()-160+16;    
    for ( int ci=0; ci  < 32; ci++)
    {
        for (int cj=0; cj < 16; cj++)
        {

            if ( getpixel( choque, px+ci, py+cj) == 0xff0000 ){
                 choca = true;
                 ci = 32;
                 cj = 16;
            }
            if ( getpixel( choque, px+ci, py+cj) == 0x00ff00 ) salir = true;
        }
    }

El primer bucle recorre la imagen a lo ancho (32 pixel) y la segunda a lo alto (16 pixel). Las variables px,py contiene la posición del jugador dentro de la imagen choque, como se ha mostrado la imagen choque en la posición (160,160), por eso se le resta 160 a la posición del jugador. Y en la altura se le suma 16 para que tenga en cuenta la parte de abajo.

El comando getpixel obtiene el pixel que se encuentra en la posicion px+ci, py+cj de la imagen choque, y devuelve el valor del color de dicho pixel.

En la función de pinta_juego(), se pinta primero el fondo, luego al personaje y finalmente la imagen con zonas transparente.


Llegado a este punto, si todo esta correctamente copiado donde debe, solo hará falta tener las imagenes, para evitar posibles fallos las he comprimido las imagenes en un archivo RAR .

Si alguno quiere descargarse el codigo fuente del proyecto en Dev-c++

9 comentarios:

  1. ¿En donde puedo generar mis propios bmp?

    ResponderEliminar
    Respuestas
    1. Puedes utilizar cualquier editor gráfico que te permita guardar en formato BMP, por ejemplo, si tienes el Sistema Operativo Windows, este trae un editor bastante sencillo y simple llamado Paint.

      Eliminar
  2. Disculpa, pero no me detecta el color verde al salir del escenario, copie todo el codigo de la pagina mijuego.h solo para verificar si estaba haciendo algo mal, pero no, todo esta igual en el codigo, sabes a que se deba?

    ResponderEliminar
  3. comprueba que en la imagen el color sea exactamente el verde 0x00ff00

    ResponderEliminar
    Respuestas
    1. Gracias, cambie el tono del verde y ahora el programa funciona a la perfeccion, buen blog amigo :D

      Eliminar
  4. Me gustaria saber para que sirve la foto que tiene el rosa que finalidad tiene y que criterios hay que usar para poner el rosa no lo entiendo muy bien muchas gracias

    ResponderEliminar
    Respuestas
    1. El comando masked_blit utiliza el color rosa 0xff00ff como color transparente (no pinta). Y se utiliza en el ejemplo para poner la imagen casa-sup.bmp por encima de las otras, de manera que el personaje puede quedar tapado segun esta imagen. ( en el ejemplo el prota se puede ocultar detras de las macetas y la bañera)

      Eliminar
  5. hola amigo... estoy siguiendo tus tutoriales desde hace unos dias, y siento que este es el mejor y mo entiendo por que lo descontinuaste para iniciar otro en otro blog... ese esta muy desordenado si lo comparamos con este...

    ResponderEliminar
    Respuestas
    1. Se creó el otro blog ya que este utiliza la librería de Allegro versión 4, y en el otro se utiliza la librería versión 5.

      Eliminar