miércoles, 16 de septiembre de 2015

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


Continuamos con el curso de crea tu juego RPG en C++ y Allegro. Hasta el momento se tiene hecho que el jugador pueda explorar por tres escenarios, aunque algunos de ellos son grandes estan algo vacios, me refiero a que no hay nadie, para arreglar esto en este curso se va a mostrar como hacer un NPC.





Que son los NPC ?

En un juego un NPC es un personaje no jugador ( Non-Player Character )

Estos NPC, pueden servir para simplemente decorar los escenarios, o para que nos den pistas o misiones.


Requisitos para el curso

Para realizar el curso debe tener el codigo fuente de todo lo anterior y el fichero DAT que contiene todo el material multimedia ( imagenes, sonidos, etc ). Para asegurar que se parte con el mismo material se deja los siguientes archivos:

Archivo RAR con el Codigo Fuente del proyecto 5,78 KBdescargar
Archivo RAR del Fichero DAT14,10 MBdescargar


Objetivo del curso

Se desea crear una clase que sirva para poner un NPC por pantalla. Inicialmente un NPC simple que no haga nada, donde le indicamos la posición en el mapa donde se quiere que aparezca y la dirección hacia donde mira este NPC.


Programación

Para este curso añadimos un nuevo archivo, que llamamos npc.h, que contendrá el siguiente código.

// npc.h

/*
    la posicion x,y es una posicion directa al mapa, por tanto debe pintarse
    directamente en el fondo, y no en el buffer
*/

class npc {
      // posicion
      int x,y;
      int ax,ay;
      
      int direccion;
      
      int animacion; 
      
      int escena;
      
      int estado;
      
      BITMAP* imagen;      
      
    public: 
             
      void crea( BITMAP *_img, int _x, int _y, int dir, int _estado, int _lugar ); 
      void pinta();   
}; 


void npc::crea( BITMAP *_img, int _x, int _y, int dir, int _estado, int _lugar )
{
     x = _x;
     y = _y;
     direccion = dir;
     animacion = 0;
     escena = _lugar;
     
     imagen = create_bitmap(_img->w, _img->h);
       
     blit( _img, imagen, 0,0, 0,0, _img->w, _img->h);
     
     // NPC parado
     estado = _estado;     
}


void npc::pinta()
{
     if ( lugar == escena )
     {
          // pinta el nuevo choque
          rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000);
          masked_blit(imagen, fondo, animacion*32, direccion*32, x, y, 32,32);
     }  
}


Se declara una clase llamada npc. Esta clase tiene varias variables privadas:

  • x,y : indica la posición actual del NPC
  • ax,ay : indica la posición anterior
  • direccion : indica hacia donde esta mirando, tiene un valor de 0 a 3.
  • animacion: indica el numero de la animación, tiene un valor de 0 a 2.
  • escena: almacena el valor de la variable global lugar, e indica en que escenario debe mostrarse.
  • estado: indica si esta parado, andando, o etc.
  • imagen: guarda la imagen del NPC
Tiene dos funciones públicas, crear y pinta.
crear(): se encarga de inicializar todas las variables, y recibe como parámetros la imagen que tendrá el NPC, la posición x,y donde se situará el personaje dentro del mapa, dirección hacia donde mirará el personaje y el estado, en este caso solo tenemos un estado que es parado y por ello no se tendrá encuenta.
pinta(): Cuando el jugador se encuentre en el escenario correspondiente se muestra el NPC. Tambien pinta un rectangulo rojo en la capa de choque, para que el jugador no pueda atravesar al NPC.


Recuerda de poner en el archivo main.cpp que se incluya el nuevo archivo con el comando #include "npc.h", este debe ser llamado antes de incluir mijuego.h.

En el arcivo global.h se han pasado del archivo mijuego.h varias variables globales

BITMAP *fondo;
BITMAP *choque;
BITMAP *cielo;

// indicará en que lugar estamos
// 1: casa
// 2: bosque
// 3: ciudad
int lugar;

int desplazamiento_map_x;
int desplazamiento_map_y;

Y se ha añadido una nueva variable que se encargará de contar el total de ticks que realiza el programa.

volatile unsigned int tiempo_total = 0;

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

Se ha cambiado el desplazamiento del jugador, ahora depende de los frames, para que aunque se aumente el FRAME_RATE se recorra el mismo espacio en el mismo tiempo. Sin este cambio cuando se aumentaba el FRAME_RATE el personaje corre mas.

// es el espacio en pixel que recorre el jugador al andar
const int desplazamiento= 120 / FRAME_RATE;

