sábado, 19 de septiembre de 2015

Crear juego RPG en C++ y Allegro 4 (12) NPC II

Continuamos con el curso de crea tu juego RPG en C++ y Allegro. Seguimos con los NPC







Objetivo del curso

En esta entrega, se hará que los NPC puedan tener movimiento, y tendrán varios tipos de movimientos ( horizontal, vertical, etc. ).


Programación

En el archivo main.h en la funcion de inicia_allegro() al final se añadirá el siguiente comando:


srand (time(NULL));

Este comando sirve para inicializar los valores aleatorios.

En el archivo players.h en la función de teclado() se elimina los cuatro if que controlan los limites globales de la pantalla.


      // limites globales
      if ( x < 0 ) x = 0;
      if ( x > PANTALLA_ANCHO-32 ) x = PANTALLA_ANCHO-32;
      if ( y < 0 ) y = 0;
      if ( y > PANTALLA_ALTO-32 )  y = PANTALLA_ALTO-32;  


Estas condiciones se habian puesto con la intención de que el jugador no pueda desaparecer de la pantalla saliendo por uno de sus bordes. Se elimina ya que esto es controlado por los mapas de choque, y si estan bien hechos el jugador no se sale de pantalla.

Se añade una nueva función llamada cambia_escenario(). Esta nueva función se encarga de borrar el rectangulo blanco creado por el jugador, y esto debe hacerse antes de cambiar a otro escenario. Con el comando rectfill() se crea un rectangulo relleno de un color, en nuestro caso se rellena del color negro (0x000000).

void player::cambia_escenario()
{
     // siempre antes de cambiar a otro escenario se debe de borrar 
     // el cuadro de choque
     int mx = desplazamiento_map_x;
     int my = desplazamiento_map_y;
               
     rectfill( choque, x+4+mx, y+17+my, x+28+mx, y+30+my, 0x000000);     
}

En el archivo npc.h se añaden nuevas funciones para que nuestro npc pueda moverse. Se añaden dos nuevas variables privadas:

      BITMAP* mifondo;
      bool primer;

Y tambien se añaden dos nuevas funciones:

      void actualiza();  
      bool chocanpc();

La funcion chocanpc(), se encarga de comprobar las colisiones. Y la función actualiza() se encarga del movimiento del npc.

En la función crea(), se añade la inicialización de las nuevas variables:

     mifondo = create_bitmap(32, 32);            
     primer = false;

La función chocanpc():

bool npc::chocanpc()
{
    int ninix,niniy;
    int nfinx,nfiny;
               
    if ( direccion == 0 )
    {
         // abajo
         ninix = 0; 
         niniy = 32 - desplazamiento;
         nfinx = 32;
         nfiny = 32;
    }
    if ( direccion == 1 )
    {
         // izquierda
         ninix = 0; 
         niniy = 0;
         nfinx = desplazamiento;
         nfiny = 32;
    }
    if ( direccion == 2 )
    {
         // derecha
         ninix = 32 - desplazamiento; 
         niniy = 0;
         nfinx = 32;
         nfiny = 32;
    }
    if ( direccion == 3 )
    {
         // arriba
         ninix = 0; 
         niniy = 0;
         nfinx = 32;
         nfiny = desplazamiento;
    }            
         
    // comprobar si colisiona con el mapa  
    for ( int ci=ninix; ci < nfinx; ci++)
    {
        for (int cj=niniy; cj < nfiny; cj++)
        {

            // color rojo
            if ( getpixel( choque, x+ci, y+cj) == 0xff0000 ){
                 return true;
            }  
            // color blanco prota
            if ( getpixel( choque, x+ci, y+cj) == 0xffffff ){
                 return true;
            }                                 
        }
    } 
    return false; 
}


Esta función se encarga de controlar la colisión entre el NPC y el escenario controlando el color rojo, y la colisión entre el NPC y el jugador con el color blanco. El sistema de colisión es igual que el del jugador mediante el comando getpixel() extrae el color del pixel que hay en el mapa choque en una posición concreta y la compara con los colores de choque, pero debido a que habrá mas cantidad de NPC para evitar que se relentice todo se ha mejorado un poco el sistema de colisión, ya que se ha reducido drasticamente el espacio que se compara para comprobar si hay un pixel de color rojo o blanco. Según la dirección se obtiene un rango de acción según las variables (ninix,niniy) y (nfinx,nfiny).


Como muestra la imagen, la zona pintada en verde indica el espacio que se comprueba para la colisión y según el número indica la dirección a la que pertenece. Por tanto si el NPC va hacia abajo se comprobará si existe colisión en el rectángulo verde con el numero 0.

Aqui tienen un video, donde se muestran las colisiones.



