viernes, 25 de septiembre de 2015

Crear juego RPG en C++ y Allegro 4 (14) Dialogos II

Aqui esta una nueva entrega del curso Crea tu juego RPG en C++ y Allegro, seguimos con los diálogos.


NOTA IMPORTANTE: 
Para realizar esta entrega es necesario tener hecho los anteriores, 
ya que solo se hace referencia a cambios en el código.



Objetivo del curso

En este curso, se creará una nueva clase que se encargue de los diálogos del juego. Siguiendo el estilo de juego RPG, cuando se muestre el texto por pantalla se hará de forma pausada para que de tiempo a leerlo y cuando el usuario pulse una tecla, salte al siguiente texto. Continuando con lo anterior se añadirá algunos dialogos a los NPC.


Programación

Se irá comentando los cambios que se deben hacer para añadir los dialogos al programa.

En el archivo global.h se añade lo siguiente:

int hablando;

Esta variable se utilizará para controlar cuando se esta mostrando un cuadro de dialogo.

En el archivo main.cpp se añade el comando para incluir la nueva libreria dialogos.h, quedando de la siguiente forma:

#include < allegro.h > 
#include "global.h"
#include "datosjuego.h"
#include "audio.h"
#include "dialogos.h"
#include "players.h"
#include "npc.h"
#include "mijuego.h"

En el archivo players.h se añade una nueva variable y cuatro funciones a la clase player. Para controlar cuando el jugador esta hablando.

class player
{
    BITMAP *prota;  
 int x,y;
 int direccion;
 int animacion;
    bool hablar; 
          
    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; };
};

La funcion accion() devuelve true en el caso de que se pulse una de las teclas de accion. Las teclas de accion definidas para el ejemplo son return e intro.

bool player::accion()
{
     return key[KEY_ENTER] || key[KEY_ENTER_PAD] ;
}

void player::habla()
{
     hablar = true;
}


void player::no_habla()
{
     hablar = false;
}

En la función teclado(), se añade una condición para que cuando este hablando no se pueda mover el jugador.

void player::teclado()
{     
      int ax = x;
      int ay = y;
      
         if ( !hablar )   
         {  
              // 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;
              } 
        
        } 
...

El resto del codigo de esta función se mantiene igual.

En el archivo mijuego.h se han realizado varios cambios para facilitar la programación mas adelante, por ello primero se va a comentar los cambios que no tienen que ver con la programación de los dialogos.

Se crea una nueva función llamada scroll_escenario(), esta contiene todo lo referente al scroll que estaba en la funcion de actualiza_juego().

void scroll_escenario()
{
  
    int ax,ay;
    
    ax = jugador.getx();
    ay = jugador.gety(); 
     
    if ( desplaza )
    {
         int d = desplazamiento / 2;
         // controla el desplazamiento del mapa si esta en los bordes
         if ( ax < scroll_rango1 && desplazamiento_map_x > 0 )
         {                
              desplazamiento_map_x-=d;
              jugador.posiciona(ax+d,ay);
              ax = jugador.getx();
              ay = jugador.gety();              
              if ( ax < scroll_rango2 && desplazamiento_map_x > 0  )
              {
                  desplazamiento_map_x-=d;
                  jugador.posiciona(ax+d,ay); 
                 ax = jugador.getx();
                 ay = jugador.gety();                                    
              }
         }
         if ( ay < scroll_rango1 && desplazamiento_map_y > 0 )
         { 
              desplazamiento_map_y-=d;
              jugador.posiciona(ax,ay+d);
              ax = jugador.getx();
              ay = jugador.gety();                
              if ( ay < scroll_rango2 && desplazamiento_map_y > 0 )
             { 
                  desplazamiento_map_y-=d;
                  jugador.posiciona(ax,ay+d); 
                 ax = jugador.getx();
                 ay = jugador.gety();                                   
             }
         }
         if ( ax > PANTALLA_ANCHO-scroll_rango1 && desplazamiento_map_x < fondo->w-PANTALLA_ANCHO )
         {
              desplazamiento_map_x+=d;
              jugador.posiciona(ax-d,ay);
              ax = jugador.getx();
              ay = jugador.gety();                
              if ( ax > PANTALLA_ANCHO-scroll_rango2 && desplazamiento_map_x < fondo->w-PANTALLA_ANCHO  )
              {
                  desplazamiento_map_x+=d;
                  jugador.posiciona(ax-d,ay);
                 ax = jugador.getx();
                 ay = jugador.gety();                                     
              }              
         }          
         if ( ay > PANTALLA_ALTO-scroll_rango1 && desplazamiento_map_y < fondo->h-PANTALLA_ALTO )
         {
              desplazamiento_map_y+=d;
              jugador.posiciona(ax,ay-d);
              ax = jugador.getx();
              ay = jugador.gety();                
              if ( ay > PANTALLA_ALTO-scroll_rango2 && desplazamiento_map_y < fondo->h-PANTALLA_ALTO )
              {
                  desplazamiento_map_y+=d;
                  jugador.posiciona(ax,ay-d);
                 ax = jugador.getx();
                 ay = jugador.gety();                  
              }              
         }
                         
    }   
               
}