Quedando de el archivo de esta forma:

/* 
  Name:     Curso RPG
  Author:   Yadok - KODAYGAMES
  Date:     27/08/15 14:49
  Web:      http://devcpp-allegro.blogspot.com/
  Description: 
         Creacion de un juego al estilo RPG
         mas informacion en la web      
  Version: 10
  
*/
  
// clave del fichero datafile
char evalc[19]="cursoRPGkodaygames";

DATAFILE *datosjuego;
 
// Ancho y alto de la pantalla
const int PANTALLA_ANCHO = 800;
const int PANTALLA_ALTO  = 600;

// En este BITMAP dibujaremos todo
BITMAP *buffer;


// Copiar el buffer a la pantalla del juego (screen)
void pintar_pantalla()
{
    blit(buffer, screen, 0, 0, 0, 0, PANTALLA_ANCHO, PANTALLA_ALTO);
}

// controla el bucle principal
bool salir;


int cambio;

// indicará en que lugar estamos
// 1: casa
// 2: bosque
// 3: ciudad
int lugar;

int desplazamiento_map_x;
int desplazamiento_map_y;

BITMAP *fondo;
BITMAP *choque;
BITMAP *cielo;


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

const int FRAME_RATE =30;

// es el espacio en pixel que recorre el jugador al andar
const int desplazamiento= 120 / FRAME_RATE;

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


En el archivo main.cpp 


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

En el archivo mijuego.h Se eliminan las lineas que se han pasado al global, y se añaden dos nuevas variables, quedando de la siguiente forma
player jugador;

npc personajes[30];
int npersonaje;

bool desplaza;

npc personajes[30]; Sirve para guardar un maximo de 30 personajes de la clase NPC, o lo que es lo mismo, que se podrá mostrar un maximo de 30 npc. y la variable npersonaje se encarga de indicar cuantos estan activos, por tanto tendrá un valor entre 0 y 29.

En la función carga_juego() se inicializa la variable cambio a cero, y se indica cuantos NPC se van a crear, en este ejemplo seran 2.

    desplazamiento_map_x=-160;
    desplazamiento_map_y=-160;     
    desplaza = false;

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

Las variables desplazamiento_map se utilizan para colocar el mapa con respecto a la pantalla.

En la funcion actualiza_juego() Se elimina la linea que inicializa la variable cambio=0. Tambien se elimina la parte en la que comprueba la colision del jugador con el mapa.

    // comprobar si colisiona con el mapa
    bool choca = false;
    int px = jugador.getx();
    int py = jugador.gety()+16;  
    
     
    if ( lugar == 1)
    {
         px = jugador.getx()-160;
         py = jugador.gety()-160+16;
    }
    if (lugar == 2 || lugar == 3)
    {
         px = px + desplazamiento_map_x;     
         py = py + desplazamiento_map_y;     
    }
      
    for ( int ci=2; ci < 30; ci++)
    {
        for (int cj=0; cj < 16; cj++)
        {

            // color rojo
            if ( getpixel( choque, px+ci, py+cj) == 0xff0000 ){
                 choca = true;
            }
            // color verde
            if ( getpixel( choque, px+ci, py+cj) == 0x00ff00 ) cambio = 1;
            // color azul
            if ( getpixel( choque, px+ci, py+cj) == 0x0000ff ) cambio = 2;
            // color amarillo
            if ( getpixel( choque, px+ci, py+cj) == 0xffff00 ) cambio = 3;
            
            
        }
    }    
    if ( choca ){
         // vuelve al estado anterior
         jugador.posiciona( ax,ay );
    }
    
Todo este código anterior es el que se debe de quitar de la función actualiza_juego(), ya que el control de choque del personaje se va a poner en la clase player.

En la función pinta_juego() justo antes de donde se pinta el fondo al buffer, se hace un bucle para que pinte a todos los NPC
    for ( int z=0; z < npersonaje; z++ )
    {
         personajes[z].pinta();
    }    

    blit( fondo, buffer, ax, ay, bx, by, ancho, alto); 

En el archivo player.h se añade la función de colisión, en la función de teclado().
       if ( ax != x || ay != y )
      {
           if ( choca() )
           {
                x =ax;
                y =ay;
           }else{    
               int mx = desplazamiento_map_x;
               int my = desplazamiento_map_y;
               
               rectfill( choque, ax+4+mx, ay+17+my, ax+28+mx, ay+30+my, 0x000000);
               
               // control choque para npcs
               rectfill( choque, x+4+mx, y+17+my, x+28+mx, y+30+my, 0xffffff);    
   
               int num = FRAME_RATE / 12; 
               if ( tiempo_total % num == 0 )
               {
                   sonido_pasos(); 
                   // entra si a cambiado alguna de las variables x,y
                   animacion++;
                   if ( animacion > 2 ) animacion = 0;
               } 
           }
      }                 