La función actualiza(), se encarga del movimiento del npc según el valor de la variable estado:

  • 0 : parado.
  • 1 : movimiento horizontal.
  • 2 : movimiento vertical.
  • 3 : movimiento giro derecha. Camina en una misma dirección y cuando colisiona gira a la derecha.
  • 4 : movimiento giro izquierda. Camina en una misma dirección y cuando colisiona gira a la izquierda.
  • 5 : movimiento aleatorio. Camina en una misma dirección y cuando colisiona gira de forma aleatoria, y además cada un determinado tiempo tiene la posibilidad de cambiar de dirección.
Aqui tienen un video donde se muestran todos los tipos de movimiento que se van a crear en este tutorial.



void npc::actualiza()
{
    // para indicar que se ejecuta dos veces 
    int num = FRAME_RATE / 6; 

    if ( tiempo_total % num == 0 )
    {
        if ( estado != 0 )
        { 
           animacion++;
           if ( animacion > 2 ) animacion = 0;
        }   
        
        switch ( estado ) 
        {           
        case 1: // camina horizontal
             ax = x;
             ay = y;        
             if ( direccion == 1 )
             {
                  // camina izquierda
                  x-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = 2;
                  }
             }
             if ( direccion == 2 )
             {                  
                  // camina derecha
                  x+=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = 1;
                  }
             } 
             
             if ( ax != x )
             {             
                  // 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);
          
             }    
             
              // 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);
                                                                                  
             break;
        case 2: // camina vertical
             ax = x;
             ay = y;        
             if ( direccion == 0 )
             {
                  // camina abajo
                  y+=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       y = ay;
                       direccion = 3;
                  }
             }
             if ( direccion == 3 )
             {                  
                  // camina arriba
                  y-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       y = ay;
                       direccion = 0;
                  }
             } 
             
             if ( ay != y )
             {
              
                  // 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);
          
             }    
             
              // 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);

             break;
        case 3: // camina giro derecha
             ax = x;
             ay = y;  
             if ( direccion == 0 )
             {
                  // camina abajo
                  y+=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       y = ay;
                       direccion = 1;
                  }
             }
             if ( direccion == 1 )
             {                  
                  // camina izquierda
                  x-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = 3;
                  }
             } 
             if ( direccion == 2 )
             {
                  // camina derecha
                  x+=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = 0;
                  }
             }
             if ( direccion == 3 )
             {                  
                  // camina arriba
                  y-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       y = ay;
                       direccion = 2;
                  }
             }        
             if ( ax != x || ay != y )
             {
                  // se ha movido en una de las direcciones
                  
                  // 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);    
             }    
             
              // 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);
    
         break;
         case 4: // camina giro izquierda
             ax = x;
             ay = y;  
             if ( direccion == 0 )
             {
                  // camina abajo
                  y+=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       y = ay;
                       direccion = 2;
                  }
             }
             if ( direccion == 1 )
             {                  
                  // camina izquierda
                  x-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = 0;
                  }
             } 
             if ( direccion == 2 )
             {
                  // camina derecha
                  x+=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = 3;
                  }
             }
             if ( direccion == 3 )
             {                  
                  // camina arriba
                  y-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       y = ay;
                       direccion = 1;
                  }
             }        
             if ( ax != x || ay != y )
             {
                  // se ha movido en una de las direcciones
                  
                  // 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);
          
             }    
             
              // 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);
                                         
             break; 
        case 5: // camina libre
             if ( tiempo_total % 200 == 0 )
             {
                  direccion = rand()%4;
             }    
             ax = x;
             ay = y;               
             if ( direccion == 0 )
             {
                      // camina abajo
                      y+=desplazamiento;
                      if ( chocanpc() )
                      {
                           // posicion no valida
                           y = ay;
                           direccion = rand()%4;
                      }
                 }
             if ( direccion == 1 )
             {                  
                  // camina izquierda
                  x-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = rand()%4;
                  }
             } 
             if ( direccion == 2 )
             {
                  // camina derecha
                  x+=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       x = ax;
                       direccion = rand()%4;
                  }
             }
             if ( direccion == 3 )
             {                  
                  // camina arriba
                  y-=desplazamiento;
                  if ( chocanpc() )
                  {
                       // posicion no valida
                       y = ay;
                       direccion = rand()%4;
                  }
                  
             }
             
             if ( ax != x || ay != y )
             {
                  // se ha movido en una de las direcciones
                  
                  // 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); 
             }    
             
              // 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);
           
             break;
        default: // parado
             if ( tiempo_total % 300 == 0 )
             {
                  direccion = rand()%4;
             }        
             // pinta el nuevo choque
                  rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000);             
             
              // restaura fondo anterior antes de pintar la nueva imagen  
              blit( mifondo, fondo, 0,0, x,y, 32,32);  
            
              // obtiene una copia del nuevo fondo que va a ser ocupado
               blit( fondo, mifondo, x,y,0,0,32,32);
        
             break;
        }
    
    }     
}