Se crea una nueva función llamada cambia_escenario(), esta contiene todo lo referente al cambio de escenario que se encontraba dentro de la función de actualiza_juego().

void cambia_escenario()
{
    
    switch ( lugar ) 
    {           
    case 1:   // casa
         if ( cambio == 1 )
         {
              // cambiamos a otro lugar  
              // bosque             
              lugar = 2;
              carga_escenario();
              // situamos al prota dentro de la casa
              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 cerca de la puerta
              jugador.posiciona( 290,440 ); 
              desplazamiento_map_x=-160;
              desplazamiento_map_y=-160;  
              sonido_abrirpuerta(); 
              para_sonido_ambiente();   
              cambio = 0;
            
         }
         if ( cambio == 3 )
         {
              // cambiamos a otro lugar
              // ciudad
              lugar = 3;
              carga_escenario();
              // situamos al prota en el camino 
              jugador.posiciona( 500,540 ); 
              desplazamiento_map_x=950;
              desplazamiento_map_y=508;   
              para_sonido_ambiente();   
              cambio = 0;   
         }         
         break;  
    case 3:   // ciudad
         if ( cambio == 1 )
         {
              // cambiamos a otro lugar
              // bosque             
              lugar = 2;
              carga_escenario();     
              // situamos al prota en el camino del bosque        
              jugador.posiciona( 650,30 ); 
              desplazamiento_map_x=200;
              desplazamiento_map_y=0; 
              cambio = 0;
         }
         break;          
    default:
         break;
    }       
}



Se crea una nueva función llamada evento_escenario(), que se encarga de controlar las posibles acciones que se pueden realizar en cada escenario, entre esos eventos se encuentra los dialogos de los NPC.

void evento_escenario()
{
    int pzx = jugador.getx() + desplazamiento_map_x;
    int pzy = jugador.gety() + desplazamiento_map_y;     
    switch ( lugar ) 
    {           
    case 1:// casa  
         break;
    case 2:   // bosque  
         break;  
    case 3:   // ciudad 
    
    
         if ( personajes[0].posicion_cerca(pzx,pzy) 
               && jugador.accion() && !jugador.hablando() )
         {
              dialogo.cambia_texto(" Dejame!! estoy ocupadooooo!! ");
              hablando = 1;              
         }  

         if ( personajes[4].posicion_cerca(pzx,pzy) 
               && jugador.accion() && !jugador.hablando() )
         {
              dialogo.cambia_texto(" Aparta!!, no tengo tiempo para hablar con pueblerinos. Tengo que seguir con mi ronda de vigilancia. " );
              hablando = 1;              
         }        
         
         if ( personajes[5].posicion_cerca(pzx,pzy) 
               && jugador.accion() && !jugador.hablando() )
         {
              dialogo.cambia_texto(" Soy la reina de los mares!! .. paseando por la calle voy ^_^ " );
              hablando = 1;              
         }           
    

         if ( personajes[6].posicion_cerca(pzx,pzy) 
               && jugador.accion() && !jugador.hablando() )
         {
              dialogo.cambia_texto(" Me han dicho que han visto un goblin merodeando por el bosque, debes tener cuidado cuando vuelvas a tu casa." );
              hablando = 1;              
         }

         if ( hablando == 1 && !jugador.accion() )
         {
              hablando = 2;
              jugador.habla();
         }

         // obliga a esperar minimo 1 segundo
         if ( hablando > FRAME_RATE && jugador.accion() ){
              hablando = 0;
         }    
         
         if ( hablando == 0 && !jugador.accion() && jugador.hablando() )
         {
              jugador.no_habla();
         }
         
         break;                   
    default:
         break;
    }        
}



Dejando la funcíon actualiza_juego(), mas facil de entender.

// actualiza el estado del juego
void actualiza_juego()
{

    scroll_escenario();
    
    jugador.teclado(); 
    
    evento_escenario();
             
    cambia_escenario();         
}