En este código anterior se añadió el comando rectfill, para pintar un rectángulo blanco sobre la imagen choque que servirá para controlar por donde va el jugador, y así pueda colisionar con los NPC. Primero se pinta un rectángulo en negro para borrar el anterior, y luego se pinta otro en blanco en la nueva posición del jugador. También se debe añadir la nueva función choca(), con respecto a la anterior esta cambia con una primera condición y es que comprueba que el pixel no sea negro ni blanco, en ese caso entra y considera que ha colisionado y comprueba de que color se trata, de esta forma ahora te colisionas con todos los colores menos el negro y el blanco.
bool player::choca()
{
    int mx = x+desplazamiento_map_x;
    int my = y+desplazamiento_map_y;     

    bool resp=false;
    for (int i=2; i < 30; i++ )
    {
        for (int j=16; j < 32; j++)
        {

            if ( getpixel ( choque, mx+i, my+j) != 0x000000 &&
                 getpixel ( choque, mx+i, my+j) != 0xffffff )
            {
                // si el color no es negro
                resp = true;
                // color verde
                if ( getpixel( choque, mx+i, my+j) == 0x00ff00 ) cambio = 1;
                // color azul
                if ( getpixel( choque, mx+i, my+j) == 0x0000ff ) cambio = 2;
                // color amarillo
                if ( getpixel( choque, mx+i, my+j) == 0xffff00 ) cambio = 3;                 
            }            
                    
        } 
    } 
    return resp;
}

Recordar de que hay que añadir la declaración de la función dentro de las funciones públicas de la clase player.
class player
{
    BITMAP *prota;  
 int x,y;
 int direccion;
 int animacion; 
          
    public:  
       void inicia();    
       void pinta(); 
       void teclado(); 
       bool choca();
       int getx(){ return x; };
       int gety(){ return y; };
       void posiciona( int _x, int _y);
};

Este curso se divide en varias partes ya que para incluir el funcionamiento de los NPC de una forma eficiente se deben de cambiar algunas cosas que ya estaban hechas. Estos cambios se explicaran en la próxima entrega. Esta entrega acaba aquí mostrando dos NPC en la casa.
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

6 comentarios:

  1. buenas eh tenido unos problemas con estos codigos nose si puedas subir un rar Gracias

    ResponderEliminar
  2. buenas, explica tu problema en el foro e intentaré ayudarte para solucionarlo.
    http://creatusjuegosdecero.webege.com/index.php

    ResponderEliminar
  3. Una pregunta, que necesidad hay de inicializar al npc poniendo la función crea(), cuando perfectamente puede ser hecho con un constructor de clase? Y además, no hay necesidad de hacer un arreglo fijo de 20 npc's, se puede usar el tipo std::list incluido en la librería para hacerlo mas flexible, ya que la lista no es limitada y al tener la propiedad list.size() no sé necesita la variable npersonaje. Gracias

    ResponderEliminar
    Respuestas
    1. Creo recordar que tenía algún problema al inicializar la variable del tipo BITMAP.
      Yo lo hice así quizás porque me resultó mas fácil en su momento, no quiere decir que sea la única forma de hacerlo
      y mucho menos que sea la mejor. De tu forma como bien dices será más flexible pues no tendrás limitado el numero de npc.

      Eliminar
  4. Tengo un problema, y es que cuando salgo de la casa, todo está bien, pero luego al volver a entrar, no me puedo mover a ninguna parte, solo cambia la animación pero el personaje no sé mueve. Por qué pasa esto? Será porque al entrar en la casa caigo en una zona pintada de amarillo en choque?

    ResponderEliminar
    Respuestas
    1. La forma de posicionar al personaje son diferentes:

      - un escenario pequeño, como es dentro de la casa. Según donde esta posicionada en pantalla la posición será:
      px = jugador.getx()-160;
      py = jugador.gety()-160+16;
      - un escenario grande, como es el bosque. La imagen es mas grande que la pantalla y por tanto tiene desplazamiento y la posición será:
      px = px + desplazamiento_map_x;
      py = py + desplazamiento_map_y;

      Es muy probable que tu personaje esta en una posición bloqueada, por ello no te deja moverte.

      Eliminar

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...