Según la variable estado se define el tipo de movimiento. Tanto en el movimiento vertical como horizontal, tiene un pequeño inconveniente ya que no se definen dos direcciones, en el de horizontal no se tiene en cuenta las direcciones 0 y 3, de igual modo en la vertical no se tiene en cuenta las direcciones 1 y 2. Esto puede provocar que el NPC no se mueva si a la función de crear el NPC recibe como parámetro de dirección por ejemplo arriba o abajo, si le decimos que tiene movimiento horizontal. O también si se pone la dirección derecha o izquierda y se le ha asignado como movimiento el vertical.

Este código escrito anteriormente se puede mejorar, si os fijáis existe algunas lineas que se repiten mucho. Esto da a entender que hay cosas que no están donde deberían. Esta mejora se pondrá mas adelante.

Se modifica la función pinta(), ahora se añade el control del fondo donde se va a pintar el NPC y la llamada a la función actualiza().

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

En el archivo mijuego.h, se añade una nueva funcion llamada carga_escenario(), que se encargará de cargar las imagenes del escenario segun la variable lugar. Tambien se indicará si el escenario tiene scroll y se pondrá la musica de cada escenario.

// carga los datos del escenario segun lugar
void carga_escenario()
{
    jugador.cambia_escenario(); 
    switch ( lugar ) 
    {           
    case 1:// casa
              fondo  = (BITMAP *)datosjuego[dicasa].dat;
              choque = (BITMAP *)datosjuego[dicasachoque].dat;
              cielo  = (BITMAP *)datosjuego[dicasasup].dat;  
              
              desplaza = false;
              
              musica_casa();  
         break;
         
    case 2:// bosque
              fondo  = (BITMAP *)datosjuego[dibosque].dat;
              choque = (BITMAP *)datosjuego[dibosquechoque].dat;
              cielo  = (BITMAP *)datosjuego[dibosquesup].dat;              

              desplaza=true;     
              
              sonido_ambiente();
              musica_bosque();
         break;
         
    case 3:// ciudad
              fondo  = (BITMAP *)datosjuego[dicity1].dat;
              choque = (BITMAP *)datosjuego[dicity1choque].dat;
              cielo  = (BITMAP *)datosjuego[dicity1sup].dat;              

              desplaza=true;   

              musica_ciudad1();    
         break;
                           
    }     
}

Aquí se ve facilmente cuantos escenarios tiene el juego y facilita el querer añadir nuevos.
Al inicio de esta función se realiza una llamada a la función del jugador cambia_escenario(), para que borre su rectángulo de choque del escenario anterior.

En la función actualiza_juego(), se cambia la parte en la que se comprueba el lugar y se hace uso de la nueva función carga_escenario(), quedando el código de la siguiente forma:

    switch ( lugar ) 
    {           
    case 1:   // casa
         if ( cambio == 1 )
         {
              // cambiamos a otro lugar  
              // bosque             
              lugar = 2;
              carga_escenario();
         
              jugador.posiciona( 410,370 ); 
              desplazamiento_map_x=0;
              desplazamiento_map_y=160; 

              cambio = 0;

           
         }
         break;
    case 2:   // bosque
         if ( cambio == 2 )
         {
              // cambiamos a otro lugar
              // casa
              lugar = 1;
              carga_escenario();

              // situamos al prota dentro de la casa
              jugador.posiciona( 290,440 ); 
              desplazamiento_map_x=-160;
              desplazamiento_map_y=-160;  

              para_sonido_ambiente();   

              cambio = 0;
            
         }
         if ( cambio == 3 )
         {
              // cambiamos a otro lugar
              // ciudad
              lugar = 3;
              carga_escenario();

              // situamos al prota dentro de la casa
              jugador.posiciona( 500,540 ); 
              desplazamiento_map_x=950;
              desplazamiento_map_y=510;  

              para_sonido_ambiente();   

              cambio = 0;   
         }         
         break;  
    case 3:   // ciudad
         if ( cambio == 1 )
         {
              // cambiamos a otro lugar
              // bosque             
              lugar = 2;
              carga_escenario();
            
              jugador.posiciona( 650,30 ); 
              desplazamiento_map_x=200;
              desplazamiento_map_y=0; 

              cambio = 0;
         }
         break;          
    }               

En esta función se ha dejado el posicionamiento del personaje, ya que habrá mapas al cual se podrá acceder desde varios lugares. Debido a esto también se a mantenido las variables de desplazamiento_map ya que según la posición del personaje, se tiene que situar la pantalla.

Ya solo queda volver a carga_juego(), y cambiar las lineas que crean a los NPC.

    npersonaje = 2;
    
    personajes[0].crea( (BITMAP *)datosjuego[diper004].dat, 160,120, 2,4,1); 
    personajes[1].crea( (BITMAP *)datosjuego[diper002].dat, 50, 200, 3,5,1);

De esta forma se crean dos npc dentro de la casa, uno que se moverá con giro a izquierda, y el segundo personaje se moverá de una forma aleatoria.

Si quieren ver como se hace para añadir un NPC mas, vean el siguiente video.




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

1 comentario:

  1. disculpa como hago para ver las capas del juego como lo haces ??

    ResponderEliminar