En la función carga_juego(), se añade las siguientes lineas para añadir a un total de ocho NPC por todo el mapa de la ciudad, y se crean dos cuadros de dialogos, en uno de ellos se muestra un texto fijo y en el otro se mostrará los textos de los dialogos con los NPC.

    npersonaje = 8;
    
    personajes[0].crea( (BITMAP *)datosjuego[diper001].dat, 1300,700, 1,1,3); 
    personajes[1].crea( (BITMAP *)datosjuego[diper005].dat, 280, 450, 0,2,3);
    personajes[2].crea( (BITMAP *)datosjuego[diper005].dat, 230, 280, 3,2,3);
    personajes[3].crea( (BITMAP *)datosjuego[diper003].dat, 960, 310, 2,3,3);
    personajes[4].crea( (BITMAP *)datosjuego[diper005].dat, 1120, 450, 0,4,3);
    personajes[5].crea( (BITMAP *)datosjuego[diper004].dat, 900, 650, 1,5,3);
    personajes[6].crea( (BITMAP *)datosjuego[diper006].dat, 850, 800, 0,0,3);
    personajes[7].crea( (BITMAP *)datosjuego[diper001].dat, 530, 280, 1,5,3);
    
    texto.crea("Demo Dialogos. Ejemplo del Curso Crea tu juego RPG en C++ y Allegro ",
       font, 5,5,230,60 );
       
    dialogo.crea("", font, 10, PANTALLA_ALTO-100, PANTALLA_ANCHO-10, PANTALLA_ALTO-10);  
    hablando = 0; 

La variable texto y dialogo se deben de declarar como variable global dentro del archivo mijuego.h

MENSAJE texto, dialogo;

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


La variable texto, crea un cuadro de dialogo en la esquina superior izquierda donde se muestra el mensaje "Demo Dialogos. Ejemplo del Curso Crea tu juego RPG en C++ y Allegro". La variable dialogo crea un cuadro que ocupa todo el ancho de la parte de abajo de la pantalla, con una altura de 90 pixel, y que inicialmente no contiene ningun texto.

Las variables scroll_rango1 y scroll_rango2 son utilizadas para definir el rango de acción del scroll.

En la funcion pinta(), se ha añadido para que se muestren los dialogos.

    texto.pinta(buffer);
    
    if ( hablando > 1 )
    {
         dialogo.pinta(buffer);
         hablando++;
    } 

Estas lineas se añaden al final, despues de pintar del cielo.


En el archivo npc.h, se añade una nueva función a la clase NPC que se llama posicion_cerca().

      bool posicion_cerca(int _x, int _y);

Esta función se encarga de controlar si la posición pasada por parámetro esta cerca de la posición del NPC.

bool npc::posicion_cerca(int _x, int _y)
{
     int d = 32 + (desplazamiento*2);
     int d2 =abs ( _x - x ) + abs ( _y - y );
     return d2 <= d ;
}

La funcion posición_cerca() mediante los parametros que recibe, calcula dos números. La variable "d" contiene el valor de sumar el desplazamiento*2 mas 32, estos valores se deben a que nuestros personajes todos tienen una dimension de 32 pixels. Y el desplazamiento*2 es para que no tengas que estar totalmente pegado al npc, en este caso permitimos que estes a dos pasos de el. Es decir, "d" es la distancia máxima a la que se puede estar de un npc para poder hablar con el o interactuar.
La variable "d2" tiene el valor de abs( _x-x) + abs(_y-y). Esto significa que se calcula la distancia que hay entre ambos puntos, en cada uno de sus ejes y se suma. la función abs() transforma el valor que recibe por parametro a su valor absoluto, es decir, si recibe como parámetro un 3 o un -3 en ambos casos devuelve 3 ya que es como ignorar el signo. El valor de la variable "d2" tendra la suma de la distancia que hay en el eje x + la distancia en el eje y.

Con la condicion que hay en el "return  d2 <= d ", lo que se consigue es que sea verdadero siempre que d2 sea menor o igual que d, es decir, que la suma de las distancias de los dos ejes x,y sea menor o igual que la distancia máxima que se permite para interactuar con el NPC.  En la siguiente imagen vemos su rango de accion pintado en verde.


Recordar que el control de choque del jugador solo contempla la mitad inferior de la imagen del personaje, es por ello que existe mas variaciones de posiciones por abajo del NPC.


De esta forma se ha controlado que la distancia sea la correcta, pero no esta del todo bien resuelto la forma para interactuar con el NPC.


Segun vemos en la imagen anterior, el jugador esta dentro de la zona verde del NPC por tanto puede interactuar con el. Pero como se ve ninguno esta mirando hacia el NPC y por tanto no debería dejar de interactuar con algo que se tiene a la espalda. Se debe añadir para arreglar este fallo una función que nos diga si el jugador esta mirando hacia una posicion concreta, como es la del NPC, pero esto lo dejo para hacer en otra entrega.

Llegado a este punto, ya estará completo para poder compilar y tendras algo parecido a lo que se muestra en 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

2 comentarios:

  1. ¿Como se declara la variable FONT? ¿Como se utiliza este tipo de dato?

    ResponderEliminar
    Respuestas
    1. la variable font es una variable que esta declarada por allegro y contiene el tipo de letra por defecto. Para declarar tu propia fuente sería de la siguiente forma: FONT *mifuente;
      Mira el curso 14A explica como poner otra fuente.

      Eliminar

Related Posts Plugin for WordPress, Blogger...