domingo, 4 de octubre de 2015

Crear juego RPG en C++ y Allegro 4 (17) Lucha II

Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se continuará el sistema de lucha.





Objetivo 

Añadir un nuevo movimiento al enemigo, cuando entra en combate, la posibilidad de golpear al jugador. Controlar que no se golpee si no esta enfrente del enemigo. Añadir mas de un enemigo.


Programación

En el archivo audio.h, se añaden dos nuevas funciones para los dos nuevos sonidos: espada choca, y herido.

void sonido_espada_choca(){
    play_sample ( (SAMPLE *)datosjuego[dsespada3].dat, 110,128, 900, 0 );        
}

void sonido_herido(){
    play_sample ( (SAMPLE *)datosjuego[dsaa].dat, 160,128, 900, 0 );             
}

En el archivo players.h, se añaden dos funciones nuevas a la clase. La función dire() que devuelve el valor de la variable direccion. Y la función herido() que se utiliza para disminuir la vida según el parámetro dado.

class player
{
    BITMAP *prota;  
 int x,y;
 int direccion;
 int animacion;
    bool hablar;
    int ataca;
    
    int vida;
    int vidamax; 
          
    public:  
       void inicia();    
       void pinta(); 
       bool choca();
       void teclado(); 
       int getx(){ return x; };
       int gety(){ return y; };
       void posiciona( int _x, int _y);
       void cambia_escenario();
       bool accion();
       void habla();
       void no_habla();
       bool hablando(){ return hablar; };
       bool atacando(){ return ataca>1; };
       void no_ataca(){ ataca = -3; };
       int  dire(){ return direccion; };
       
       int getvida(){ return vida; };
       int getvidamax(){ return vidamax; };
       void herido(int n){ vida-=n; };
};

En el archivo npc.h, se cambia la función posicion_cerca(). Se quitan los parámetros que recibía, y se calcula la posición del jugador.

bool npc::posicion_cerca()
{
     int _x = jugador.getx() + desplazamiento_map_x;
     int _y = jugador.gety() + desplazamiento_map_y;
     int d = 32 + (desplazamiento*2);
     int d2 =abs ( _x - x ) + abs ( _y - y );
     return d2 <= d && lugar == escena ;
}

Esta función devuelve true en el caso de que el jugador este cerca del enemigo. Las dos primeras líneas declaran dos variables que contienen la posición del jugador. Como la función de posición del jugador es con respecto a la pantalla y no con respecto al escenario, es por ello que se le añade el desplazamiento del mapa para obtener la posición real en el escenario. El resto del código se mantiene igual, calculando la distancias entre ambos puntos.

Recuerda que al cambiar la función npc::posicion_cerca(), en la función evento_escenario() de mijuego.h debes modificar todas las llamadas, ya que ahora no tiene parámetros.

En la clase enemigo, se añade una nueva función para evitar el fallo que había antiguamente de que si el jugador estaba cerca pero no mira hacia el objetivo, esta función se encarga de comprobar si el jugador esta de frente al objetivo.

class enemigo : public npc {
      int vida;
      int v_actual; 
      bool muerto;     
      int golpeado;
     public:
        void crea( BITMAP *_img, int _x, int _y, int dir, int _estado, int _lugar, int v );
        void herida( int d ); 
        void pinta();
        bool ha_muerto() { return muerto; };
        void movimiento();
        bool frente();
};

También se a añadido una nueva función en la que se controla el movimiento del enemigo cuando entra en lucha contra el jugador.

En la función crea(), se tiene que inicializar la nueva variable golpeado con el valor cero.


void enemigo::herida( int d )
{     
  if ( !muerto )
  {   
     int num = FRAME_RATE / 2;      
     v_actual-=d;
         
     if ( v_actual <= 0 )
     {
           muerto = true;
           blit( mifondo, fondo, 0,0, x,y, 32,32); 
           rectfill( choque, x+2, y+1, x+30, y+31, 0x000000);
           sonido_muere();
     }else{
        // daño defensivo del enemigo   
        if ( tiempo_total % num == 0 )
        {
             if ( rand()%2 == 1 )
             {
                 sonido_herido(); 
                 jugador.herido(5+rand()%5); 
             }
        }   
     }
  }   
};

La función herida() ha cambiado, ahora se añadió que cada vez que reciba un daño tenga la oportunidad de responder con un daño al jugador, de esta manera se crea como un daño defensivo del enemigo.

El funcionamiento de herida() es el siguiente: primero se comprueba que no este muerto, en el caso de no estarlo se resta el daño pasado por parametro a la vida. En el caso de que la vida actual llegue a cero muere el enemigo. Y aqui llega lo nuevo, sino esta muerto se comprueba si el tiempo_total es divisible entre num, y si el numero aleatorio obtenido es 1, en ese caso el enemigo daña al jugador.


bool enemigo::frente()
{
    int jx = jugador.getx() + desplazamiento_map_x;
    int jy = jugador.gety() + desplazamiento_map_y; 
    
    int d =  jugador.dire();
    
    if ( jx > x )
    {
        if ( abs ( jy - y ) < desplazamiento*2 )
        {
             if ( d == 1 )
             {
                  return true;
             }else{
                  return false; 
             }
        }
    }  
    
    if ( jx < x )
    {
        if ( abs ( jy - y ) < desplazamiento*2 )
        {
             if ( d == 2 )
             {
                  return true;
             }else{
                  return false; 
             }
        }
    }    
    
    if ( jy < y )
    {
        if ( abs ( jx - x ) < desplazamiento*2 )
        {
             if ( d == 0 )
             {
                  return true;
             }else{
                  return false; 
             }
        }
    }  
       
    if ( jy > y )
    {
        if ( abs ( jx - x ) < desplazamiento*2 )
        {
             if ( d == 3 )
             {
                  return true;
             }else{
                  return false; 
             }
        }
    }    
    
    return false;            
}


Esta función frente(), se encarga de comprobar en que direccion esta el jugador. En el caso de que este mirando hacia el enemigo se considera que esta de frente y devuelve true.

Se añade también la función movimiento().

void enemigo::movimiento()
{
    ax = x;
    ay = y;  
    int jx = jugador.getx() + desplazamiento_map_x;
    int jy = jugador.gety() + desplazamiento_map_y;  

    // para indicar que se ejecuta dos veces 
    int num = FRAME_RATE / 6; 
    
    int esta = 0; 
    
    // mira hacia donde este el jugador
    if ( jx > x )
    {
         direccion = 2;
    }else{
         direccion = 1; 
    }

    if ( jy > y )
    {
        if ( abs ( jx - x ) < desplazamiento*3 )
        {         
            direccion = 0;
        }
    }else{
        if ( abs ( jx - x ) < desplazamiento*3 )
        {   
            direccion = 3; 
        }    
    }

    // enemigo te persigue

    if ( tiempo_total % num == 0 )
    {
             
        if ( x+32 < jx )
        {
             x+=desplazamiento;
             esta = 1;
        }
        
        if ( x > jx+32 )
        {
             x-=desplazamiento;
             esta = 1;
        }
        if ( y+32 < jy )
        {
             y+=desplazamiento;
             esta = 1;
        }
        
        if ( y > jy+32  )
        {
             y-=desplazamiento;
             esta = 1;
        }    
  
    }
       
     
    if ( ax != x || ay != y )
    {
      // se ha movido en una de las direcciones
      
      if ( chocanpc() )
      {
           x = ax;
           y = ay;
           esta = 0;
      }
      if ( esta != 0 )
      { 
           animacion++;
           if ( animacion > 2 ) animacion = 0;
      }       
                 
      // borrar antiguo choque
      rectfill( choque, ax+2, ay+1, ax+30, ay+31, 0x000000);

      // pinta el nuevo choque
      rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000);    
    }     
    

    if ( posicion_cerca() )
    {
        int num = FRAME_RATE / 3;
        if ( tiempo_total % num == 0 )
        {
             if ( rand()%3 == 1 )
             {             
                 sonido_herido(); 
                 jugador.herido(2+rand()%2);
                 animacion++;
                 if ( animacion > 2 ) animacion = 0;                 
             } 
        }                
    } 
     
    
    // cambie o no se cambia el fondo por la animacion
    
    // restaura fondo anterior antes de pintar la nueva imagen  
    blit( mifondo, fondo, 0,0, ax,ay, 32,32);  
    
    // obtiene una copia del nuevo fondo que va a ser ocupado
    blit( fondo, mifondo, x,y,0,0,32,32);    
}

Esta función movimiento(), hace que el enemigo intente ir hacia donde este el jugador. En el caso de estar cerca al jugador realiza de forma aleatoria un ataque. Se podría decir que es la IA del enemigo cuando entra modo lucha.

En la función pinta(), se a añadido alguna cosas que anteriormente estaban fuera.

void enemigo::pinta()
{ 
 
     if ( lugar == escena && !muerto )
     {
           
          if ( !primer )
          {
               // obtiene una copia de lo anterior
               blit( fondo, mifondo, x,y,0,0,32,32);
               primer = true;                
          }
                                    
          if ( v_actual == vida )
          {
               actualiza();
          }else{
               movimiento(); 
          }     
          masked_blit(imagen, fondo, animacion*32, direccion*32, x, y, 32,32);

        if ( golpeado == 1 )
        {
            int xn = 2 + rand()%2;
            jugador.no_ataca();
            if ( rand()%10 != 1 )
            {
               sonido_espada_da();            
               herida(xn);              
            }else{
               sonido_espada_choca();   
            }   
            golpeado = 0;
            
        }
        
        if ( golpeado == 0 && jugador.atacando() && posicion_cerca()
             && frente() )
        {
              golpeado = 1;
        } 
                    
          if ( !muerto )
          {
              int nm = (v_actual * 30 ) / vida;               
              rectfill( fondo, x+1, y, x+nm, y+5, 0x00ff00);
              rect( fondo, x, y, x+31, y+5, 0x000000);
          }
     } 
      
     if ( lugar != escena && v_actual < vida )
     {
        int num = FRAME_RATE / 5; 
        if ( tiempo_total % num == 0 )
        {          
            v_actual++;
        }
     }     
}

Se controla si el jugador a golpeado al enemigo, teniendo una pequeña probilidad de pararlo (1 de 10). También si el jugador se va del escenario donde se encuentra el enemigo, en el caso de estar herido el enemigo recuperará vida poco a poco. Mientras el enemigo tenga la vida al completo se mueve como un npc y si esta herido se mueve según la función movimiento().

En el archivo mijuego.h, después de la declaración de la variable jugador se realiza el #include "npc.h", por ello se debe de eliminar el include de npc.h del archivo main.cpp.

Se crean dos variables globales malos[], nmalos, para controlar los enemigos. Quedando de la siguiente forma el inicio del archivo mijuego.h.

player jugador;

#include "npc.h"

npc personajes[30];
int npersonaje;

enemigo malos[30];
int nmalos;

MENSAJE texto, dialogo;

int mision;

// para saber si el mapa tiene scroll 
bool desplaza;

const int scroll_rango1 = 200;
const int scroll_rango2 = 90;

En la función carga_juego(), se añade la creación de dos enemigos, sustituyendo la linea donde se creaba al enemigo malo.crea().

    nmalos = 2;
    malos[0].crea( (BITMAP *)datosjuego[diene001].dat, 380, 280, 3,5,2,100);
    malos[1].crea( (BITMAP *)datosjuego[diene001].dat, 400, 720, 0,5,2,100);

En la función evento_escenario(), se elimina lo siguiente:

    if ( jugador.atacando() && malo.posicion_cerca(pzx,pzy)
       && !malo.ha_muerto() )
    {
        int xn = 2 + rand()%2;
        jugador.no_ataca();
        sonido_espada_da();
        malo.herida(xn);               
    }

Esto ya no es necesario aqui, ya que esto se controla en la clase enemigo.

En la función pinta_juego(), se elimina la llamada malo.pinta(), y se sustituye por lo siguiente:

       for ( int z=0; z < nmalos; z++ )
        {         
            malos[z].pinta();
        }

De esta forma se pinta tantos malos como se tengan, según la variable nmalos y la dimensión de la variable malos[], que en este caso es de 30.

Hasta aquí llega la programación, solo hay que actualizar el fichero DAT que contiene los nuevos sonidos, etc.

Haz clic aqui para descarcar fichero DAT en formato RAR

Recuerda

Si tienes algún problema con los ejemplos de la pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación:  http://creatusjuegosdecero.webege.com/index.php

No hay comentarios:

Publicar un comentario

Antes de publicar un comentario

Todos los comentarios que se realicen en el blog son moderados.

Debido a esto es muy probable que tu comentario no aparezca de inmediato ya que previamente debe ser revisado para evitar un mal uso (SPAM).

Podrán realizar comentario cualquiera que tenga una cuenta de Google.

Related Posts Plugin for WordPress, Blogger...