Crea tu RPG en C++ y Allegro 4

September 17, 2017 | Author: Carlos Villamizar | Category: Frame Rate, C++, Computer File, Role Playing Games, Film Frame
Share Embed Donate


Short Description

Descripción: Creación de un juego en RPG con la librería gráfica de allegro versión 4...

Description

Crea tu RPG en C++ y Allegro Este es un curso realizado en el Dev-C++, esta programado en C++ y con la librería Allegro 4.4. En el curso se va enseñando poco a poco a montar un juego RPG, lo que se suele llamar como action RPG. De manera que se tenga una base para luego poder hacer uno mismo su propio juego. Para poder realizar el curso se recomienda que se tenga conocimientos básicos en programación y C++. Aunque se intentará explicar todos los comandos nuevos que se van utilizando.

Contenido del curso 1. Creando un personaje 2. Añadiendo animación 3. Programación modular 4. Control de FPS 5. Creando mi primer escenario 6. Control del Scroll 7. Añadiendo algunos efectos sonoros 8. Añadiendo música 9. Creacion de un fichero DAT 10. Añadiendo un segundo escenario 11. Creando un NPC 12. Movimiento de un NPC 13. Una nueva clase para los diálogos 14. Añadiendo diálogos al juego o Nueva fuente en los diálogos 15. Sistema de Lucha 16. Barra de vida 17. Sistema de Lucha II

18. Experiencia 19. Nivel de dificultad del juego 20. Inventario 21. Inventario II 22. Tiendas 23. Tiendas II 24. Tiendas III 25. Guardar Partida 26. Cargar Partida 27. Menu 28. Menu II 29. Control de errores

Debido a que cada vez que se añade una imagen, sonido, etc., se debe de modificar el fichero DAT, se hace algo pesado tener que descargarlo en cada entrega la versión correspondiente. Como este archivo se va manteniendo para todo el curso y se va ampliando según va avanzando el curso el tamaño de este archivo va aumentando mucho, he decidido facilitar el trabajo a todos aquellos que están haciendo el curso, poniendo aquí en esta página el ultimo fichero DAT. De esta manera aunque estés empezando los primeros temas, ya no tendrás que ir actualizandolo, solo tendrás que descargarlo una vez, siempre que no salga un nuevo tema que incluya algún fichero multimedia. Recordad que este fichero DAT se empieza a utilizar a partir del tema 9, ya que antes no se utiliza. Este fichero DAT es acumulativo, es decir, que el ultimo te vale para todo lo anterior.

Recursos del curso Tema - Contenido - Link

1 - Primer ejemplo - Descargar 4 - Ejemplo completo - Descargar 5 - Código programa - Descargar 5 - Imagenes - Descargar 7 - Archivo sonido Wav - Descargar 8 - Archivo música midi - Descargar

Para todos los temas superior al 8. Fichero DAT, versión válida hasta el tema 28. - Descargar Fichero DAT, versión válida hasta el tema 24. - Descargar Fichero DAT, versión válida hasta el tema 21. - Descargar Fichero DAT, versión válida hasta el tema 20. - Descargar Fichero DAT, versión válida hasta el tema 18. - Descargar Fichero DAT, versión valida hasta el tema 17. - Descargar Fichero DAT (archivos multimedia), versión válida hasta el tema 16. - Descargar A partir del tema 21 se utiliza también los archivos objetos. Fichero objetos DAT y TXT . valido para 21. - Descargar Fichero DAT datostienda tema 24 - Descargar Fichero DAT objetos tema 24 - Descargar Del tema 28, los archivos de los objetos y la tienda, que incluye objetos.dat, objetos.txt, datostienda.dat, tiendas.txt, list01.txt y list02.txt. Descargar

Crear juego RPG en C++ y Allegro 4 Aqui comienza el primer curso de crear tu juego, al estilo RPG maker, para ello se utiliza el lenguaje C++ y la librería Allegro Version 4.4. Cual es el estilo RPG maker, pues para que se entienda mejor mirar la siguiente imagen ...

En este primer tutorial, se hará el manejo del personaje principal. Para ello tendremos en cuenta: 1. Se utilizará para la imagen del protagonista un character set del RPG Maker. 2. Se utilizará las teclas del cursor para mover al personaje.

Que es un character set del RPG Maker ? Se trata de una imagen, donde viene dibujado al personaje según las cuatro direcciones, abajo, izquierda, derecha y arriba. Y cada una de ellas tiene tres variantes para hacer una pequeña animación. Para nuestro tutorial utilizaremos la siguiente imagen, pero si no os gusta o queréis utilizar otra podéis buscar mas en google.

Estas imágenes suelen venir en formato png, pero como el Allegro no trabaja con png, se debe transformar la imagen a BMP. Además para luego no tener problemas el color blanco de fondo se debe sustituir por el color rosa que es el que utiliza Allegro para las transparencia.

Esta imagen la guardaremos con el nombre de "personaje.bmp".

Programación A continuación se explica paso a paso el programa utilizado. allegro_init(); install_keyboard(); set_color_depth(32); set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0);

Se empieza inizializando la librería y definiendo el tamaño de la ventana que vamos a crear, en este ejemplo será de 800x600. BITMAP *buffer = create_bitmap(800, 600); BITMAP *prota = load_bmp("personaje.bmp",NULL); bool salir; int x,y; // inicializar vbles x = 10; y = 10; salir = false;

Se inicializan las variables que se van a manejar en el programa. Dos imagenes BITMAP, una de ellas contendrá la imagen del personaje (prota) y la otra la utilizaremos para montarlo todo antes de volcarlo a la pantalla ( buffer ). Las variables x,y contienen la posición del personaje, que inicialmente se le da el valor de 10,10 respectivamente. El bucle principal se encarga de pintar el fondo, comprobar si el usuario a pulsado alguna tecla para mover al personaje, pinta al personaje y finalmente lo muestra por pantalla. clear_to_color(buffer, 0xaaaaaa); masked_blit(prota, buffer, 0,0, x, y, 32,32);

// teclas control usuario if ( key[KEY_UP] ) { y--; } if ( key[KEY_DOWN] ) { y++; } if ( key[KEY_LEFT] ) { x--; } if ( key[KEY_RIGHT] ) { x++; } // if if if if

limites ( x < 0 ) ( x > 800 ( y < 0 ) ( y > 600

x ) y )

= x = y

0; = 800; 0; = 600;

blit(buffer, screen, 0, 0, 0, 0, 800, 600); rest(10); // tecla de salida if ( key[KEY_ESC] ) salir = true;

clear_to_color(buffer, 0xaaaaaa); Borra el contenido de buffer, rellenandolo de un color gris (0xaaaaaa).

masked_blit(prota, buffer, 0,0, x, y, 32,32); Pinta la imagen prota sobre la imagen buffer, en la posición x,y dentro de la imagen buffer. Con un tamaño de la imagen prota de 32x32. El apartado de teclas control usuario, se encarga de las teclas definidas para mover el personaje, para ello se modifican los valores de x,y. Siendo "x" para moverlo en horizontal y la "y" para moverlo en vertical, en este caso se utilizan las teclas del cursor. El apartado limites, se encarga de establecer los limites de las variables x,y , que dependen del tamaño de la ventana que en este caso son 800,600. De esta forma se evita que se salga del campo de vision la imagen prota. blit(buffer, screen, 0, 0, 0, 0, 800, 600); Vuelca el contenido de la imagen buffer sobre screen, que es la pantalla, con un tamaño que previamente sea definido, según el ejemplo es 800,600. Es justo en este momento en el que se muestra todo por pantalla. rest(10); Hace una pequeña pausa de 10 milisegundos. CODIGO COMPLETO DEL EJEMPLO ? 1 /* 2 Name: RPG 3 Author: Yadok - KODAYGAMES 4 Date: 27/08/15 14:49 5 Web: http://devcpp-allegro.blogspot.com/ 6 Description: 7 Creacion de un juego al estilo RPG 8 mas informacion en la web 9 */ 10 11 12#include 13 14int main()

15{ 16 allegro_init(); 17 install_keyboard(); 18 19 set_color_depth(32); 20 set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0); 21 22 BITMAP *buffer = create_bitmap(800, 600); 23 BITMAP *prota = load_bmp("personaje.bmp",NULL); 24 25 bool salir; 26 int x,y; 27 28 // inicializar vbles 29 x = 10; 30 y = 10; 31 salir = false; 32 33 while ( !salir ) 34 { 35 clear_to_color(buffer, 0xaaaaaa); 36 37 masked_blit(prota, buffer, 0,0, x, y, 32,32); 38 39 // teclas control usuario 40 if ( key[KEY_UP] ) 41 { 42 y--; 43 } 44 if ( key[KEY_DOWN] ) 45 { 46 y++; 47 } 48 if ( key[KEY_LEFT] ) 49 { 50 x--; 51 } 52 if ( key[KEY_RIGHT] ) 53 {

54 x++; 55 } 56 57 // limites 58 if ( x < 0 ) x = 0; 59 if ( x > 800 ) x = 800; 60 if ( y < 0 ) y = 0; 61 if ( y > 600 ) y = 600; 62 63 64 blit(buffer, screen, 0, 0, 0, 0, 800, 600); 65 66 rest(10); 67 68 // tecla de salida 69 if ( key[KEY_ESC] ) salir = true; 70 71 } 72 73 destroy_bitmap(prota); 74 destroy_bitmap(buffer); 75 76 return 0; 77} 78END_OF_MAIN();

Si todo esta correcto, se tendrá un programa que muestra un personaje por pantalla que se puede mover con las teclas del cursor.

Si deseas descargar todo lo que se utiliza en este ejemplo pulsa en el link

Crear juego RPG en C++ y Allegro 4 (2) Segunda entrega del curso, continuando con lo anterior en esta entrega vamos a poner animación a nuestro personaje, ya que aunque se puede desplazar por la pantalla la imagen del personaje no cambia.

Como anteriormente vimos, la imagen personaje.bmp contiene la animación y direcciones.

Según vemos en la imagen, empezando de arriba a abajo se tiene las siguientes direcciones: 1. 2. 3. 4.

Abajo Izquierda Derecha Arriba

El tamaño de uno de los personajes si se quisiera cojer solo es de 32x32 pixel. Para controlar la dirección y la animación se crean dos variables. La variable direccion contendrá cuatro posibles valores 0 - 3. La variable animacion contendrá tres posibles valores 0 - 2. También se crea una copia de (x,y) llamados (ax,ay) para guardar el valor anterior, y así comprobar si ha cambiado o no.

Programación Empezamos con los cambios en nuestro programa. Se declaran e inicializan las nuevas variables

int ax,ay; int direccion; int animacion; int desplazamiento; // inicializar vbles ax = 10; ay = 10; direccion = 0; animacion = 0; desplazamiento = 4;

Dentro del bucle principal al inicio se debe de dar valor a las variables (ax,ay) con sus respectivos valores.

ax = x; ay = y;

La parte de control del teclado de usuario cambia completamente por lo siguiente:

// 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; }

Y lo ultimo que se debe de cambiar y mas importante es la parte que se encarga de mostrar al personaje.

masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32);

Con este comando estamos indicando de que pinte en buffer una imagen de 32x32, que sera recortada de la imagen prota según las variables animacion y direccion. Y serán situadas en la posición x,y. Quedando todo el bucle principal de la siguiente manera:

? 1 while ( !salir ) 2 {

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

clear_to_color(buffer, 0xaaaaaa); masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32);

ax ay // if {

= x; = y; teclas control usuario ( 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; } // if if if if

limites ( x < 0 ) ( x > 800 ( y < 0 ) ( y > 600

x ) y )

= x = y

0; = 800; 0; = 600;

42 43 44 45 46 47 48 49 50

blit(buffer, screen, 0, 0, 0, 0, 800, 600); rest(60); // tecla de salida if ( key[KEY_ESC] ) salir = true; }

Si se fijan, ahora el valor de rest() es mayor debido a que en el pc en el que se ha probado iba muy rápido, por eso se aumentó el valor a 60, en el caso de ir algo lento pueden reducirlo, recuerda que el numero indica el tiempo de espera en milisegundos. Llegado a este punto, si todo esta correcto tendremos lo mismo que se muestra en el siguiente video.

Crear juego RPG en C++ y Allegro 4 (3) Una nueva entrega del tutorial para crear tu propio juego RPG en C++ y Allegro.

En este tutorial prepararemos el código programado, en algo mas modular y organizado para facilitar futuras ampliaciones. Para ello vamos a crear algunas funciones y alguna que otra clase. Si observamos el código que tenemos hasta el momento, vemos que la función main lo contiene todo, si siguiéramos programando de este modo llegaría un punto en el que sería muy difícil de encontrar un fallo, o algo que queramos modificar. Para evitar que perdamos el control debemos poner comentarios para facilitar la búsqueda de cada uno de los procesos que realiza el juego, además de crear funciones modulares que nos permita tener el código de forma ordenada. Ahora se añadirá un nuevo archivo al proyecto. Para los que tenga el Dev-C++ se colocará sobre el nombre de nuestro proyecto, pulsa con el boton derecho, y en el menú desplegable selecciona la opcion "Nuevo codigo fuente", para añadir otro archivo a nuestro proyecto el cual lo llamaremos "global.h"

En este archivo, se pondrá todas las variables que se quieran que sean global.

Que es una variable Global ? Una variable global puede ser modificada en cualquier parte del programa y cualquier parte del programa depende de ella. Es por ello que una variable global tiene un potencial ilimitado para crear dependencias. Sin embargo, en algunas ocasiones, las variables globales resultan muy útiles. Por ejemplo, se pueden usar para evitar tener que pasar variables usadas muy frecuentemente de forma continua entre diferentes subrutinas o funciones

Por tanto, en global.h pondremos lo siguiente: // GLOBAL.H // Ancho y alto de la pantalla const int PANTALLA_ANCHO = 800; const int PANTALLA_ALTO = 600;

// En este BITMAP dibujaremos todo BITMAP *buffer; // es el espacio en pixel que recorre el jugador al andar const int desplazamiento=4; // Copiar el buffer a la pantalla del juego (screen) void pintar_pantalla() { blit(buffer, screen, 0, 0, 0, 0, PANTALLA_ANCHO, PANTALLA_ALTO); }

Se crean dos variables PANTALLA_ANCHO y PANTALLA_ALTO para guardar el tamaño de la pantalla y así facilitar su uso. El BITMAP *buffer se pone de forma global pues será utilizado muchas veces y nos facilitará su uso. De igual forma se crea una función pintar_pantalla() para que sea mas legible nuestro código, esta función se encarga de volcar el contenido de buffer a la pantalla. Como tenemos ahora global.h, este fichero o librería debemos incluirlo en el código principal. Y esto se hace mediante el comando #include "global.h" En el archivo main.cpp, para que sea algo mas modular se separa lo que es la inicialización del Allegro y configuración de la resolución a una función llamada inicia_allegro(). 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); }

Se crea una clase llamada player que se encarga de todo lo referente al usuario o jugador, como comprobar las teclas, pintar el personaje, etc. ? 1 // Esta clase se encarga del manejo del jugador 2 class player 3 { 4 BITMAP *prota; 5 int x,y; 6 int direccion; 7 int animacion; 8 9 public: 10 player(); 11 void pinta(); 12 void teclado(); 13}; 14 15player::player() 16{ 17 prota = load_bmp("personaje.bmp",NULL); 18 // inicializar vbles 19 direccion = 0; 20 animacion = 0; 21 x = 10; 22 y = 10; 23} 24 25void player::pinta() 26{ 27 masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32); 28} 29 30void player::teclado() 31{ 32 int ax = x; 33 int ay = y; 34 // teclas control usuario

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67}

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; } // if if if if

limites ( x < 0 ) x = 0; ( x > PANTALLA_ANCHO ) x = PANTALLA_ANCHO; ( y < 0 ) y = 0; ( y > PANTALLA_ALTO ) y = PANTALLA_ALTO;

Esta clase si se quiere se puede poner en otro archivo aparte y de esta manera separamos todo lo referente al jugador. Y con todo esto se consigue tener una función principal mas corta y sencilla de entender.

? 1 // programa principal 2 int main() 3 { 4 inicia_allegro(); 5 6 player jugador; 7 bool salir; 8 9 salir = false; 10 11 while ( !salir ) 12 { 13 clear_to_color(buffer, 0xaaaaaa); 14 15 jugador.teclado(); 16 17 jugador.pinta(); 18 19 pintar_pantalla(); 20 21 rest(60); 22 23 // tecla de salida 24 if ( key[KEY_ESC] ) salir = true; 25 26 } 27 28 destroy_bitmap(buffer); 29 30 return 0; 31}

El

programa

sigue

haciendo

exactamente

lo

mismo

pero

internamente

para

seguir

programando

es

mas

fácil.

Recuerda añadir el include del nuevo archivo global.h al inicio, justamente debajo de la llamada a la libreria allegro, tal y como se muestra a continuación: ? 1#include 2#include "global.h"

En el caso que hayas decidido separar la clase player a otro archivo, deberás poner su correspondiente include, si por ejemplo se ha llamado el archivo player.h, debes poner #include "player.h"

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 pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación: http://creatusjuegosdecero.webege.com/index.php 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: ? 1 /* 2 Name: RPG 3 Author: Yadok - KODAYGAMES 4 Date: 27/08/15 5 Web: http://devcpp-allegro.blogspot.com/ 6 Description: 7 Creacion de un juego al estilo RPG 8 mas informacion en la web 9 Version: curso 4 10*/ 11 12 13#include < allegro .h > 14#include "global.h"

15#include "players.h" 16#include "mijuego.h" 17 18 19 20void inicia_allegro() 21{ 22 allegro_init(); 23 install_keyboard(); 24 25 set_color_depth(32); 26 set_gfx_mode(GFX_AUTODETECT_WINDOWED, PANTALLA_ANCHO, PANTALLA_ALTO, 0, 0); 27 28 buffer = create_bitmap(PANTALLA_ANCHO, PANTALLA_ALTO); 29 30 LOCK_VARIABLE(contador_tiempo_juego); 31 LOCK_FUNCTION(inc_contador_tiempo_juego); 32 33 // Iniciamos el limitador de FPS 34 install_int_ex(inc_contador_tiempo_juego, BPS_TO_TIMER( FRAME_RATE )); 35} 36 37 // programa principal 38int main() 39{ 40 inicia_allegro(); 41 42 carga_juego(); 43 44 salir = false; 45 46 47 while ( !salir ) 48 { 49 if ( contador_tiempo_juego ) 50 { 51 while ( contador_tiempo_juego ) 52 { 53 actualiza_juego();

54 55 contador_tiempo_juego--; 56 } 57 58 clear_to_color(buffer, 0x00000); 59 60 pinta_juego(); 61 62 pintar_pantalla(); 63 64 }else{ 65 66 rest(1); 67 } 68 // tecla de salida 69 if ( key[KEY_ESC] ) salir = true; 70 } 71 72 destroy_bitmap(buffer); 73 74 return 0; 75} 76END_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 ? 1 2 3 4 5 6 7 8

// players.h // Esta clase se encarga del manejo del jugador class player { BITMAP *prota; int x,y;

9 int direccion; 10 int animacion; 11 12 public: 13 void inicia(); 14 void pinta(); 15 void teclado(); 16 int getx(){ return x; }; 17 int gety(){ return y; }; 18 void posiciona( int _x, int _y); 19}; 20 21 22void player::inicia() 23{ 24 prota = load_bmp("personaje.bmp",NULL); 25 // inicializar vbles 26 direccion = 0; 27 animacion = 0; 28 x = 540; 29 y = 280; 30} 31 32 33void player::pinta() 34{ 35 masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32); 36} 37 38void player::teclado() 39{ 40 int ax = x; 41 int ay = y; 42 // teclas control usuario 43 if ( key[KEY_UP] ) 44 { 45 y-=desplazamiento; 46 direccion = 3; 47 }

48 if ( key[KEY_DOWN] ) 49 { 50 y+=desplazamiento; 51 direccion = 0; 52 } 53 if ( key[KEY_LEFT] ) 54 { 55 x-=desplazamiento; 56 direccion = 1; 57 } 58 if ( key[KEY_RIGHT] ) 59 { 60 x+=desplazamiento; 61 direccion = 2; 62 } 63 if ( ax != x || ay != y ) 64 { 65 // entra si a cambiado alguna de las variables x,y 66 animacion++; 67 if ( animacion > 2 ) animacion = 0; 68 } 69 70 // limites globales 71 if ( x < 0 ) x = 0; 72 if ( x > PANTALLA_ANCHO ) x = PANTALLA_ANCHO; 73 if ( y < 0 ) y = 0; 74 if ( y > PANTALLA_ALTO ) y = PANTALLA_ALTO; 75} 76 77void player::posiciona( int _x, int _y) 78{ 79 x=_x; 80 y=_y; 81}

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 ? 1 /* 2 mijuego.h 3 4 */ 5 6 BITMAP *fondo; 7 BITMAP *choque; 8 BITMAP *alto; 9 10player jugador; 11 12 13// carga todo lo necesario antes de empezar el juego 14void carga_juego() 15{ 16 jugador.inicia(); 17 // cargamos imagenes del primer escenario 18 fondo = load_bmp("casa.bmp",NULL); 19 choque = load_bmp("casa-choque.bmp",NULL); 20 alto = load_bmp("casa-sup.bmp",NULL); 21} 22 23 24// actualiza el estado del juego 25void actualiza_juego() 26{ 27 int ax,ay; 28 ax = jugador.getx(); 29 ay = jugador.gety(); 30 jugador.teclado(); 31 32 // comprobar si colisiona con el mapa 33 bool choca = false; 34 int px = jugador.getx()-160;

35 int py = jugador.gety()-160+16; 36 for ( int ci=0; ci < 32; ci++) 37 { 38 for (int cj=0; cj < 16; cj++) 39 { 40 41 if ( getpixel( choque, px+ci, py+cj) == 0xff0000 ){ 42 choca = true; 43 ci = 32; 44 cj = 16; 45 } 46 if ( getpixel( choque, px+ci, py+cj) == 0x00ff00 ) salir = true; 47 } 48 } 49 if ( choca ){ 50 // vuelve al estado anterior 51 jugador.posiciona( ax,ay ); 52 } 53 54} 55 56 57// Se encarga de pintar todo sobre el buffer 58void pinta_juego() 59{ 60 blit( fondo, buffer, 0,0, 160, 160, 480,325); 61 jugador.pinta(); 62 masked_blit( alto, buffer, 0,0, 160, 160, 480,325); 63}

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.

? 1install_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. ? 1 bool choca = false; 2 int px = jugador.getx()-160; 3 int py = jugador.gety()-160+16; 4 for ( int ci=0; ci < 32; ci++) 5 { 6 for (int cj=0; cj < 16; cj++) 7 { 8 9 if ( getpixel( choque, px+ci, py+cj) == 0xff0000 ){ 10 choca = true; 11 ci = 32; 12 cj = 16;

13 14 15 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++

Crear juego RPG en C++ y Allegro 4 (5) Escenarios

Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se hará que nuestro personaje pueda salir de la casa al exterior, y recorrer el bosque.

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 pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación: http://creatusjuegosdecero.webege.com/index.php

Para este curso necesitaremos tres imágenes del nuevo lugar, es decir, del bosque.

Uno con la imagen completa del nuevo escenario, una segunda imagen con las zonas de choque, y una tercera imagen con las cosas por las que el protagonista pasará por debajo, como son las copas de los arboles. Es importante que las tres imágenes tengan el mismo tamaño.

El mapa de choque se utilizará para indicar con el color rojo las zonas que no podemos pisar, y por tanto chocaremos, y también con otros colores indicaremos otras acciones. En el ejemplo se utilizará el color azul para detectar la puerta de la casa.

Programación ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

/* mijuego.h */ BITMAP BITMAP BITMAP BITMAP BITMAP BITMAP

*casa_a; *casa_b; *casa_c; *bosq_a; *bosq_b; *bosq_c;

BITMAP *fondo; BITMAP *choque; BITMAP *cielo; player jugador; // indicará en que lugar estamos // 1: casa // 2: bosque int lugar; // carga todo lo necesario antes de empezar el juego void carga_juego() { jugador.inicia(); casa_a = load_bmp("casa.bmp",NULL);

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

casa_b = load_bmp("casa-sup.bmp",NULL); casa_c = load_bmp("casa-choque.bmp",NULL); bosq_a = load_bmp("bosque.bmp",NULL); bosq_b = load_bmp("bosque-sup.bmp",NULL); bosq_c = load_bmp("bosque-choque.bmp",NULL); // cargamos imagenes del primer escenario fondo = casa_a; choque = casa_c; cielo = casa_b; lugar = 1; } // actualiza el estado del juego void actualiza_juego() { int cambio = 0; int ax,ay; ax = jugador.getx(); ay = jugador.gety(); jugador.teclado(); // 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) { py=py+160; }

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

for ( int ci=2; ci < 30; ci++) { for (int cj=0; cj < 16; cj++) { // color rojo if ( getpixel( choque, choca = true; } // color verde if ( getpixel( choque, // color azul if ( getpixel( choque, // color amarillo if ( getpixel( choque,

px+ci, py+cj) == 0xff0000 ){

px+ci, py+cj) == 0x00ff00 ) cambio = 1; px+ci, py+cj) == 0x0000ff ) cambio = 2; px+ci, py+cj) == 0xffff00 ) salir = true;

} } if ( choca ){ // vuelve al estado anterior jugador.posiciona( ax,ay ); } switch ( lugar ) { case 1: // casa if ( cambio == 1 ) { // cambiamos a otro lugar lugar = 2; fondo = bosq_a; choque = bosq_c; cielo = bosq_b; jugador.posiciona( 410,370 ); } break; case 2: // bosque if ( cambio == 2 ) {

110 // cambiamos a otro lugar 111 lugar = 1; 112 fondo = casa_a; 113 choque = casa_c; 114 cielo = casa_b; 115 // situamos al prota dentro de la casa 116 jugador.posiciona( 290,440 ); 117 } 118 break; 119 default: 120 break; 121 } 122 123} 124 125 126 127// Se encarga de pintar todo sobre el buffer 128void pinta_juego() 129{ 130 int ancho, alto; 131 int ax=0; 132 int ay=0; 133 int bx=0; 134 int by=0; 135 136 switch ( lugar ) 137 { 138 case 1: // casa 139 bx=160; 140 by=160; 141 ancho = 480; 142 alto = 325; 143 break; 144 case 2: // bosque 145 ay=160; 146 ancho = PANTALLA_ANCHO; 147 alto = PANTALLA_ALTO; 148 break;

149 150 151 152 153 154 155 156}

default: break; } blit( fondo, buffer, ax, ay, bx, by, ancho, alto); jugador.pinta(); masked_blit( cielo, buffer, ax, ay, bx, by, ancho, alto);

Todas las modificaciones se han realizado solo en el archivo mijuego.h, por tanto todos los demás archivos se dejan tal cual, solo se debe de sustituir este nuevo código y añadir las nuevas imágenes.

Paso a Paso Se han declarado nuevas variables del tipo BITMAP, que son las que contendrá las imagenes de los escenarios. Se crea el concepto de fases, controlado con la variable lugar, que contendrá en que fase o lugar esta:  

1 En la casa 2 En el bosque

En la función carga_juego(), cargamos todas las imágenes que se utilizarán en el programa. Las variables fondo, choque, y cielo, serán variables generales que indicaran el lugar actual en el que estamos, por tanto, siempre que se cambie de lugar (fase) se debe de actualizar estas variables con las nuevas imágenes del lugar. ? 1 for ( int ci=2; ci < 30; ci++) 2 { 3 for (int cj=0; cj < 16; cj++) 4 { 5 6 // color rojo

7 8 9 10 11 12 13 14 15 16 17}

if ( getpixel( choque, choca = true; } // color verde if ( getpixel( choque, // color azul if ( getpixel( choque, // color amarillo if ( getpixel( choque,

px+ci, py+cj) == 0xff0000 ){

px+ci, py+cj) == 0x00ff00 ) cambio = 1; px+ci, py+cj) == 0x0000ff ) cambio = 2; px+ci, py+cj) == 0xffff00 ) salir = true;

}

En la función de colisión se han añadido algunas condiciones, según colores utilizados. Para el ejemplo utilizamos el rojo (0xff0000) para bloquear el paso del personaje, el verde (0x00ff00) para salir de la casa, el azul (0x0000ff) para volver al interior de la casa. Se han cambiado los valores del primer bucle for, ahora empieza en el 2 para acabar en el 29. Esto se ha realizado para reducir el tamaño de bloqueo del personaje, por tanto ahora el tamaño de choque es de 28x16, tal y como se muestra en la siguiente imagen el recuadro pintado de verde es lo que realmente se esta comprobando si choca.

En la función pinta_juego() siempre se pinta el fondo y el cielo. Lo único que va variando son los parámetros de muestra, que cambian según el escenario o lugar, para ello se crean las variables ax,ay, bx,by, ancho y alto. Estos datos cambian según las propiedades de las imágenes utilizadas para los escenarios. Aqui teneis las Imagenes bosque comprimida en RAR. Y aqui tienen el archivo mijuego.h comprimido en RAR.

Crear juego RPG en C++ y Allegro 4 (6) - Scroll

Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se hará que nuestro personaje pueda recorrer el extenso bosque, mediante la ayuda del scroll.

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

Que es un scroll ? Se denomina scroll, desplazamiento, al movimiento en 2D de los contenidos que conforman el escenario de un videojuego. En el ejemplo anterior solo llegaba a mostrarse un trozo del mapa bosque, ya que la imagen es superior a la resolución utilizada en el programa ( 800x600 ). Por ello, se crea un scroll para que cuando el personaje se acerque a uno de los bordes de la pantalla, se desplace el escenario mostrando el resto del mapa.

Programación Dentro de mijuego.h se define una variable booleana para indicar si el mapa tiene o no desplazamiento, esta variable la llamamos desplaza, si es TRUE es que tiene desplazamiento. También se definen las variables enteras desplazamiento_map_x y desplazamiento_map_y, estas variables contendrá el valor del desplazamiento del scroll segun su eje x, y. Estas variables se definen de forma global para que puedan ser utilizadas en todas las funciones. En la funcion de carga_juego(), se inicializan las variables: desplazamiento_map_x=0; desplazamiento_map_y=0; desplaza = false;

Se les da un valor de 0 y false, ya que inicialmente partimos de que el primer mapa no tendrá scroll. En la función actualiza_juego(), se controla el desplazamiento del scroll mediante el siguiente codigo: if ( desplaza ) { int d = desplazamiento / 2; // controla el desplazamiento del mapa si esta en los bordes if ( ax < 160 && desplazamiento_map_x > 0 ) { desplazamiento_map_x-=d; jugador.posiciona(ax+d,ay); ax = jugador.getx(); ay = jugador.gety(); if ( ax < 60 && desplazamiento_map_x > 0 ) { desplazamiento_map_x-=d; jugador.posiciona(ax+d,ay); }

} if ( ay < 160 && desplazamiento_map_y > 0 ) { desplazamiento_map_y-=d; jugador.posiciona(ax,ay+d); ax = jugador.getx(); ay = jugador.gety(); if ( ay < 60 && desplazamiento_map_y > 0 ) { desplazamiento_map_y-=d; jugador.posiciona(ax,ay+d); } } if ( ax > PANTALLA_ANCHO-160 && 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-60 && desplazamiento_map_x < fondo->w-PANTALLA_ANCHO { desplazamiento_map_x+=d; jugador.posiciona(ax-d,ay); } } if ( ay > PANTALLA_ALTO-160 && 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-60 && desplazamiento_map_y < fondo->h-PANTALLA_ALTO ) { desplazamiento_map_y+=d; jugador.posiciona(ax,ay-d); } } ax = jugador.getx(); ay = jugador.gety();

)

}

En lineas generales lo que hace el código es que cuando el jugador se acerca a uno de los bordes, ya sea por arriba, abajo, izquierda o derecha, cuando esta a menos de 160 pixel se realiza un pequeño desplazamiento del escenario, en el caso de que se siga acercando al borde y este de menos de 60 pixel, se realiza otro desplazamiento para evitar que el jugador quede fuera de pantalla. Este código debe de ir justo antes de la llamada del jugador.teclado(). Tambien se debe de cambiar la siguiente condición al completo. if (lugar == 2) { px = px + desplazamiento_map_x; py = py + desplazamiento_map_y; }

Para que actualice la posición del protagonista cuando haya un desplazamiento. switch ( lugar ) { case 1: // casa if ( cambio == 1 ) { // cambiamos a otro lugar lugar = 2; fondo = bosq_a; choque = bosq_c; cielo = bosq_b; jugador.posiciona( 410,370 ); desplazamiento_map_x=0; desplazamiento_map_y=160; desplaza=true; } break;

case 2: // bosque if ( cambio == 2 ) { // cambiamos a otro lugar lugar = 1; fondo = casa_a; choque = casa_c; cielo = casa_b; // situamos al prota dentro de la casa jugador.posiciona( 290,440 ); desplazamiento_map_x=0; desplazamiento_map_y=0; desplaza=false; } break; default: break; }

Se añade en cada una de las fases el valor correspondiente, según tenga o no scroll el próximo mapa que va a cargar. Y finalmente se añade el desplazamiento del scroll a la hora de pintar los escenarios en la función pinta_juego() void pinta_juego() { int ancho, alto; int ax=0; int ay=0; int bx=0; int by=0; switch ( lugar ) { case 1: // casa bx=160; by=160;

case 2:

ancho = 480; alto = 325; break; // bosque ax = desplazamiento_map_x; ay = desplazamiento_map_y; ancho = PANTALLA_ANCHO; alto = PANTALLA_ALTO; break;

default: break; } blit( fondo, buffer, ax, ay, bx, by, ancho, alto); jugador.pinta(); masked_blit( cielo, buffer, ax, ay, bx, by, ancho, alto); }

Y llegados a este punto ya se tendrá el escenario del juego con scroll. 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 pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación: http://creatusjuegosdecero.webege.com/index.php

Crear juego RPG en C++ y Allegro 4 (7) Efectos sonoros Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se añadirá algunos efectos sonoros.

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

Añadiendo Sonidos Para este curso necesitaremos algunos recursos, concretamente los archivos de audio de los efectos que queramos añadir. Para ello se han buscado algunas páginas gratuitas, donde se puede descargar el efecto deseado.

1. Pagina web de sonidos gratuitos 2. Página web de sonidos gratuitos Es importante recordar que el Allegro solo reconoce el formato WAV sin compresión, así que se debe de asegurar que todos los archivos de audio estén en este formato. En este ejemplo añadiremos tres sonidos:   

pasos.wav: el personaje hará un ruido al caminar abrir_puerta.wav: al cambiar de escenario pasando por la puerta, suena el sonido de abrir una puerta. bosque.wav: es un sonido ambiente del bosque donde se oyen animales, que se repetirá de forma cíclica.

Programación Para poder utilizar sonidos en Allegro se debe de inicializar, y esto se añade a la función inicia_allegro() , justamente despues de ajustar la resolución de la pantalla.

? 1// incializa el audio en allegro 2if (install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, NULL) != 0) { 3

allegro_message("Error: inicializando sistema de sonido\n%s\n", allegro_error);

4} 5 6// inicializa todo lo referente al sonido

7inicia_sonido();

Todo lo demás referente al sonido se va a poner aparte en un archivo llamado audio.h, que se encargará de todo el audio de nuestro juego.

? 1 // audio.h 2 3 SAMPLE *spasos; 4 SAMPLE *spuerta1; 5 SAMPLE *sbosque; 6 7 // funcion que carga todos los ficheros de audio 8 void inicia_sonido(){ 9

set_volume(230, 90);

10 11 spasos

=

load_wav("pasos.wav");

12 spuerta1 = load_wav("abrir_puerta.wav"); 13 sbosque

= load_wav("bosque.wav");

14} 15 16void sonido_pasos(){ 17

play_sample ( spasos, 100,128, 3300, 0 );

18} 19 20void sonido_abrirpuerta(){ 21

play_sample ( spuerta1, 100,128, 1300, 0 );

22} 23 24void sonido_ambiente(){ 25

play_sample ( sbosque, 80,128, 900, 1 );

26} 27 28void para_sonido_ambiente(){ 29

stop_sample( sbosque );

30}

El comando play_sample requiere cinco parámetros: 1. El SAMPLE que se quiere reproducir.

2. 3. 4. 5.

Indica el volumen, con un rango de 0 a 255 (máximo volumen). Indica si se quiere que se escuche por la izquierda 0 o la derecha 255. La frecuencia es relativa: 1000 representa la frecuencia a la que el sample fue grabado, 2000 es el doble, etc. Repetición, si esta a 1 indica que se repetirá.

Ahora ya solo queda hacer las llamadas a las funciones de sonido, en el momento justo de cada una de las acciones a las que se quieren poner sonido.

En el archivo players.h, se coloca la llamada al sonido_pasos. Dentro de la función teclado, justo cuando se comprueba que se ha pulsado alguna de las teclas de dirección, dentro de la siguiente condición tal y como se muestra a continuación:

? 1if ( ax != x || ay != y ) 2{ 3

sonido_pasos();

4

// entra si a cambiado alguna de las variables x,y

5

animacion++;

6

if ( animacion > 2 ) animacion = 0;

7}

Dentro del archivo mijuego.h, se añadirán los sonidos de la puerta (sonido_abrirpuerta) y ambiente (sonido_ambiente), justo cuando se cambia de escenario, en la función actualiza_juego(), después de comprobar si existe colisión.

? 1 switch ( lugar ) 2 { 3 case 1:

// casa

4

if ( cambio == 1 )

5

{

6

// cambiamos a otro lugar

7

lugar = 2;

8

fondo

9

choque = bosq_c;

10

cielo

11

jugador.posiciona( 410,370 );

12

desplazamiento_map_x=0;

13

desplazamiento_map_y=160;

14

desplaza=true;

15

sonido_abrirpuerta();

= bosq_a;

= bosq_b;

16

sonido_ambiente();

17

}

18

break;

19case 2:

// bosque

20

if ( cambio == 2 )

21

{

22

// cambiamos a otro lugar

23

lugar = 1;

24

fondo

25

choque = casa_c;

26

cielo

27

// situamos al prota dentro de la casa

28

jugador.posiciona( 290,440 );

29

desplazamiento_map_x=0;

30

desplazamiento_map_y=0;

31

desplaza=false;

32

sonido_abrirpuerta();

33

para_sonido_ambiente();

34

}

= casa_a;

= casa_b;

35

break;

36default: 37

break;

38}

Haz clic aqui para descargar los archivos wav comprimidos en RAR 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 pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación:

http://creatusjuegosdecero.webege.com/index.php

Crear juego RPG en C++ y Allegro 4 (8) Música Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se añadirá música de fondo, una diferente para cada escenario, tal y como se muestra en el video.

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

Para este ejemplo se necesita dos archivos MIDI, para poder poner una música a cada escenario. Los archivos MIDI se llamaran:  

musica1: música de fondo que se escuchara cuando este el personaje dentro de la casa. musica2: música de fondo que se escuchara cuando este el personaje en el bosque.

Los archivos utilizados para este curso, puedes descargarlo haciendo clic aqui.

Programación Siguiendo con el curso anterior, todo lo referente al sonido se incluye dentro de audio.h. Se declaran dos variables del tipo MIDI, llamadas musica1, musica2. Dentro de la función inicia_sonido se inicializan estas dos variables de la siguiente forma: ? 1musica1 = load_midi("musica1.mid"); 2musica2 = load_midi("musica2.mid");

Y se añaden dos funciones nuevas para reproducir la música deseada. ? 1void 2 3} 4 5void 6 7}

musica_casa(){ play_midi(musica1,1); musica_bosque(){ play_midi(musica2,1);

El segundo parámetro de la función play_midi, indica si se quiere que la música sea repetitiva, como si se quiere que se repita se pone un 1. En la funcion carga_juego() de mijuego.h se añade una llamada a musica_casa, ya que el programa se inicia en ese escenario. Las próximas llamadas a cambiar la musica de fondo se realizan en la funcion actualiza_juego(), donde se controla que se cambia de escenario.

? 1 switch ( lugar ) 2 { 3 case 1: // casa 4 if ( cambio == 1 ) 5 { 6 // cambiamos a otro lugar 7 lugar = 2; 8 fondo = bosq_a; 9 choque = bosq_c; 10 cielo = bosq_b; 11 jugador.posiciona( 410,370 ); 12 desplazamiento_map_x=0; 13 desplazamiento_map_y=160; 14 desplaza=true; 15 sonido_abrirpuerta();

16 17 18 19 20case 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

sonido_ambiente(); musica_bosque(); } break; 2: // bosque if ( cambio == 2 ) { // cambiamos a otro lugar lugar = 1; fondo = casa_a; choque = casa_c; cielo = casa_b; // situamos al prota dentro de la casa jugador.posiciona( 290,440 ); desplazamiento_map_x=0; desplazamiento_map_y=0; desplaza=false; sonido_abrirpuerta(); para_sonido_ambiente(); musica_casa(); } break;

Y aquí acaba esta entrega.

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 pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación:

http://creatusjuegosdecero.webege.com/index.php

Crear juego RPG en C++ y Allegro 4 (9) DAT Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se utilizará la aplicación grabber con la idea de unificar todos los archivos utilizados en uno solo.

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

GRABBER ficheros DAT Si nunca ha utilizado esta utilidad, le aconsejo que se miren esta página, ya que aquí se centra mas en la programación que en el manejo del grabber y la creación del archivo. Se crea un archivo .DAT mediante la utilidad grabber. Este fichero contendrá todas las imágenes, sonidos, y músicas que se han utilizado en todo el proyecto, de esta forma se facilita el traslado del proyecto, sin que se pierda ningún archivo, y permite proteger el contenido. A cada uno de los archivos que se van poniendo se le ha puesto como nombre, el nombre que tenia el objeto y una letra d delante para indicar que es un objeto del datafile, y seguido de una "i" si se trata de una imagen, una "s" si se trata de un sonido, o una "m" si se trata de una música. Ejemplo: La imagen del prota llamada personaje.bmp se ha renombrado dentro del datafile como dipersonaje. La imagen casasup.bmp se ha llamado como dicasasup. El sonido del bosque.wav queda dsbosque. Y la musica1.mid queda como dmmusica1. El fichero datafile creado tiene una contraseña que es "cursoRPGkodaygames", y lleva una compresión global para reducir el tamaño de los archivos. El archivo queda como se muestra la imagen.

Programación En el archivo global.h definimos dos variables, una que contiene la contraseña del fichero DAT y la otra datosjuegos que es del tipo DATAFILE. // clave del fichero datafile char evalc[19]="cursoRPGkodaygames"; DATAFILE *datosjuego;

En el archivo main.cpp, en la función inicia_allegro() se quita la llamada a la función inicia_sonido(), y se sustituye por set_volume(230, 90); ya que no es necesaria la función inicia_sonido(). Tambien hay que añadir un nuevo #include para añadir el archivo datosjuego.h que se genera a partir del fichero DAT. #include #include "global.h"

#include #include #include #include

"datosjuego.h" "audio.h" "players.h" "mijuego.h"

En el archivo players.h, solo hay que cambiar una linea que es la que se encarga de cargar la imagen del personaje, debe sustituirse por: prota

= (BITMAP *)datosjuego[dipersonaje].dat;

Este cambio se debe a que ya no se utiliza el archivo BMP directamente, sino que se extrae del archivo datosjuego.dat que lo contiene todo. En el archivo audio.h, se elimina todas las variables declaradas de forma global, y también se borra la función inicia_sonido(), ya que todos estos datos que anteriormente se cargaba ahora lo hace cuando se carga el fichero DAT. El acceso a los sonidos y la música cambia quedando de la siguiente forma el archivo audio.h // audio.h void sonido_pasos(){ play_sample ( (SAMPLE *)datosjuego[dspasos].dat, 160,128, 3300, 0 ); } void sonido_abrirpuerta(){ play_sample ( (SAMPLE *)datosjuego[dsabrirpuerta].dat, 100,128, 1300, 0 ); } void sonido_ambiente(){ play_sample ( (SAMPLE *)datosjuego[dsbosque].dat, 80,128, 900, 1 ); } void para_sonido_ambiente(){ stop_sample( (SAMPLE *)datosjuego[dsbosque].dat );

} void musica_casa(){ play_midi((MIDI *)datosjuego[dmmusica1].dat,1); } void musica_bosque(){ play_midi((MIDI *)datosjuego[dmmusica2].dat,1); }

En el archivo mijuego.h, se elimina las siguientes variables. BITMAP BITMAP BITMAP BITMAP BITMAP BITMAP

*casa_a; *casa_b; *casa_c; *bosq_a; *bosq_b; *bosq_c;

Todas estas variables tampoco hacen falta, pues se utiliza directamente las del fichero DAT. Por tanto en la función carga_juego(), se debe de eliminar también la inicialización de las variables anteriores quedando la función de la siguiente forma: // carga todo lo necesario antes de empezar el juego void carga_juego() { packfile_password(evalc); datosjuego = load_datafile("datosjuego.dat"); if ( !datosjuego ){ allegro_message("Error: archivo datosjuego.dat no encontrado\n%s\n", allegro_error); }

jugador.inicia(); fondo = (BITMAP *)datosjuego[dicasa].dat; choque = (BITMAP *)datosjuego[dicasachoque].dat; cielo = (BITMAP *)datosjuego[dicasasup].dat; lugar = 1; desplazamiento_map_x=0; desplazamiento_map_y=0; desplaza = false; musica_casa(); }

En esta función se añade las primeras lineas para la carga del fichero DAT, la primera linea especificamos la contraseña del archivo. En la segunda linea carga el archivo datosjuego.dat. Y en la siguiente condición se comprueba de que no haya un error en la lectura del archivo. En la función actualiza_juego(), cuando se comprueba la variable lugar para saber si se cambia de escenario, se cambia las variables fondo, choque y cielo. switch ( lugar ) { case 1: // casa if ( cambio == 1 ) { // cambiamos a otro lugar lugar = 2; fondo = (BITMAP *)datosjuego[dibosque].dat; choque = (BITMAP *)datosjuego[dibosquechoque].dat; cielo = (BITMAP *)datosjuego[dibosquesup].dat; jugador.posiciona( 410,370 ); desplazamiento_map_x=0; desplazamiento_map_y=160;

desplaza=true; sonido_abrirpuerta(); sonido_ambiente(); musica_bosque(); } break; case 2: // bosque if ( cambio == 2 ) { // cambiamos a otro lugar lugar = 1; fondo = (BITMAP *)datosjuego[dicasa].dat; choque = (BITMAP *)datosjuego[dicasachoque].dat; cielo = (BITMAP *)datosjuego[dicasasup].dat; // situamos al prota dentro de la casa jugador.posiciona( 290,440 ); desplazamiento_map_x=0; desplazamiento_map_y=0; desplaza=false; sonido_abrirpuerta(); para_sonido_ambiente(); musica_casa(); } break; default: break; }

Y aquí acaba todos los cambios que se deben hacer para poder utilizar el fichero DAT. Para evitar algunos problemas con los archivos, les dejo el fichero DAT comprimido en RAR para descargar. 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 pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación:

http://creatusjuegosdecero.webege.com/index.php

Crear juego RPG en C++ y Allegro 4 (10) Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se utilizará todo lo anterior para crear un nuevo escenario.

En este curso, se repasará todos los puntos para crear un escenario nuevo para el juego. Para crear un nuevo escenario en el juego, se necesita crear una imagen del escenario en cuestión. Mientras se esta creando se recomienda hacerlo en un programa que permita capas, y hacer un mínimo de tres capas.

1. La parte inferior o suelo, en esta capa se pintará todo lo que va en el suelo por donde el protagonista podrá moverse. 2. La capa media o choque, en esta capa se pintará todo aquello que pueda bloquear el paso al personaje, ya sea un edificio, como un árbol. 3. La capa alta o superior, en esta capa debe ir todo lo que se quiera que se pinte por encima del personaje, es decir, que lo van a tapar, como la copa de los arboles o los tejados de los edificios. Todo esto se divide de esta manera para facilitar a la hora de hacer las tres capas que se necesitan para crear el escenario en cuestión. Para ellos se necesita una capa base, que lo contiene todo. Una segunda capa superior, en la que solo se pintará las cosas de la capa alta, y el resto se pintará del color rosa transparente. Y una tercera capa en la que se pintará las zonas bloqueadas con el color rojo, esta capa se hará a partir de la capa media.

Se recomienda tener bien claro lo que se desea hacer, ya que es muy laborioso hacer los escenarios como para que luego no guste o no sirvan. En el tutorial 5, se explica la función de colisión, en ella aparece el control de cuatro colores; rojo, verde, azul y amarillo. Si desea añadir algún otro color es tan simple como añadir otra condición e indicar el color y el valor de la variable cambio, de esta forma se esta creando otro color con el que colisionar y realizar alguna acción. Supongamos que se quiere añadir otro color para el control de acceso a un edificio. ? 1// color blanco 2if ( getpixel( choque, px+ci, py+cj) == 0xffffff ) cambio = 4;

El color blanco viene definido en hexadecimal 0xffffff pero se puede poner también en decimal si se desea. Si se cumple la condición la variable cambio vale 4, por tanto si cambio es 4, quiere decir que se ha pisado el color blanco. De esta manera en cualquiera de los escenario se puede añadir alguna nueva función en el caso de pisar el color blanco. Se puede añadir tantos colores como se desee, pero cuantos mas se añadan mas lento será el proceso de colisión. En cada escenario, es necesario definir que hacer en caso de colisionar con alguno de los colores, por tanto cuando la variable cambio tiene algún valor indica que se ha colisionado con uno de los colores. Por ejemplo, en el mapa bosque se ha añadido la opción para poder pasar al nuevo escenario de la ciudad. ? 1 case 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

2: // bosque if ( cambio == 2 ) { // cambiamos a otro lugar // casa lugar = 1; fondo = (BITMAP *)datosjuego[dicasa].dat; choque = (BITMAP *)datosjuego[dicasachoque].dat; cielo = (BITMAP *)datosjuego[dicasasup].dat; // situamos al prota dentro de la casa jugador.posiciona( 290,440 ); desplazamiento_map_x=0; desplazamiento_map_y=0; desplaza=false; sonido_abrirpuerta(); para_sonido_ambiente(); musica_casa(); } if ( cambio == 3 ) { // cambiamos a otro lugar // ciudad

24 25 26 27 28 29 30 31 32 33 34 35 36 37

lugar = 3; fondo = (BITMAP *)datosjuego[dicity1].dat; choque = (BITMAP *)datosjuego[dicity1choque].dat; cielo = (BITMAP *)datosjuego[dicity1sup].dat; // situamos al prota jugador.posiciona( 500,540 ); desplazamiento_map_x=950; desplazamiento_map_y=510; desplaza=true; para_sonido_ambiente(); musica_ciudad1(); } break;

Tal y como muestra en el código, cuando se esta en el mapa bosque si se colisiona con el color amarillo carga los datos de la ciudad, sitúa al personaje en las coordenadas (500,540) de la pantalla, sitúa el mapa con un desplazamiento de (950,510), ya que el mapa es mucho mas grande que la resolución de la ventana. Se indica que el escenario tiene scroll, se detiene el sonido ambiente y se inicial la nueva música para la ciudad. A la hora de pintar tambien hay que describir el escenario, quedando de la siguiente forma la función pinta_juego(). ? 1 // Se encarga de pintar todo sobre el buffer 2 void pinta_juego() 3 { 4 int ancho, alto; 5 int ax=0; 6 int ay=0; 7 int bx=0; 8 int by=0; 9 10 switch ( lugar ) 11 { 12 case 1: // casa 13 bx=160; 14 by=160;

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37}

case 2:

case 3:

ancho = 480; alto = 325; break; // bosque ax = desplazamiento_map_x; ay = desplazamiento_map_y; ancho = PANTALLA_ANCHO; alto = PANTALLA_ALTO; break; // ciudad1 ax = desplazamiento_map_x; ay = desplazamiento_map_y; ancho = PANTALLA_ANCHO; alto = PANTALLA_ALTO; break;

default: break; } blit( fondo, buffer, ax, ay, bx, by, ancho, alto); jugador.pinta(); masked_blit( cielo, buffer, ax, ay, bx, by, ancho, alto);

? 1 En este caso, tal y como se muestra ya sea el bosque o la ciudad, ambos tienen los mismos datos, quizás más adelante cuando se tengan mas escenario nos daremos cuenta de que se repite siempre lo mismo, que solo es diferente para los mapas que son mas pequeño que la resolución utilizada para el proyecto del juego (800,600), por tanto se podrá simplificar, pero eso lo dejamos para otro momento. Recordad, que cada escenario puede tener una música y un sonido ambiente, tal y como lo tiene el escenario del bosque. Espero que haya quedado más claro de como se hacen los escenarios por si alguno se anima a hacer alguno mas. Aquí tenéis el mapa del nuevo escenario, la ciudad.

Llegados a este punto aquí os dejo todos los códigos que hay hecho hasta el momento, con esto el juego tiene tres escenarios: la casa, el bosque y la ciudad. En el archivo RAR viene comprimido el proyecto del DEV-C++ y todos los códigos, que con el archivo DAT tendrá para poder ejecutarlo. 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 pagina, o alguna duda. Puedes plantear tu pregunta en el foro de programación:

http://creatusjuegosdecero.webege.com/index.php

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 KB

descargar

Archivo RAR del Fichero DAT

14,10 MB

descargar

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. ? 1 // npc.h

2 3 /* 4

la posicion x,y es una posicion directa al mapa, por tanto debe pintarse

5

directamente en el fondo, y no en el buffer

6 */ 7 8 class npc { 9

// posicion

10

int x,y;

11

int ax,ay;

12 13

int direccion;

14 15

int animacion;

16 17

int escena;

18 19 20

int estado;

21

BITMAP* imagen;

22 23

public:

24 25

void crea( BITMAP *_img, int _x, int _y, int dir, int _estado, int _lugar );

26

void pinta();

27}; 28 29 30void npc::crea( BITMAP *_img, int _x, int _y, int dir, int _estado, int _lugar ) 31{ 32

x = _x;

33

y = _y;

34

direccion = dir;

35

animacion = 0;

36

escena = _lugar;

37 38 39

imagen = create_bitmap(_img->w, _img->h);

40

blit( _img, imagen, 0,0, 0,0, _img->w, _img->h);

41 42

// NPC parado

43

estado = _estado;

44} 45 46 47void npc::pinta() 48{ 49

if ( lugar == escena )

50

{

51

// pinta el nuevo choque

52

rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000);

53

masked_blit(imagen, fondo, animacion*32, direccion*32, x, y, 32,32);

54

}

55}

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 ? 1 BITMAP *fondo; 2 BITMAP *choque; 3 BITMAP *cielo; 4 5 // indicará en que lugar estamos

6 // 1: casa 7 // 2: bosque 8 // 3: ciudad 9 int lugar; 10 11int desplazamiento_map_x; 12int desplazamiento_map_y;

Y se ha añadido una nueva variable que se encargará de contar el total de ticks que realiza el programa. ? 1volatile unsigned int tiempo_total = 0; 2 3// Función para controlar la velocidad 4void inc_contador_tiempo_juego() 5{ 6

contador_tiempo_juego++;

7

tiempo_total++;

8} 9END_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. ? 1// es el espacio en pixel que recorre el jugador al andar 2const int desplazamiento= 120 / FRAME_RATE;

Quedando de el archivo de esta forma: ? 1 /* 2

Name:

Curso RPG

3

Author:

Yadok - KODAYGAMES

4

Date:

27/08/15 14:49

5

Web:

http://devcpp-allegro.blogspot.com/

6

Description:

7

Creacion de un juego al estilo RPG

8

mas informacion en la web

9

Version: 10

10 11*/

12 13// clave del fichero datafile 14char evalc[19]="cursoRPGkodaygames"; 15 16DATAFILE *datosjuego; 17 18// Ancho y alto de la pantalla 19const int PANTALLA_ANCHO = 800; 20const int PANTALLA_ALTO

= 600;

21 22// En este BITMAP dibujaremos todo 23BITMAP *buffer; 24 25 26// Copiar el buffer a la pantalla del juego (screen) 27void pintar_pantalla() 28{ 29 30}

blit(buffer, screen, 0, 0, 0, 0, PANTALLA_ANCHO, PANTALLA_ALTO);

31 32// controla el bucle principal 33bool salir; 34 35 36int cambio; 37 38// indicará en que lugar estamos 39// 1: casa 40// 2: bosque 41// 3: ciudad 42int lugar; 43 44int desplazamiento_map_x; 45int desplazamiento_map_y; 46 47BITMAP *fondo; 48BITMAP *choque; 49BITMAP *cielo;

50 51 52// Variable usada para la velocidad 53volatile unsigned int contador_tiempo_juego = 0; 54volatile unsigned int tiempo_total = 0; 55 56const int FRAME_RATE =30; 57 58// es el espacio en pixel que recorre el jugador al andar 59const int desplazamiento= 120 / FRAME_RATE; 60 61// Función para controlar la velocidad 62void inc_contador_tiempo_juego() 63{ 64

contador_tiempo_juego++;

65

tiempo_total++;

66} 67END_OF_FUNCTION(inc_contador_tiempo_juego)

En el archivo main.cpp

? 1#include < allegro .h > 2#include "global.h" 3#include "datosjuego.h" 4#include "audio.h" 5#include "players.h" 6#include "npc.h" 7#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 ? 1player jugador; 2 3npc personajes[30]; 4int npersonaje; 5

6bool 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. ? 1desplazamiento_map_x=-160; 2desplazamiento_map_y=-160; 3desplaza = false; 4 5cambio = 0; 6npersonaje = 2; 7 8personajes[0].crea( (BITMAP *)datosjuego[diper004].dat, 160,120, 2,0,1); 9personajes[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. ?

1 // comprobar si colisiona con el mapa 2 bool choca = false; 3 int px = jugador.getx(); 4 int py = jugador.gety()+16; 5 6 7 if ( lugar == 1) 8 { 9

px = jugador.getx()-160;

10

py = jugador.gety()-160+16;

11} 12if (lugar == 2 || lugar == 3) 13{ 14

px = px + desplazamiento_map_x;

15

py = py + desplazamiento_map_y;

16} 17 18for ( int ci=2; ci < 30; ci++) 19{

20

for (int cj=0; cj < 16; cj++)

21

{

22 23

// color rojo

24

if ( getpixel( choque, px+ci, py+cj) == 0xff0000 ){

25

choca = true;

26

}

27

// color verde

28

if ( getpixel( choque, px+ci, py+cj) == 0x00ff00 ) cambio = 1;

29

// color azul

30

if ( getpixel( choque, px+ci, py+cj) == 0x0000ff ) cambio = 2;

31

// color amarillo

32

if ( getpixel( choque, px+ci, py+cj) == 0xffff00 ) cambio = 3;

33 34 35

}

36} 37if ( choca ){ 38

// vuelve al estado anterior

39

jugador.posiciona( ax,ay );

40} 41

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 ? 1for ( int z=0; z < npersonaje; z++ ) 2{ 3

personajes[z].pinta();

4} 5 6blit( 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(). ? 1 if ( ax != x || ay != y ) 2 {

3

if ( choca() )

4

{

5

x =ax;

6

y =ay;

7

}else{

8

int mx = desplazamiento_map_x;

9

int my = desplazamiento_map_y;

10 11

rectfill( choque, ax+4+mx, ay+17+my, ax+28+mx, ay+30+my, 0x000000);

12 13

// control choque para npcs

14

rectfill( choque, x+4+mx, y+17+my, x+28+mx, y+30+my, 0xffffff);

15 16

int num = FRAME_RATE / 12;

17

if ( tiempo_total % num == 0 )

18

{

19

sonido_pasos();

20

// entra si a cambiado alguna de las variables x,y

21

animacion++;

22

if ( animacion > 2 ) animacion = 0;

23 24

} }

25}

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. ? 1 bool player::choca() 2 { 3

int mx = x+desplazamiento_map_x;

4

int my = y+desplazamiento_map_y;

5 6

bool resp=false;

7

for (int i=2; i < 30; i++ )

8

{

9

for (int j=16; j < 32; j++)

10

{

11 12

if ( getpixel ( choque, mx+i, my+j) != 0x000000 &&

13

getpixel ( choque, mx+i, my+j) != 0xffffff )

14

{

15

// si el color no es negro

16

resp = true;

17

// color verde

18

if ( getpixel( choque, mx+i, my+j) == 0x00ff00 ) cambio = 1;

19

// color azul

20

if ( getpixel( choque, mx+i, my+j) == 0x0000ff ) cambio = 2;

21

// color amarillo

22

if ( getpixel( choque, mx+i, my+j) == 0xffff00 ) cambio = 3;

23

}

24 25

}

26

}

27

return resp;

28}

Recordar de que hay que añadir la declaración de la función dentro de las funciones públicas de la clase player. ? 1 class player 2 { 3

BITMAP *prota;

4

int x,y;

5

int direccion;

6

int animacion;

7 8

public:

9

void inicia();

10

void pinta();

11

void teclado();

12

bool choca();

13

int getx(){ return x; };

14

int gety(){ return y; };

15

void posiciona( int _x, int _y);

16};

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

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

limites globales ( x < 0 ) x = 0; ( x > PANTALLA_ANCHO-32 ) x = PANTALLA_ANCHO-32; ( y < 0 ) y = 0; ( 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(): ? 1 bool npc::chocanpc() 2 { 3 int ninix,niniy; 4 int nfinx,nfiny; 5 6 if ( direccion == 0 ) 7 { 8 // abajo 9 ninix = 0; 10 niniy = 32 - desplazamiento; 11 nfinx = 32; 12 nfiny = 32; 13 } 14 if ( direccion == 1 ) 15 { 16 // izquierda 17 ninix = 0; 18 niniy = 0; 19 nfinx = desplazamiento; 20 nfiny = 32;

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56}

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

? 1 void npc::actualiza() 2 { 3 // para indicar que se ejecuta dos veces 4 int num = FRAME_RATE / 6; 5 6 if ( tiempo_total % num == 0 ) 7 { 8 if ( estado != 0 ) 9 { 10 animacion++; 11 if ( animacion > 2 ) animacion = 0; 12 } 13 14 switch ( estado ) 15 { 16 case 1: // camina horizontal 17 ax = x; 18 ay = y; 19 if ( direccion == 1 ) 20 { 21 // camina izquierda 22 x-=desplazamiento; 23 if ( chocanpc() ) 24 { 25 // posicion no valida 26 x = ax; 27 direccion = 2; 28 } 29 } 30 if ( direccion == 2 ) 31 { 32 // camina derecha 33 x+=desplazamiento; 34 if ( chocanpc() ) 35 {

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

// 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; } }

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

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;

114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

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; }

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191

} 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;

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230

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, 000000); // pinta el nuevo choque rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000);

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269

} // 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; }

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308

} 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

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 }

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

? 1 void 2 { 3 4 5 6 7 8 9 10 11 12 13 14 15 16}

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. ? 1 // carga los datos del escenario segun lugar 2 void carga_escenario() 3 { 4 jugador.cambia_escenario(); 5 switch ( lugar ) 6 { 7 case 1:// casa 8 fondo = (BITMAP *)datosjuego[dicasa].dat; 9 choque = (BITMAP *)datosjuego[dicasachoque].dat; 10 cielo = (BITMAP *)datosjuego[dicasasup].dat; 11 12 desplaza = false; 13 14 musica_casa(); 15 break;

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39}

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

Crear juego RPG en C++ y Allegro 4 (13) Dialogos Aqui esta una nueva entrega del curso Crea tu juego RPG en C++ y Allegro, esta vez nos vamos a centrar en los diálogos.

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. En esta primera entrega estará centrada en la clase dialogo.

Programación Se va a programar las siguientes funciones: menor, mayor, numlineas, crea, pinta, y cambia_texto. La funcion menor, compara dos variables y devuelve el contenido de la variable de menor valor. ? 1int 2 3 4 5 6 7}

menor(int x, int y){ if ( x < y ){ return x; }else{ return y; }

La función mayor, compara dos variables y devuelve el contenido de la variable de mayor valor. ? 1int mayor(int x, int y){ 2 if ( x < y ){ 3 return y; 4 }else{

5 6 7}

return x; }

La función numlineas, calcula el número de lineas que se necesita para mostrar un texto dentro de un rectángulo determinado. ? 1 void 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

MENSAJE::numlineas(){ int cont; int espacio = 0; char* mtexto = (char*)stexto.c_str(); nlineas=1; if ( tancho+espaciado > ancho ){ // no cabe en una linea string resto = stexto; string trozo; char caracter[] = " "; char* caracter2; int nuevoancho = 0; int nc = 0; int restoancho = 0; do{ cont=1; trozo = resto.substr(0,cont); mtexto = (char*)trozo.c_str(); nuevoancho = text_length( fuente, mtexto ); espacio = 0; while ( nuevoancho+espaciado < ancho ){ trozo = resto.substr(cont,1); caracter2 = (char*)trozo.c_str(); if ( strcmp(caracter2,caracter)==0 ){ espacio = cont; } cont++; trozo = resto.substr(0,cont); mtexto = (char*)trozo.c_str();

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47}

nuevoancho = text_length( fuente, mtexto ); } nc = resto.length(); trozo = resto.substr(cont,1); caracter2 = (char*)trozo.c_str(); nlineas++; if ( espacio >0 && cont < nc && strcmp(caracter2,caracter)!=0 ) { resto = resto.substr(espacio); }else{ resto = resto.substr(cont); } restoancho = text_length( fuente, resto.c_str() ); }while( restoancho+espaciado > ancho ); }

Esta función sería mucho mas simple en el caso de que la fuente utilizada tenga la cualidad de que todos sus caracteres ocupen el mismo ancho, es decir, que por ejemplo la "W" ocupe lo mismo que la "l". Pero como la mayoría no es así, se complica un poco para averiguar cuantos caracteres caben en un ancho determinado. Por ello, se va cogiendo carácter a carácter hasta completar una linea sin llegar a sobrepasar el limite dado por el rectángulo. Debido a esto es algo complejo averiguar cuantas lineas van a ocupar, aunque si es fácil averiguar cuantas lineas es el máximo que cabe en el espacio definido por el rectángulo. El número total de lineas que ocupará el texto es almacenado en la variable nlineas. La función crea, se encarga de inicializar las variables de la clase y define texto que se va a mostrar y las dimensiones del rectángulo de texto. ? 1 void MENSAJE::crea(const char* t, FONT* f, int x1, int y1, int x2, int y2){ 2 stexto = t; 3 tancho = text_length( f, t ); 4 talto = text_height(f) + espaciado; 5 fuente = f; 6 7 mx1 = menor(x1,x2);

8 9 10 11 12 13 14 15 16};

mx2 = my1 = my2 = ancho alto

mayor(x1,x2); menor(y1,y2); mayor(y1,y2); = abs( mx1 - mx2 ); = abs( my1 - my2 );

numlineas();

Esta función recibe como parámetro el texto a mostrar, la fuente ( tipo de letra ), y las dimensiones del cuadro de dialogo. Inicializa los valores que utiliza la clase, y se asegura que las coordenadas para definir el tamaño del rectángulo estén de forma correcta. Y calcula el ancho y alto del rectángulo. La función pinta, se encarga de mostrar por pantalla el rectángulo de dialogo. Coloca en lo mejor posible el texto ajustado al tamaño del rectángulo. ? 1 void MENSAJE::pinta(BITMAP* b){ 2 int cont; 3 int espacio = 0; 4 char* mtexto = (char*)stexto.c_str(); 5 int linea=0; 6 int ni; 7 int altura = 0; 8 float exacto; 9 BITMAP *cuadro = create_bitmap(ancho,alto); 10 11 12 clear_to_color(cuadro, 0x222222); 13 set_trans_blender(0,0,0,130); 14 draw_trans_sprite(b, cuadro, mx1, my1); 15 set_trans_blender(0,0,0,255); 16 17 rect(b, mx1-1, my1-1, mx2-1, my2-1, 0xfcf902);

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

rect(b, mx1+1, my1+1, mx2+1, my2+1, 0x363712); rect(b, mx1, my1, mx2, my2, 0x222222); if ( tancho+espaciado > ancho ){ // no cabe en una linea string resto = stexto; string trozo; char caracter[] = " "; char* caracter2; int nuevoancho = 0; int nc = 0; int restoancho = 0; do{ cont=1; trozo = resto.substr(0,cont); mtexto = (char*)trozo.c_str(); nuevoancho = text_length( fuente, mtexto ); espacio = 0; while ( nuevoancho+espaciado < ancho ){ trozo = resto.substr(cont,1); caracter2 = (char*)trozo.c_str(); if ( strcmp(caracter2,caracter)==0 ){ espacio = cont; } cont++; trozo = resto.substr(0,cont); mtexto = (char*)trozo.c_str(); nuevoancho = text_length( fuente, mtexto ); } nc = resto.length(); trozo = resto.substr(cont,1); caracter2 = (char*)trozo.c_str(); if ( espacio >0 && cont < nc && strcmp(caracter2,caracter)!=0 ){ trozo = resto.substr(0,espacio); mtexto = (char*)trozo.c_str(); resto = resto.substr(espacio);

57 }else{ 58 trozo = resto.substr(0,cont); 59 mtexto = (char*)trozo.c_str(); 60 resto = resto.substr(cont); 61 } 62 63 64 altura = alto - (talto*nlineas); 65 exacto = ( alto / nlineas ); 66 ni = int( exacto ); 67 68 textout_centre_ex(b, fuente, mtexto, mx1+1+int(ancho/2), my1+2+(ni*linea)-(talto/2)+(ni/2) 69, 0x363712, -1); 70 textout_centre_ex(b, fuente, mtexto, mx1+int(ancho/2), my1+1+(ni*linea)-(talto/2)+(ni/2) , 710xffffff, -1); 72 73 linea++; 74 restoancho = text_length( fuente, resto.c_str() ); 75 }while( restoancho+espaciado > ancho ); 76 77 mtexto = (char*)resto.c_str(); 78 textout_centre_ex(b, fuente, mtexto, mx1+1+int(ancho/2), my1+2+(ni*linea)-(talto/2)+(ni/2) , 790x363712, -1); 80 textout_centre_ex(b, fuente, mtexto, mx1+int(ancho/2), my1+1+(ni*linea)-(talto/2)+(ni/2) , 810xffffff, -1); 82 83 }else{ 84 85 textout_centre_ex(b, fuente, mtexto, mx1+(ancho/2)+1, my1+1+((alto-talto)/2), 0x363712, 861); 87 textout_centre_ex(b, fuente, mtexto, mx1+(ancho/2), my1+((alto-talto)/2), 0xffffff, 881); 89 } destroy_bitmap(cuadro); };

Esta función se encarga de pintar un rectángulo, y sobre el se pinta el texto de manera que este centrado dentro de el todo el texto. En el caso de que el texto no cabe en una linea según el ancho del rectángulo, se realiza un bucle que se encarga de cojer carácter a carácter y va formando las lineas para que no superen el ancho del rectángulo. Una vez que llega al ancho muestra la linea, y vuelve a repetir el proceso con lo que queda por mostrar. Esta forma de hacerlo tiene un inconveniente, y es que cada vez que se muestra se vuelve a calcular todo, por tanto es un proceso algo lento. Aquí en este video se muestra un ejemplo utilizando la librería, creando varios cuadros de diálogos de distintos tamaño, mostrando un texto que se ajusta según va variando el tamaño de estos rectángulos.

La función cambia_texto, como su nombre indica sirve para cambiar el texto. ? 1void MENSAJE::cambia_texto( const char* t){ 2 stexto = t; 3 tancho = text_length( fuente, t ); 4 5 numlineas(); 6};

Esta función se encarga de cambiar el texto a mostrar, actualizando los valores del texto. La clase se define de la siguiente manera ? 1 class MENSAJE{ 2 3 string stexto; 4 5 FONT *fuente; 6 7 // ancho total del texto enviado

8 int tancho; 9 int talto; 10 11 // para delimitar el rectangulo de vision 12 int mx1,my1; 13 int mx2,my2; 14 // ancho y alto del rectangulo 15 int ancho, alto; 16 17 // numero de lineas totales q caben en el rectangulo 18 int nlineas; 19 20 void numlineas(); 21 22 public: 23 24 void crea(const char* t, FONT* f, int x1, int y1, int x2, int y2); 25 26 void pinta(BITMAP* b); 27 28 void cambia_texto( const char* t ); 29 30};

La clase creada se ha llamado MENSAJE.       

stexto : contiene el texto a mostrar fuente : contiene el tipo de letra que se utilizará para escribir el texto. tancho y talto : son dimensiones del texto según la fuente utilizada. mx1,my1 y mx2,my2 : son las coordenadas que delimitan nuestro cuadro de dialogo. ancho, alto: son las dimensiones del cuadro de dialogo. nlineas: indica el numero de lineas que ocupa el texto para mostrarse en el cuadro de dialogo. numlineas() : función privada que se encarga de contar el número de lineas.

Para que no tengan problema con esta nueva librería, les dejo el siguiente link para descargar dialogos.h en archivo 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

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 #include #include #include #include #include #include #include

< allegro.h > "global.h" "datosjuego.h" "audio.h" "dialogos.h" "players.h" "npc.h" "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( personajes[1].crea( personajes[2].crea( personajes[3].crea( personajes[4].crea( personajes[5].crea( personajes[6].crea( personajes[7].crea(

(BITMAP (BITMAP (BITMAP (BITMAP (BITMAP (BITMAP (BITMAP (BITMAP

*)datosjuego[diper001].dat, *)datosjuego[diper005].dat, *)datosjuego[diper005].dat, *)datosjuego[diper003].dat, *)datosjuego[diper005].dat, *)datosjuego[diper004].dat, *)datosjuego[diper006].dat, *)datosjuego[diper001].dat,

1300,700, 1,1,3); 280, 450, 0,2,3); 230, 280, 3,2,3); 960, 310, 2,3,3); 1120, 450, 0,4,3); 900, 650, 1,5,3); 850, 800, 0,0,3); 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 1; }; void no_ataca(){ ataca = -3; }; }

Como se puede observar las dos nuevas funciones son bastantes sencilla. atacando() se encarga de devolver TRUE cuando el valor de la variable ataca es superior a 1. La función no_ataca() asigna un valor a la variable ataca, en este caso -3.

En la función inicia(), se debe añadir la inicialización de la nueva variable, por tanto se añade lo siguiente: ataca = 0;

La función pinta(), cambia a lo siguiente: void player::pinta() { if ( ataca > 1 && ( direccion == 1 || direccion == 3 ) ) { masked_blit((BITMAP *)datosjuego[diespada].dat, buffer, 0, direccion*96, x-32, y-32, 96,96); } masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32); if ( ataca > 1 && ( direccion == 0 || direccion == 2 ) ) { masked_blit((BITMAP *)datosjuego[diespada].dat, buffer, 0, direccion*96, x-32, y-32, 96,96); } if ( ataca > 1 || ataca < 0) ataca++; }

Existen muchas formas de haber programado esto. En el caso de poseer las imagenes de nuestro personaje portando la espada, en todas direcciones y con animación, la programación hubiese sido añadir una condición y poner otro masked_blit. En este caso, no se posee dicha imagen. Se ha creado una imagen con el arma aparte. Debido a esto en la función se añade la nueva imagen, que es la espada del personaje. Llegado a este punto, se puede hacer de dos formas. Una de ella será poner la imagen espada para cada una de las distintas direcciones y animaciones, lo cual es bastante tedioso programar. Y la otra forma de hacerlo, es la que finalmente se ha utilizado. Para no complicar la programacion de colocar la espada en cada una de las situaciones, se ha creado una imagen que es tres veces mas grande que el personaje, si el personaje es de 32x32, la imagen de la espada es de 96x96. Esto se ha hecho asi para que no se tenga que colocar la espada al personaje mediante programación, sino cuando se crea la imagen, de esta forma la espada independientemente de la direccion de personaje siempre se situa en el mismo lugar en la posición x-32, y-32. Existen dos condiciones parecidas, ya que según la direccion interesa que la espada se pinte por encima del personaje o por debajo. La ultima condición, se encarga de incrementar la variable ataca, siempre que no sea ni 0, ni 1.

La funcion teclado() 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; } if ( key[KEY_SPACE] && ataca == 0 ) { ataca = 1; } if ( !key[KEY_SPACE] && ataca == 1 ) {

ataca = 2; sonido_espada_aire(); } } if ( ax != x || ay != y ) { // entra si a cambiado alguna de las variables x,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(); animacion++; if ( animacion > 2 ) animacion = 0; } } if ( ataca > (FRAME_RATE / 4) ) ataca = 0; }

En esta función, se añadió la tecla que se utiliza para atacar ( la barra espaciadora ), se ha cambiado lo que controla la animación, para que aunque se choque se siga moviendo el personaje. En el archivo npc.h, se ha añadido la nueva clase enemigo, ya que se ha creado como una derivada de la clase npc.

class enemigo : public npc { int vida; int v_actual; bool muerto; public: void void void bool

crea( BITMAP *_img, int _x, int _y, int dir, int _estado, int _lugar, int v ); herida( int d ); pinta(); ha_muerto() { return muerto; };

}; void enemigo::crea( BITMAP *_img, int _x, int _y, int dir, int _estado, int _lugar, int v ) { x = _x; y = _y; direccion = dir; animacion = 0; escena = _lugar; imagen = create_bitmap(_img->w, _img->h); mifondo = create_bitmap(32, 32); blit( _img, imagen, 0,0, 0,0, _img->w, _img->h); estado = _estado; primer = false; vida = v; v_actual = vida; muerto = false; }; void enemigo::herida( int d ) { if ( !muerto ) { v_actual-=d;

if ( v_actual x ) { direccion = 2; }else{ direccion = 1;

} if ( jy > y ) { if ( abs ( jx { direccion } }else{ if ( abs ( jx { direccion } }

- x ) < desplazamiento*3 ) = 0; - x ) < desplazamiento*3 ) = 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). Tambien 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 segun la función movimiento(). En el archivo mijuego.h, despues 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

Crear juego RPG en C++ y Allegro 4 (18) Experiencia Continuando con nuestro juego, después de la lucha cuando se mata al enemigo debemos recibir experiencia. Pues de esa parte nos vamos a encargar en este tutorial.

Objetivo Hacer que el personaje tenga experiencia y nivel. Y según un numero de experiencia suba un nivel. También se mostrará al igual que la barra de vida, la barra de experiencia para que el jugador sepa cuanto le falta para el próximo nivel.

Programación Empezamos por el archivo global.h, aquí se añade unas nuevas variables. bool lvl_up; int nlvlup; int lux, luy;

Con estas variable se controlará lo siguiente:   

lvl_up se utiliza para indicar si se ha subido nivel, si es TRUE entonces indica que si sube nivel. nlvlup se utiliza para mostrar por pantalla la imagen LVL UP!, va contando el numero de veces que se ha mostrado. lux,luy sirven para posicionar por pantalla la imagen LVL UP!.

En el archivo players.h, se añaden nuevas variables y funciones para la experiencia, quedando la clase de la siguiente forma: class player { BITMAP *prota; int x,y; int direccion; int animacion; bool hablar; int ataca; int vida; int vidamax; int enivel; int exp; 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; }; void sube_experiencia( int e ); int getnivel(){ return enivel; }; int getexp(){ return exp; }; };

La variable enivel contiene el nivel actual del personaje, y la variable exp contiene la experiencia actual. Se han añadido dos funciones para poder ver los valores de las dos nuevas variables, la función getnivel() y getexp(). Y la función sube_experiencia(), tiene como parámetro la cantidad de experiencia que se aumenta, se encarga de aumentar el valor de la experiencia y comprobar si se ha llegado a un nuevo nivel. void player::sube_experiencia( int e ) { exp = exp + e; int nxp = 100 * ( enivel + 1 ); if ( exp >= nxp && nxp != 1 ) { exp = exp - nxp; enivel++;

// ha subido de nivel !! sonido_sube_nivel(); lvl_up = true; nxp = 100 * ( enivel + 1 ); while ( exp >= nxp ) { exp = exp - nxp; enivel++; // ha subido de nivel !! sonido_sube_nivel(); nxp = 100 * ( enivel + 1 ); } } }

La variable nxp contiene el valor de la ecuación 100 * (enivel + 1), esto quiere decir que al nivel 1 el valor de nxp es 200, a nivel 2 es 300, a nivel 3 es 400, y así sucesivamente. Esta ecuación tiene una progresión aritmética, quiere decir que la diferencia entre dos términos sucesivos cualesquiera de la secuencia es una constante, en este caso es 100. Al usar este tipo de ecuación se esta haciendo que la dificultad de subir un nivel sea relativamente sencillo. Esta ecuación no es la mas correcta pero para nosotros nos vale por el momento, próximamente se hará un curso exclusivo hablando de esta ecuación y la dificultad. En la esta función se encarga de sumar la experiencia que se recibe por el parámetro e. Luego se compara con el nivel de experiencia que se necesita para subir el nivel actual (npx) y si es superior la experiencia actual (exp), a esta experiencia actual se le resta lo npx. Se calcula el valor del próximo nivel de experiencia (npx) y si exp sigue siendo mayor se repite hasta que no lo sea. En la funcion player::inicia(), se inicializan a cero las nuevas variables. exp = 0; enivel = 0;

En el archivo audio.h, se añade la funcion del sonido para cuando sube de nivel. void sonido_sube_nivel(){ play_sample ( (SAMPLE *)datosjuego[dsubenivel].dat, 110,128, 1300, 0 );

}

En el archivo npc.h, en la clase enemigo se añade una nueva variable exp, y una función para actualizar este valor. class enemigo : public npc { int vida; int v_actual; bool muerto; int golpeado; int exp; 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(); void daexp(int e){ exp = e; }; };

En la función enemigo::crea(), se debe inicializar la nueva variable, para nuestro caso le daremos el valor por defecto de 50. exp = 50;

Se añade en la función enemigo::herida() la llamada a la función sube_experiencia(), que se añadirá después del sonido_muere(). jugador.sube_experiencia(exp);

En el archivo mijuego.h, se añade una nueva barra en la función pinta_barra_vida(), aparte de pintar la barra de vida, ahora también pintará la barra de experiencia. void pinta_barra_vida() { int n = (jugador.getvida()*150) / jugador.getvidamax() ;

rectfill( buffer, PANTALLA_ANCHO-162, rectfill( buffer, PANTALLA_ANCHO-160, rectfill( buffer, PANTALLA_ANCHO-160, rect( buffer, PANTALLA_ANCHO-162, 10,

10, PANTALLA_ANCHO-8, 25, 0x003300); 12, PANTALLA_ANCHO-160+n, 23, 0x00ff00); 12, PANTALLA_ANCHO-160+n, 15, 0xbbffaa); PANTALLA_ANCHO-8, 25, 0x000000);

int nxp = 100 * ( jugador.getnivel() + 1 ); int n2 = (jugador.getexp()*150) / nxp ; rectfill( buffer, PANTALLA_ANCHO-162, 30, PANTALLA_ANCHO-8, 45, 0x000033); rectfill( buffer, PANTALLA_ANCHO-160, 32, PANTALLA_ANCHO-160+n2, 43, 0x0000ff); rectfill( buffer, PANTALLA_ANCHO-160, 32, PANTALLA_ANCHO-160+n2, 35, 0xbbaaff); rect( buffer, PANTALLA_ANCHO-162, 30, PANTALLA_ANCHO-8, 45, 0x000000); textprintf_centre_ex( buffer, font, PANTALLA_ANCHO - 80, 34, 0xFFFFFF, -1, "Niv: %d", jugador.getnivel() ); }

Se crea una nueva función llamada pinta_lvlup(), que se encargará de mostrar una imagen con el texto LVL UP! por encima del jugador. void pinta_lvlup() { if ( lvl_up ) { nlvlup = 1; lux = jugador.getx(); luy = jugador.gety(); lvl_up = false; } if ( nlvlup > 0 ) { masked_blit ( (BITMAP *)datosjuego[dilvlup].dat, buffer, 0,0, lux, luy - nlvlup*2, 32,32 ); int num = FRAME_RATE / 10; if ( tiempo_total % num == 0 ) { nlvlup++; } if ( nlvlup > 40 ) nlvlup = 0;

} }

Con la variable num, se obtiene un valor para que la imagen se muestre 10 veces en un segundo, y se va aumentando la variable nlvlup para contar cuantas veces se ha mostrado, cuando supere las 40 se reinicia a cero. En la función pinta_juego(), se añade la llamada a la funcion pinta_lvlup() justo después de la llamada a la funcion pinta_barra_vida(). Y aqui ya termina la programación, solo falta tener el ultimo archivo DAT, que contiene el sonido y la imagen. Puedes descargar archivo DAT haciendo clic aqui A continuación teneis un video donde se muestra como el jugador lucha contra varios enemigos. Dos de ellos dan mas experiencia, que son los soldados, de este modo suben varios niveles. Cuando se sube de nivel aparece encima del jugador la imagen que pone "LVL UP".

RPG (19): Nivel de Dificultad Este tema es teórico, y no se va a realizar nada de programación. En el se explicará la ecuación utilizada para la subida de nivel.

En la función player::sube_experiencia(), se encuentra la ecuación que determina los niveles. nxp = 100 * ( enivel + 1 );

Esta ecuación tiene una progresión aritmética, que quiere decir que la diferencia entre dos términos sucesivos cualesquiera de la secuencia es una constante, en este caso es 100.

Como ya se dijo anteriormente esta ecuación no es la mas indicada, debido a que la dificultad de subir un nivel va disminuyendo. Para que se entienda esto, pondremos un ejemplo. Segun la ecuación, esta es la lista de experiencia necesaria para cada nivel. Niv. Exp. 1 200 2 300 3 400 4 500 5 600 La experiencia por defecto que da al matar un enemigo es de 50, a continuación se muestra cuantos enemigos se deben matar para subir dicho nivel. Niv. Exp. 1 2 3 4 5

200 300 400 500 600

Ene. lvl. 1 4 6 8 10 12

Supongamos que se crea otros tipos enemigos, pero de un nivel superior. Enemigos de nivel 2, 3, etc.. y estos enemigos darán mas experiencia según nivel. Se Muestra en una tabla cuantos enemigo de cada nivel se debe matar para subir al siguiente nivel, se presupone que no puedas matar a un enemigo que este 2 niveles por encima de tu nivel.

Ene. Ene. Ene. Ene. Ene. Ene. Ene. Ene. Ene. Ene. lvl. 1 lvl. 2 lvl. 3 lvl. 4 lvl. 5 lvl. 6 lvl. 7 lvl. 40 lvl. 100 lvl. 101 200 4 300 6 4 400 8 5.3 4 500 10 6.6 5 4 600 12 8 6 4.8 4 700 14 9.3 7 5.6 4.6 4 800 16 10.6 8 6.4 5.3 4.5 4 4100 82 54.6 41 32.8 27.3 23.4 20.5 4 10100 202 134.6 101 80.8 67.3 57.7 50.5 9.85 4 10200 204 136 102 81.6 68 58.2 51 9.95 4.03 4

Niv. Exp. 1 2 3 4 5 6 7 40 100 101

Como se muestra en la tabla cada enemigo mayor se va disminuyendo el valor para el siguiente nivel, tendiendo a 4. Para el nivel 100 es necesario matar 4 de nivel 100, y para subir al nivel 101 tan solo se debe matar 4,03 enemigos de nivel 100. Es decir, la progresión es tan pequeña que para niveles altos resultará mas fácil subir al siguiente nivel, siempre y cuando se este matando enemigos de tu nivel o uno inferior. Llegaría un punto que ya matarías enemigos de dos o tres niveles por debajo y aun así seguirías subiendo igualmente. Por tanto, tal y como se ha demostrado no interesa que tenga una progresión aritmética ya que la diferencia entre un nivel y el consecutivo es un valor constante, y en nuestro caso es el 100, este valor en niveles pequeño este bien, pero para niveles altos el valor es ridículo. Quizás lo que nos interesa es un valor que vaya aumentando que no sea constante. A esto se llama progresión geométrica cuando cada elemento se obtiene multiplicando el elemento anterior por una constante. Probamos a cambiar la ecuacion a lo siguiente: npx = 200 * 2 ^( enivel - 1 )

NIV EXP ENE 1 200 4 2 400 8 3 800 16 4 1600 32 5 3200 64 Con esta nueva ecuación, vemos que conforme se sube de nivel se duplica el numero de experiencia necesaria para subir de nivel, por tanto, cuanto mas niveles se suba mas difícil resulta subir.

En esta imagen, se muestran las dos ecuaciones representadas gráficamente y se puede comprobar que la segunda ecuación que se ha elegido como buena opción, quizás no lo sea tanto, debido a que aumenta el doble a cada nivel que va subiendo. Veamos la tabla de enemigos de esta nueva ecuación.

NIV EXP ENE 2 3 4 5 6 1 200 4 2 400 8 4 3 800 16 8 4 4 1600 32 16 8 4 5 3200 64 32 16 8 4 6 6400 128 64 32 16 8 4 Como se muestra en la tabla, los valores son exactamente los mismos. Por tanto si se quiere subir al nivel 4, solo tienes que matar 4 de nivel 4. Si eres nivel 4 y quieres subir al 5 solo tienes que matar 4 de nivel 5, es decir, que debes matar siempre a 4 de un nivel superior al tuyo o 8 de tu nivel para subir al siguiente nivel. Escoger una buena ecuación es importante, ya que en este modo de juego que se esta programando no se tendrá limite de nivel, por tanto, si se escoge una ecuación como la primera llegará a ser muy fácil el subir niveles cuando seas un nivel alto. En cambio con la segunda opción la dificultad es siempre la misma debes matar el mismo numero de enemigos de tu nivel independientemente del nivel en el que te encuentres. Toda esta información es orientativa, ya que he considerado como algo normal el hecho de matar 8 enemigos de tu nivel para subir al nivel siguiente. La dificultad no solo se representa en matar 4 o 8 enemigos, si no también en cuanto nos cuesta matar un enemigo. Es recomendable que si se va a poner armas que aumentan su daño, también sea algo progresivo, es decir, que aumente pero siempre la misma proporción para que el jugador siempre intente conseguir armas de su nivel para facilitar el poder matar a un enemigo.

Crear juego RPG en C++ y Allegro 4 (20) Inventario Continuamos con el curso de crea tu juego RPG en C++ y Allegro. En esta entrega se hará el inventario.

Objetivo En este curso se añadirá los objetos. Estos objetos se podrá conseguir matando enemigos. Tambien se añade el inventario del jugador, que tiene un limite de 12 objetos.

Programación En el archivo global.h, se añade una nueva variable, que se va a encargar de controlar cuando se esta mostrando el inventario, esta variable se llamará swinv. int swinv;

En el archivo players.h, se añaden nuevas funciones y una variable para el manejo del inventario. class player { BITMAP *prota; int x,y;

int direccion; int animacion; bool hablar; int ataca; int vida; int vidamax; int enivel; int exp; int inventario[12]; 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; }; BITMAP* getimg(){ return prota; }; void sube_experiencia( int e ); int getnivel(){ return enivel; };

int getexp(){ return exp; }; void obtiene_objeto( int id ); int getinventario(int id){ return inventario[id]; }; };

La variable inventario es una matriz de 12 elementos, en los cuales se almacenará el id del objeto. La función obtiene_objeto(), recibe el id de un objeto y lo asigna a una posición de la matriz siempre que exista un hueco vacio, en el caso de estar el inventario lleno, el objeto en cuestión se pierde. La función getinventario(), devuelve según el parámetro id, lo que se contiene en la posición id del vector inventario. void player::obtiene_objeto( int id ){ int n = 0; while ( n < 12 && inventario[n] != 0 ) { n++; } // tiene un hueco if ( inventario[n] == 0 ){ inventario[n] = id; } }

Esta función recibe el id del objeto, e intenta almacenarlo en una de las casillas del vector inventario. Inicializa n a cero, y se repite el bucle hasta que encuentre un hueco vacio, es decir, que inventario[n]=0 o que n sea mayor que 11. En el caso de encontrar un hueco, asigna el valor de id, al vector inventario en la posición n. En el archivo npc.h, en la función enemigo::herida() se añade justo despues de subir la experiencia al jugador, lo siguiente para que el jugador reciba un objeto. if ( v_actual 0 ) { int jx = jugador.getx();

int jy = jugador.gety(); if ( jx > PANTALLA_ANCHO/2 ){ jx = (PANTALLA_ANCHO/4) - 160; }else{ jx = (PANTALLA_ANCHO*3/4) - 160; } if ( jy > PANTALLA_ALTO/2 ){ jy = (PANTALLA_ALTO/4) - 81; }else{ jy = (PANTALLA_ALTO*3/4) - 81; } // se muestra fondo inventario masked_blit( (BITMAP *)datosjuego[diinventario].dat, buffer, 0,0, jx,jy, 320,162); masked_blit( jugador.getimg(), buffer, 32,0, jx+65,jy+60, 32,32); masked_blit( (BITMAP *)datosjuego[dio005].dat, buffer, 0,0, jx+115,jy+74, 32,32); masked_blit( (BITMAP *)datosjuego[dio006].dat, buffer, 0,0, jx+12,jy+74, 32,32); int id; for ( int i=0; i < 4; i++){ for ( int j=0; j < 3; j++){ int num = (j*4) + i ; id = jugador.getinventario( num ); if ( id != 0 ){ int posx = jx + 172 + i*34; int posy = jy + 40 + j*34; masked_blit( (BITMAP *)datosjuego[id].dat, buffer, 0,0, posx,posy, 32,32); } } } if ( !key[KEY_I]) swinv = 2; } }

Esta función se podría haber simplificado mucho, pero debido a la imagen que utilicé para mostrar el contenido del inventario y que cambia de posicion para que se pueda seguir jugando mientras ves el inventario se ha complicado un poco. El codigo que hay del principio de la función hasta el comentario "//se muestra fondo inventario", esa parte se encarga de calcular la posición de la ventana inventario representada por la posicion jx,jy. Todo esto depende de la posicion del jugador, se divide la pantalla en 4 partes y se coloca el inventario en el que no se encuentra el jugador siguiendo la siguiente regla, si la posicion x es menor de la mitad de la pantalla, la posicion x del inventario será mayor de la mitad, sino es a la inversa. Y asi igualmente para el eje y. Despues de todo esto se pinta de forma transparente con el comando masked_blit, priemro el fondo del inventario, luego el personaje, y despues dos objetos ( armadura y espada ) El doble bucle for se utiliza para mostrar todos los posibles objetos que tenga el jugador en el inventario.

En la funcion pinta_juego(), se añade la llamada a la función pinta_inventario(). Esta llamada se pondrá despues de pintar la barra de vida y el lvlup. pinta_barra_vida(); pinta_lvlup(); pinta_inventario();

Y llegado a este punto si todo se ha copiado correctamente solo nos faltará actualizar el fichero DAT para tener las nuevas imágenes que se utilizan en este curso. Haz clic para descargar el fichero DAT en formato RAR.

Crear juego RPG en C++ y Allegro 4 (21) Inventario II Otra entrega del curso crea tu juego RPG en C++ y Allegro. En esta entrega se continua el inventario.

Objetivo En esta entrega, haremos que se pueda equipar el personaje con algunos de los objetos recibidos, y poder cambiar los objetos de lugar.

Programación En el archivo global.h, se añade lo siguiente: DATAFILE *datobjetos; // control raton en inventario int swraton; int nsel; int nid;

Se crea una nueva variable del tipo DATAFILE para el manejo del nuevo fichero de objetos. Con estas tres variables, se va a controlar si se ha pulsado el boton del raton swraton, en que casilla se ha pulsado nsel, y cual es el objeto en caso de tener nid. La función pintar_pantalla(), se borra de global.h, para ponerlo en el main.cpp. En el archivo audio.h, se añade dos funciones: void sonido_boton(){ play_sample ( (SAMPLE *)datosjuego[dsboton1].dat, 110,128, 1100, 0 ); } void sonido_boton2(){ play_sample ( (SAMPLE *)datosjuego[dsboton2].dat, 100,128, 1400, 0 ); }

La función sonido_boton se ha utilizado para poner sonido cuando se suelta un objeto. La función sonido_boton2 se ha utilizado para poner el sonido cuando se coje el objeto. Tanto dsboton1 y dsboton2 son nuevos sonidos que se han añadido al fichero DAT. En el archivo players.h, se han añadido todo lo referente a la equipación que lleva actualmente el personaje y algunas funciones para acceder y manejar las nuevas variables del equipo y el inventario. Añadir lo siguiente en la parte privada de la clase player. // equipacion actual int casco; int armadura; int arma; int anillo;

Añadir lo siguiente al final, en la parte pública de la clase player. void cambia_inventario(int p1, int p2 );

void pon_inventario(int pos, int id){ inventario[pos]=id; }; int int int int

getarma(){ return arma; }; getarmadura(){ return armadura; }; getcasco(){ return casco; }; getanillo(){ return anillo; };

void void void void

pon_arma(int id){ arma=id; }; pon_armadura(int id); pon_casco(int id){ casco=id; }; pon_anillo(int id){ anillo=id; };

Estas funciones son muy básicas, las "get" para obtener el valor, y las funciones "pon" para asignar un valor. La funcion pon_inventario(pos, id), segun la posicion dada, coloca el objeto id en el inventario. void player::cambia_inventario(int p1, int p2 ) { int t; t = inventario[p1]; inventario[p1] = inventario[p2]; inventario[p2] = t; };

La funcion player::cambia_inventario(), intercambia dos posiciones del inventario. void player::pon_armadura(int id) { armadura = id; if ( id != -1 ) prota = (BITMAP *)datobjetos[equipo_personaje(armadura)].dat; if ( id == -1 ) prota = (BITMAP *)datosjuego[dipersonaje0].dat; };

La función player::pon_armadura(), es un poco mas compleja que las demás ya que al quitar o cambiar la armadura tambien se cambia la imagen del personaje. En el caso de que id sea distinto de -1 indica que se esta equipando una armadura. En el caso de que id

sea -1, indica de que no tiene ninguna armadura, por tanto esta desnudo. En la función player::inicia(), se inicializan las nuevas variables para que el personaje tenga una equipación inicial. casco = -1; armadura = 4; arma = 5; anillo = -1; prota

= (BITMAP *)datobjetos[equipo_personaje(armadura)].dat;

Según estos datos, estamos indicando de que no tendrá casco (-1), la armadura será la (4) el traje normal, el arma el (5) la espada, y anillo no tiene (-1). Y finalmente se asigna a la imagen del personaje, la imagen correspondiente a la armadura seleccionada. Se crea un nuevo archivo llamado misobjetos.h, que se encargará del manejo de los objetos. ? 1 // misobjetos.h 2 3 #include < vector > 4 using namespace std; 5 6 struct m_objetos{ 7 int id; 8 int tipo; 9 char nombre[50]; 10 int img; 11}; 12 13vector< m_objetos > lobj; 14 15 16int tipo( int id ){ 17 for(int i = 0; i < lobj.size(); i++) 18 { 19 if ( lobj[i].id == id ) return lobj[i].tipo;

20 } 21 return -1; 22} 23 24int equipo_personaje( int id ){ 25 for(int i = 0; i < lobj.size(); i++) 26 { 27 if ( lobj[i].id == id && lobj[i].tipo < 5 ) return lobj[i].img; 28 } 29 return -1; 30} 31 32void lee_objetos(){ 33 char contenido[255]; 34 35 m_objetos temp; 36 37 packfile_password(NULL); 38 PACKFILE *fichero; 39 40 fichero = pack_fopen("objetos.txt","r"); 41 42 while ( !pack_feof(fichero) ) 43 { 44 pack_fgets( contenido, 255, fichero); 45 temp.id = atoi( contenido ); 46 47 pack_fgets( contenido, 255, fichero); 48 temp.tipo = atoi( contenido ); 49 50 pack_fgets( temp.nombre, 255, fichero); 51 52 pack_fgets( contenido, 255, fichero); 53 temp.img = atoi( contenido ); 54 55 lobj.push_back( temp ); 56 } 57 58 pack_fclose(fichero);

59};

Se crea una estructura llamada m_objetos, que contiene id, tipo, nombre y img. Se crea un vector de la estructura que se llama lboj, en la cual se va a guardar todos los objetos. La funcion tipo(), devuelve el tipo del objeto segun el id recibido por parámetro. La función equipo_personaje(), devuelve el valor de img del objeto si es un tipo de objeto equipable. En caso contrario devuelve -1 para indicar que no se ha encontrado. La función lee_objetos(), se encarga de leer un archivo objetos.txt, en el cual estan almacenados todos los objetos que se utilizaran en el juego. Conteniendo de cada objeto el id, tipo, nombre y imagen. El id es el numero de la imagen dentro del fichero DAT, el tipo se utiliza para de que tipo de objeto es: 1. 2. 3. 4. 5. 6.

armas cascos armaduras anillos pócimas otros

La img, no es mas que el numero de una imagen dentro del fichero DAT de objetos. En el archivo mijuego.h, se suprime todo lo referente a los objetos, ya que se ha puesto en el nuevo fichero misobjetos.h. En la función carga_juego(), antes de inicializar la variable jugador se carga el nuevo archivo objetos.dat, y se realiza la llamada a la función lee_objetos(). Y se inicializa la variable swraton a -1. Se debe borrar el bucle for que había para cargar los objetos. datobjetos = load_datafile("objetos.dat"); if ( !datobjetos ){ allegro_message("Error: archivo objetos.dat no encontrado\n%s\n", allegro_error); }

lee_objetos(); jugador.inicia(); swraton=-1;

La función pinta_inventario(), cambia tanto, que he decidido ponerla entera. ? 1 void pinta_inventario() 2 { 3 if ( swinv > 0 ) 4 { 5 6 int jx = jugador.getx(); 7 int jy = jugador.gety(); 8 int posx; 9 int posy; 10 11 if ( jx > PANTALLA_ANCHO/2 ){ 12 jx = (PANTALLA_ANCHO/4) - 160; 13 }else{ 14 jx = (PANTALLA_ANCHO*3/4) - 160; 15 } 16 17 if ( jy > PANTALLA_ALTO/2 ){ 18 jy = (PANTALLA_ALTO/4) - 81; 19 }else{ 20 jy = (PANTALLA_ALTO*3/4) - 81; 21 } 22 23 // se muestra fondo inventario 24 masked_blit( (BITMAP *)datosjuego[diinventario].dat, buffer, 0,0, jx,jy, 320,162); 25 26 if ( swraton == 1 && mouse_b&2 ) 27 { 28 swraton = -2;

29 } 30 31 masked_blit( jugador.getimg(), buffer, 32,0, jx+65,jy+60, 32,32); 32 33 if ( jugador.getcasco() != -1 ) 34 { 35 masked_blit((BITMAP *)datobjetos[equipo_personaje(jugador.getcasco())].dat, buffer, 36 32, 0, jx+65, jy+60, 32,32); 37 } 38 39 40 posx=jx+116; 41 posy=jy+40; 42 if ( mouse_x > posx && mouse_x < posx+32 && mouse_y > posy && mouse_y < posy+32 ) 43 { 44 if ( mouse_b&1 && swraton != -2) 45 { 46 // se ha pulsado dentro de un hueco 47 if ( swraton == -1 ) 48 { 49 // primer clic 50 swraton = 1; 51 nsel = -4; 52 nid = jugador.getanillo(); 53 if ( nid == -1 ){ 54 swraton = -1; 55 }else{ 56 sonido_boton2(); 57 } 58 }else{ 59 60 if ( nid != jugador.getanillo() && tipo(nid) == 4 ) 61 { 62 // cambia objeto de sitio 63 if ( jugador.getanillo() != -1 ){ 64 jugador.pon_inventario(nsel,jugador.getanillo()); 65 }else{ 66 jugador.pon_inventario(nsel,0); 67 }

68 69 70 jugador.pon_anillo(nid); 71 sonido_boton(); 72 73 swraton = -2; 74 } 75 } 76 } 77 } 78 79 80 81 if ( jugador.getanillo() != -1 ) 82 { 83 masked_blit( (BITMAP *)datobjetos[jugador.getanillo()].dat, buffer, 0,0, 84 jx+116,jy+40, 32,32); 85 } 86 87 posx=jx+116; 88 posy=jy+74; 89 if ( mouse_x > posx && mouse_x < posx+32 && mouse_y > posy && mouse_y < posy+32 ) 90 { 91 if ( mouse_b&1 && swraton != -2) 92 { 93 // se ha pulsado dentro de un hueco 94 if ( swraton == -1 ) 95 { 96 // primer clic 97 98 swraton = 1; 99 nsel = -1; 100 nid = jugador.getarmadura(); 101 if ( nid == -1 ){ 102 swraton = -1; 103 }else{ 104 sonido_boton2(); 105 } 106 }else{

107 108 if ( nid != jugador.getarmadura() && tipo(nid) == 3 ) 109 { 110 // cambia objeto de sitio 111 if ( jugador.getarmadura() != -1 ){ 112 jugador.pon_inventario(nsel,jugador.getarmadura()); 113 }else{ 114 jugador.pon_inventario(nsel,0); 115 } 116 117 jugador.pon_armadura(nid); 118 sonido_boton(); 119 120 swraton = -2; 121 } 122 } 123 } 124 } 125 126 if ( jugador.getarmadura() != -1 ) 127 { 128 masked_blit( (BITMAP *)datobjetos[jugador.getarmadura()].dat, buffer, 0,0, 129 jx+116,jy+74, 32,32); 130 } 131 132 133 posx=jx+13; 134 posy=jy+74; 135 if ( mouse_x > posx && mouse_x < posx+32 && mouse_y > posy && mouse_y < posy+32 ) 136 { 137 if ( mouse_b&1 && swraton != -2) 138 { 139 // se ha pulsado dentro de un hueco 140 if ( swraton == -1 ) 141 { 142 // primer clic 143 144 swraton = 1; 145 nsel = -2;

146 nid = jugador.getarma(); 147 if ( nid == -1 ){ 148 swraton = -1; 149 }else{ 150 sonido_boton2(); 151 } 152 }else{ 153 154 if ( nid != jugador.getarma() && tipo(nid) == 1 ) 155 { 156 // cambia objeto de sitio 157 if ( jugador.getarma() != -1 ){ 158 jugador.pon_inventario(nsel,jugador.getarma()); 159 }else{ 160 jugador.pon_inventario(nsel,0); 161 } 162 163 jugador.pon_arma(nid); 164 sonido_boton(); 165 166 swraton = -2; 167 } 168 } 169 } 170 } 171 172 173 if ( jugador.getarma() != -1 ) 174 { 175 masked_blit( (BITMAP *)datobjetos[jugador.getarma()].dat, buffer, 0,0, 176 jx+13,jy+74, 32,32); 177 } 178 179 posx=jx+13; 180 posy=jy+40; 181 if ( mouse_x > posx && mouse_x < posx+32 && mouse_y > posy && mouse_y < posy+32 ) 182 { 183 if ( mouse_b&1 && swraton != -2) 184 {

185 // se ha pulsado dentro de un hueco 186 if ( swraton == -1 ) 187 { 188 // primer clic 189 190 swraton = 1; 191 nsel = -3; 192 nid = jugador.getcasco(); 193 if ( nid == -1 ){ 194 swraton = -1; 195 }else{ 196 sonido_boton2(); 197 } 198 }else{ 199 200 if ( nid != jugador.getcasco() && tipo(nid) == 2 ) 201 { 202 // cambia objeto de sitio 203 if ( jugador.getcasco() != -1 ){ 204 jugador.pon_inventario(nsel,jugador.getcasco()); 205 }else{ 206 jugador.pon_inventario(nsel,0); 207 } 208 209 210 jugador.pon_casco(nid); 211 sonido_boton(); 212 213 swraton = -2; 214 } 215 } 216 } 217 } 218 219 if ( jugador.getcasco() != -1 ) 220 { 221 masked_blit( (BITMAP *)datobjetos[jugador.getcasco()].dat, buffer, 0,0, 222 jx+13,jy+40, 32,32); 223 }

224 225 int id; 226 for ( int i=0; i < 4; i++){ 227 for ( int j=0; j < 3; j++){ 228 int num = (j*4) + i ; 229 id = jugador.getinventario( num ); 230 posx = jx + 172 + i*34; 231 posy = jy + 40 + j*34; 232 if ( id != 0 ){ 233 masked_blit( (BITMAP *)datobjetos[id].dat, buffer, 0,0, posx,posy, 234 32,32); 235 } 236 if ( mouse_x > posx && mouse_x < posx+32 && mouse_y > posy && mouse_y < 237 posy+32 ) 238 { 239 if ( mouse_b&1 && swraton != -2) 240 { 241 // se ha pulsado dentro de un hueco 242 if ( swraton == -1 ) 243 { 244 // primer clic 245 if ( id != 0 ) 246 { 247 swraton = 1; 248 nsel = num; 249 nid = id; 250 sonido_boton2(); 251 } 252 }else{ 253 254 if ( nsel >= 0 ) 255 { 256 if ( nsel != num && nsel != -1) 257 { 258 // cambia objeto de sitio 259 jugador.cambia_inventario(nsel,num); 260 sonido_boton(); 261 swraton = -2; 262 }

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301

}else{ // ha quitado algo del ekipo if ( nsel == -1 && (tipo(id) == 3 { // armadura jugador.pon_inventario( num, if ( id == 0 ) id = -1; jugador.pon_armadura( id ); sonido_boton(); swraton = -2; } if ( nsel == -2 && (tipo(id) == 1 { // arma jugador.pon_inventario( num, if ( id == 0 ) id = -1; jugador.pon_arma( id ); sonido_boton(); swraton = -2; } if ( nsel == -3 && (tipo(id) == 2 { // arma jugador.pon_inventario( num, if ( id == 0 ) id = -1; jugador.pon_casco( id ); sonido_boton(); swraton = -2; }

|| id == 0) ) nid );

|| id == 0) ) nid );

|| id == 0) ) nid );

if ( nsel == -4 && (tipo(id) == 4 || id == 0) ) { // arma jugador.pon_inventario( num, nid ); if ( id == 0 ) id = -1; jugador.pon_anillo( id ); sonido_boton(); swraton = -2;

302 303 304 305 306 307

} } } } } if ( swraton == -2 && !mouse_b&1 ) swraton=-1; } } if ( !key[KEY_I]) swinv = 2; } }

En el archivo main.cpp, se añade la funcion pintar_pantalla(). // Copiar el buffer a la pantalla del juego (screen) void pintar_pantalla() { if ( swinv > 0 ) { masked_blit( (BITMAP *)datosjuego[dicursor].dat, buffer, 0,0,mouse_x, mouse_y, 16,16); } if ( swinv > 0 && swraton > -1 ) { masked_blit( (BITMAP *)datobjetos[nid].dat, buffer, 0,0,mouse_x, mouse_y, 32,32); } blit(buffer, screen, 0, 0, 0, 0, PANTALLA_ANCHO, PANTALLA_ALTO); }

En esta función se ha añadido, que mediante la variable swinv se muestre la flecha del raton. Esto es para que cuando se muestre el inventario, se muestre la flecha del raton y si se pulsa sobre un objeto se pinte el objeto donde esta el cursor. En el inicio del main.cpp, se añade el include del nuevo archivo.

#include #include #include #include #include #include #include #include

< allegro.h > "datosjuego.h" "global.h" "audio.h" "misobjetos.h" "dialogos.h" "players.h" "mijuego.h"

En la función inicia_allegro(), se debe inicializar el raton que se añade justo despues de inicializar el teclado. allegro_init(); install_timer(); install_keyboard(); install_mouse();

En el archivo npc.h, se cambia lo referente al objeto ya que se ha cambiado el formato y uso. En la función enemigo::herida(), se elimina la siguiente linea: int q = objetos[rand()%6].id;

Para ser sustituida por las siguientes: int q2 = lobj.size(); int q = lobj[rand()%q2].id;

Ya que ahora se utiliza lobj para almacenar la lista de objetos. Llegados a este puntos, solo nos faltan algunos archivos como son:   

objetos.dat: contiene las nuevas imagenes de los objetos objetos.txt: contiene información de los objetos (id, tipo, nombre, img). datosjuego.dat contiene el material multimedia del juego ( imagenes, sonidos, etc ).

Haz clic aqui para descargar los objetos TXT y DAT en archivo RAR Haz clic aquí para descargar el fichero datosjuego.dat en RAR. Haz clic aquí para descargar el fichero datosjuego.h en RAR.

Crear juego RPG en C++ y Allegro 4 (22) Tiendas Aquí esta una nueva entrega del curso crea tu juego RPG en C++ y Allegro. Dedicada a explicar como programar una tienda.

Objetivo Se trata de crear un npc con el que se hable y te de opción a comprar objetos o vender los objetos que tengamos en nuestro inventario. En esta primera parte nos vamos a centrar en los objetos y sus características.

Programación Para crear la tienda debemos tener una lista de objetos. De cada objeto debemos tener un mínimo de información, el nombre, la imagen, descripción, etc. Todo código referente a los objetos se pondrá en el archivo misobjetos.h. Este archivo cambia mucho con respecto al anterior, por ello he decidido poner todo su contenido. Para almacenar estos objetos se crea una estructura llamada m_objetos. ? 1 struct m_objetos{ 2 int nid; 3 int id; 4 int tipo; 5 char nombre[30]; 6 char descripcion[255]; 7 int img; 8 int precio; 9 }; 10 11vector< m_objetos > lobj;

nid: es un número para distinguir a los objetos. De esta manera si queremos guardar un objeto en el inventario, solo se guarda este dato. Ya que mediante este dato se puede acceder al resto de información. id: es el id de la imagen almacenada en el archivo objetos.dat tipo: Indica de que tipo de archivo se trata: 1. 2. 3. 4. 5. 6.

armas cascos armaduras anillos pocimas otros

nombre: contiene el nombre del objeto, con un máximo de 30 caracteres. descripción: contiene la descripcion del objeto, teniendo un limite de 255 caracteres. img: es el id de una imagen almacenada en el archivo objetos.dat, que será utilizada en el caso de que sea un objeto equipable, es decir, que el tipo sea entre 1 y 4. precio: es el precio del objeto cuando se vende en la tienda. En el archivo misobjetos.h, se pone las siguientes funciones: ? 1 int id_img_objeto( int id ) 2 { 3 for(int i = 0; i < lobj.size(); i++) 4 { 5 if ( lobj[i].nid == id ) return lobj[i].id; 6 } 7 return 0; 8 } 9 10 11char* descripcion_objeto( int id ) 12{ 13 for(int i = 0; i < lobj.size(); i++)

14 { 15 if ( lobj[i].nid == id ) return lobj[i].descripcion; 16 } 17 return ""; 18} 19 20char* nombre_objeto( int id ) 21{ 22 for(int i = 0; i < lobj.size(); i++) 23 { 24 if ( lobj[i].nid == id ) return lobj[i].nombre; 25 } 26 return ""; 27} 28 29int precio_objeto( int id ) 30{ 31 for(int i = 0; i < lobj.size(); i++) 32 { 33 if ( lobj[i].nid == id ) return lobj[i].precio; 34 } 35 return 0; 36} 37 38 39int tipo( int id ){ 40 for(int i = 0; i < lobj.size(); i++) 41 { 42 if ( lobj[i].nid == id ) return lobj[i].tipo; 43 } 44 return -1; 45} 46 47int equipo_personaje( int id ){ 48 for(int i = 0; i < lobj.size(); i++) 49 { 50 if ( lobj[i].nid == id && lobj[i].tipo < 5 ) return lobj[i].img; 51 } 52 return -1;

53} 54 55void lee_objetos(){ 56 char contenido[255]; 57 58 m_objetos temp; 59 60 packfile_password(NULL); 61 PACKFILE *fichero; 62 63 fichero = pack_fopen("objetos.txt","r"); 64 65 while ( !pack_feof(fichero) ) 66 { 67 pack_fgets( contenido, 255, fichero); 68 temp.nid = atoi( contenido ); 69 70 pack_fgets( contenido, 255, fichero); 71 temp.id = atoi( contenido ); 72 73 pack_fgets( contenido, 255, fichero); 74 temp.tipo = atoi( contenido ); 75 76 pack_fgets( contenido, 255, fichero); 77 strncpy( temp.nombre, contenido, 29 ); 78 temp.nombre[30]='\0'; 79 80 pack_fgets( temp.descripcion, 255, fichero); 81 82 pack_fgets( contenido, 255, fichero); 83 temp.img = atoi( contenido ); 84 85 pack_fgets( contenido, 255, fichero); 86 temp.precio = atoi( contenido ); 87 88 lobj.push_back( temp ); 89 } 90 91 pack_fclose(fichero);

92};

La función id_img_objeto(), recibe un id y devuelve el id de la imagen almacenada. La función descripcion_objeto(), recibe un id de objeto y devuelve su descripción. La función nombre_objeto(), recibe un id de objeto y devuelve el nombre. La función precio_objeto(), recibe un id de objeto y devuelve el precio. La función tipo(), recibe un id de objeto y devuelve el tipo. La función equipo_personaje(), recibe un id de objeto y devuelve el id de la imagen si el objeto es equipable. Todas estas funciones, actuan del mismo modo. Reciben un id del objeto y van recorriendo el vector en busca del objeto que tenga dicho id, y devuelve lo que se solicita de dicho objeto. La función lee_objetos(), accede al archivo objetos.txt y vuelca su contenido a la memoria almacenandola en la variable lobj, que contendrá la lista de objetos. El archivo objetos.txt debe tener unas caracteristicas especiales, para que el programa funcione y no de ningun error. Por cada objeto debe tener: un entero que indique el id un entero para indicar el id de la imagen del objeto un entero para indicar el tipo el cual debe tener un valor entre el 1 y el 6. una cadena de caracteres para indicar el nombre, a ser posible que no supere los 30 caracteres. una cadena de caracteres para indicar la descripción, con un máximo de 255 caracteres, en el caso de superar este valor el programa fallará. un entero para indicar el id de la imagen secundaria un entero para indicar el precio del objeto. Ejemplo de un objeto: 1 0 6

Margarita flor del campo 0 1 A continuación dejo el archivo que se utiliza para nuestro ejemplo, pero si uno quiere puede modificarlo para añadir mas objetos, cambiar nombres, descripciones, precios, etc. Se recomienda que no se borren estos primeros objetos, ya que algunos son utilizados por el jugador al empezar. Haz clic aqui para descargar archivo objetos.txt en RAR. Mediante la instruccion pack_fgets, se va obteniendo una linea del archivo. Y segun la linea que sea se realiza una operación diferente. Se considera que una linea no puede superar los 255 caracteres, en el caso de que en el archivo exista una linea supere este valor el programa dará un error. Cuando se lee el nombre, aunque se obtiene la linea entera de 255 caracteres, el programa guarda en el nombre solo los 29 primeros caracteres e ignora los demas. El caracter 30 se le asigna el valor de fin de linea. En la mayoría de los casos aunque se lee una cadena de caracteres es necesario transformarlo en un formato numérico, para ellos se utiliza el comando atoi(), que se encarga de transformar una cadena de caracteres a un entero.

Al inicio del archivo misobjetos.h se debe añadir la librería vector que se utiliza en nuestras funciones. #include < vector > using namespace std;

Si deseas tener el archivo misobjetos.h, puedes descargar el RAR haciendo clic aqui. Aunque se ha variado la clase objeto, las imagenes no se han tocado, por tanto se mantiene el archivo objetos.dat del tema anterior. Se crea un nuevo archivo llamado botones.h, que contendrá todo lo necesario para crear botones de texto.

? 1 /* botones.h 2 F. Finalizado : 22-12-12 3 Autor: KodayGames 4 5 clase TBOTON 6 para crear texto como boton 7 */ 8 9 10#ifndef _BOTONES_H_ 11#define _BOTONES_H_ 12 13#include < allegro.h > 14 15 16class TBOTON{ 17 public: 18 void crear(const char* text, const FONT* bfuente); 19 void pinta(BITMAP* bfondo, int x, int y, int color1, int color2); 20 bool ratonb(); 21 22 private: 23 int bx, by; 24 int bpulsa; 25 int bancho,balto; 26 const char* btext; 27 const FONT* bfuente; 28}; 29 30// incializa las variables de la clase 31void TBOTON::crear(const char* text, const FONT* fuente) 32{ 33 btext = text; 34 bpulsa = 0; 35 bfuente = fuente; 36 bancho = text_length(bfuente, btext); 37 balto = text_height(bfuente);

38}; 39 40// muestra en pantalla el texto, sobre el fondo indicado, con la fuente indicada, en la posicion x,y 41// con el color1 cuando esta señalado y color2 cuando no 42void TBOTON::pinta(BITMAP* bfondo, int x, int y, int color1, int color2) 43{ 44 bx=x; 45 by=y; 46 47 if (mouse_x >= bx && mouse_x < bx+bancho+1 && mouse_y >= by && mouse_y < by+balto+1) 48 { 49 textout_ex(bfondo, bfuente, btext, bx, by, color1, -1); 50 }else{ 51 textout_ex(bfondo, bfuente, btext, bx, by, color2, -1); 52 } 53}; 54 55// comprueba si el raton esta situado encima de nuestro texto y a pulsado el boton del raton 56bool TBOTON::ratonb() 57{ 58 return (mouse_x >= bx && mouse_x = by && mouse_y 10 ) { rect(buffer, x_tienda+470, y_tienda, x_tienda+490, y_tienda+372,0xffffff ); rect(buffer, x_tienda+470, y_tienda+30, x_tienda+490, y_tienda+342,0xffffff ); triangle( buffer, x_tienda+473,y_tienda+25, x_tienda+487,y_tienda+25, x_tienda+480,y_tienda+5,0xffffff ); triangle( buffer, x_tienda+473,y_tienda+347, x_tienda+487,y_tienda+347, x_tienda+480,y_tienda+367,0xffffff ); // barra central // distancia unidad float ny1 = ( 340.0 - 32.0 ) / vlista_tienda.size() ; // parte superior por donde se inicia int ny2 = int(inicia_objeto * ny1); // siempre se muestran 10 - longitud barra int ny3 = int(10.0 * ny1); rectfill(buffer, x_tienda+472, y_tienda+32+ny2, x_tienda+488, y_tienda+32+ny3+ny2, 0xffffff ); if ( mouse_x > x_tienda+460 && mouse_x < x_tienda+490 && mouse_y > y_tienda && mouse_y < y_tienda+30) { // esta situado encima de boton triangle( buffer, x_tienda+473,y_tienda+25, x_tienda+487,y_tienda+25, x_tienda+480,y_tienda+5,0xffff00 ); if ( mouse_b&1 ) { // pulsa hacia arriba

inicia_objeto--; if (inicia_objeto < 0 ) inicia_objeto = 0; } } if ( mouse_x > x_tienda+460 && mouse_x < x_tienda+490 && mouse_y > y_tienda+342 && mouse_y < y_tienda+372) { // esta situado encima de boton triangle( buffer, x_tienda+473,y_tienda+347, x_tienda+487,y_tienda+347, x_tienda+480,y_tienda+367,0xffff00 ); if ( mouse_b&1 ) { // pulsa hacia arriba inicia_objeto++; if (inicia_objeto+10 > vlista_tienda.size() ) inicia_objeto--; } } } for ( int it=inicia_objeto; it < vlista_tienda.size() && it < inicia_objeto+10; it++ ) { int ty = y_tienda+((it+1)*34) - (inicia_objeto*34); masked_blit( (BITMAP*)datobjetos[vlista_tienda[it].id].dat, buffer, 0,0,x_tienda,ty, 32,32); textprintf_ex( buffer, (FONT *)datosjuego[dftextos].dat, x_tienda+34, ty, 0xFFFFFF, -1, "%s", vlista_tienda[it].nombre ); textprintf_ex( buffer, "%8d", vlista_tienda[it].precio );

(FONT

*)datosjuego[dftextos].dat,

x_tienda+367,

int numi = it - inicia_objeto; if ( mouse_x > x_tienda+1 && mouse_x < x_tienda+470 && mouse_y > y_tienda+32+(numi*34) && mouse_y < y_tienda+64+(numi*34)) {

ty,

0xFFFFFF,

-1,

rect(buffer, x_tienda+2, y_tienda+34+(numi*34), x_tienda+468, y_tienda+63+(numi*34),0xffff00 ); tdesc.cambia_texto( descripcion_objeto(vlista_tienda[it].nid) ); tdesc.pinta(buffer); textprintf_ex( buffer, (FONT *)datosjuego[dftextos].dat, x_tienda+274, 0xFFFFFF, -1, "Tienes:%2d", jugador.cuantos_objetos(vlista_tienda[it].nid) ); if ( {

mouse_b&1 ) // // // if

intenta comprar objeto comprobar que existe hueco para la compra tiene dinero suficiente ? ( jugador.num_inventario() < 12 && jugador.getdinero() > vlista_tienda[it].precio )

{ sel_obj = it; }else{ if ( swerror == 0 ) { swerror = 1; } } }// fin se pulso el raton }// fin dentro de un objeto } if ( swerror == 2 ) swerror=0; if ( swerror == 1 && !mouse_b&1) { sonido_error(); swerror=2; } if ( sel_obj != -1 && !mouse_b&1 )

y_tienda,

{ jugador.menosdinero( vlista_tienda[sel_obj].precio ); jugador.obtiene_objeto( vlista_tienda[sel_obj].nid ); sel_obj = -1; sonido_boton(); } } // fin compras

//inicia ventas if ( tienda_estado == 2 ) { int obj_tienes = jugador.num_inventario(); if ( obj_tienes == 0 ) { textprintf_ex( buffer, (FONT *)datosjuego[dftextos].dat, x_tienda+75, y_tienda+185, 0xFFFFFF, -1, "No tienes objetos en tu inventario" ); }

if ( obj_tienes > 10 ) { //pinta barra desplazamiento rect(buffer, x_tienda+470, y_tienda, x_tienda+490, y_tienda+372,0xffffff ); rect(buffer, x_tienda+470, y_tienda+30, x_tienda+490, y_tienda+342,0xffffff ); triangle( buffer, x_tienda+473,y_tienda+25, x_tienda+487,y_tienda+25, x_tienda+480,y_tienda+5,0xffffff ); triangle( buffer, x_tienda+473,y_tienda+347, x_tienda+487,y_tienda+347, x_tienda+480,y_tienda+367,0xffffff ); // barra central // distancia unidad float ny1 = ( 340.0 - 32.0 ) / obj_tienes ; // parte superior por donde se inicia

int ny2 = int(inicia_objeto * ny1); // siempre se muestran 10 - longitud barra int ny3 = int(10.0 * ny1); rectfill(buffer, x_tienda+472, y_tienda+32+ny2, x_tienda+488, y_tienda+32+ny3+ny2, 0xffffff ); if ( mouse_x > x_tienda+470 && mouse_x < x_tienda+490 && mouse_y > y_tienda && mouse_y < y_tienda+30) { // esta situado encima de boton triangle( buffer, x_tienda+473,y_tienda+25, x_tienda+487,y_tienda+25, x_tienda+480,y_tienda+5,0xffff00 ); if ( mouse_b&1 ) { // pulsa hacia arriba inicia_objeto--; if (inicia_objeto < 0 ) inicia_objeto = 0; } } if ( mouse_x > x_tienda+460 && mouse_x < x_tienda+490 && mouse_y > y_tienda+342 && mouse_y < y_tienda+372) { // esta situado encima de boton triangle( buffer, x_tienda+473,y_tienda+347, x_tienda+487,y_tienda+347, x_tienda+480,y_tienda+367,0xffff00 ); if ( mouse_b&1 ) { // pulsa hacia arriba inicia_objeto++; if (inicia_objeto+10 > obj_tienes ) inicia_objeto= obj_tienes - 10; } } }

int it = 0; for ( int i=0; i < 12; i++ ) { if ( jugador.getinventario(i) != 0 && it < 10+inicia_objeto && it >= inicia_objeto) { int mid = jugador.getinventario(i); int ty = y_tienda+((it+1)*34) - (inicia_objeto*34); masked_blit( (BITMAP*)datobjetos[id_img_objeto(mid)].dat, buffer, 0,0,x_tienda,ty, 32,32); textprintf_ex( buffer, (FONT *)datosjuego[dftextos].dat, x_tienda+34, ty, 0xFFFFFF, -1, "%s", nombre_objeto(mid) ); textprintf_ex( "%8d", precio_objeto(mid) );

buffer,

(FONT

*)datosjuego[dftextos].dat,

x_tienda+367,

int numi = it - inicia_objeto; if ( mouse_x > x_tienda+1 && mouse_x < x_tienda+470 && mouse_y > y_tienda+32+(numi*34) && mouse_y < y_tienda+64+(numi*34)) { rect(buffer, x_tienda+2, y_tienda+34+(numi*34), x_tienda+468, y_tienda+63+(numi*34),0xffff00 ); tdesc.cambia_texto( descripcion_objeto(jugador.getinventario(i)) ); tdesc.pinta(buffer); if ( {

mouse_b&1 )

// vende objeto sel_obj = i; }// fin se pulso el raton

ty,

0xFFFFFF,

-1,

}// fin dentro de un objeto it++; }else{ if ( jugador.getinventario(i) != 0

&& it < inicia_objeto) it++;

} } if ( sel_obj != -1 && !mouse_b&1 ) { jugador.masdinero( precio_objeto(jugador.getinventario(sel_obj)) ); // objeto con id cero no existe jugador.pon_inventario( sel_obj, 0); sel_obj = -1; sonido_boton(); obj_tienes = jugador.num_inventario(); if ( obj_tienes == 11 ) inicia_objeto = 1; if ( inicia_objeto > 0 && obj_tienes < 11 ) inicia_objeto=0; } } // fin ventas

if ( comprar.ratonb() && tienda_estado != 1) { tienda_estado = 1; inicia_objeto = 0; sel_obj = -1; sonido_boton(); }

if ( vender.ratonb() && tienda_estado != 2) { tienda_estado = 2; inicia_objeto = 0; sel_obj = -1; sonido_boton(); } if ( salir.ratonb() ){ // oculta la flecha y sale swtienda = 0; muestra_tienda = false; sonido_boton(); } } }

La función pinta_tienda(), es la que se encarga de todo referente a la tienda. Primero se comprueba si se muestra la tienda. Se crea una variable tipo MENSAJE que se encarga de mostrar la descripción de los objetos. Cuando entra la primera vez swtienda vale cero y por tanto se inicializan todas la variables utilizadas en la función tienda. Se inicializan a cero las variables inicia_objeto, swinv, Y se inicializa a 1 la variable tienda_estado. La variable swtienda pasa a valer 1, para que la próxima vez que entre no se vuelvan a inicializar las variables. Y la variable sel_obj se inicializa a -1, para indicar que no hay ninguno seleccionado. Se crean tres botones, comprar, vender y salir. Que se declaran del tipo TBOTON. Al igual que las variables descripción, y precios. Y se inicializan utilizando la función crear, que definen el texto que tienen y el tipo de letra que se utilizará. Mediante la variable tienda_estado, se consideran dos posibles valores: 1 para cuando se muestra el listado de comprar, y 2 para cuando se muestra nuestros objetos para vender. En el caso de que se tengan que mostrar mas de 10 objetos entonces se pinta la barra de desplazamiento, que nos servirá para movernos por la lista de objetos. Todo se muestra con una posición relativa a las variables x_tienda, y_tienda. Para que se pueda situar donde queramos con tan solo

cambiar el valor de estas variables. En la opción comprar, la lista a mostrar es vlista_tienda. Y en la opción de venta, la lista que se muestra es el inventario del jugador. Si deseas descargar el archivo tiendas.h en RAR haz clic aqui. Y hasta aqui hemos llegado por hoy, aun no ha acabado esto de la tienda. Aunque ya tenemos el codigo principal, hay que intergrarlo en el juego.

Crear juego RPG en C++ y Allegro 4 (24) Tiendas III Aquí esta una nueva entrega del curso crea tu juego RPG en C++ y Allegro. Continuamos con las tiendas.

En la entrega anterior se puso la función pinta_tienda(), y se explicó un poco su funcionamiento. Ahora iremos explicando algunas partes del código. rect(buffer, x_tienda+470, y_tienda, x_tienda+490, y_tienda+372,0xffffff ); rect(buffer, x_tienda+470, y_tienda+30, x_tienda+490, y_tienda+342,0xffffff ); triangle( buffer, x_tienda+473,y_tienda+25, x_tienda+487,y_tienda+25, x_tienda+480,y_tienda+5,0xffffff ); triangle( buffer, x_tienda+473,y_tienda+347, x_tienda+487,y_tienda+347, x_tienda+480,y_tienda+367,0xffffff );

Este código se encarga de dibujar la barra de desplazamiento, dibujando dos rectángulos y dos triángulos. El comando rect recibe una imagen que es en la que se va a dibujar el rectángulo, y dos posiciones para indicar las dimensiones del rectángulo dando la esquina superior izquierda, y la esquina inferior derecha. Y finalmente se indica el color en el que se pinta. El comando triangle funciona de manera similar solo que ahora pide tres posiciones, las tres esquinas del triángulo. // distancia unidad float ny1 = ( 340.0 - 32.0 ) / vlista_tienda.size() ; // parte superior por donde se inicia int ny2 = int(inicia_objeto * ny1); // siempre se muestran 10 - longitud barra int ny3 = int(10.0 * ny1);

Estas variables contienen información para poder pintar la barra interior que va moviendose dentro de la barra de desplazamiento. ny1: contiene el tamaño total de la barra de desplazamiento dividido por la cantidad de objetos que se van a mostrar. ny2: contiene donde se va a situar la barra, según el elemento mostrado que se indica en inicia_objeto. ny3: contiene el tamaño de la barra. Como en el espacio que tenemos solo se puede mostrar 10 objetos, por eso se multiplica por 10. if ( mouse_x > x_tienda+460 && mouse_x < x_tienda+490 && mouse_y > y_tienda && mouse_y < y_tienda+30) { // esta situado encima de boton triangle( buffer, x_tienda+473,y_tienda+25, x_tienda+487,y_tienda+25, x_tienda+480,y_tienda+5,0xffff00 ); if ( mouse_b&1 )

{ // pulsa hacia arriba inicia_objeto--; if (inicia_objeto < 0 ) inicia_objeto = 0; } } if ( mouse_x > x_tienda+460 && mouse_x < x_tienda+490 && mouse_y > y_tienda+342 && mouse_y < y_tienda+372) { // esta situado encima de boton triangle( buffer, x_tienda+473,y_tienda+347, x_tienda+487,y_tienda+347, x_tienda+480,y_tienda+367,0xffff00 ); if ( mouse_b&1 ) { // pulsa hacia abajo inicia_objeto++; if (inicia_objeto+10 > vlista_tienda.size() ) inicia_objeto--; } }

Estas dos condiciones se encargan de controlar los dos botones de la barra de desplazamiento, la primera para el botón de arriba y la segunda para el de abajo. Se controla si esta encima de la imagen del botón y si se ha pulsado. Dependiendo de cual pulse se añade o se resta a la variable inicia_objeto, esta variable indica en que objeto se empieza a mostrar. for ( int it=inicia_objeto; it < vlista_tienda.size() && it < inicia_objeto+10; it++ ) { int ty = y_tienda+((it+1)*34) - (inicia_objeto*34); masked_blit( (BITMAP*)datobjetos[vlista_tienda[it].id].dat, buffer, 0,0,x_tienda,ty, 32,32); textprintf_ex( buffer, (FONT *)datosjuego[dftextos].dat, x_tienda+34, ty, 0xFFFFFF, -1, "%s", vlista_tienda[it].nombre ); textprintf_ex( buffer, "%8d", vlista_tienda[it].precio );

(FONT

*)datosjuego[dftextos].dat,

x_tienda+367,

ty,

0xFFFFFF,

-1,

int numi = it - inicia_objeto; if ( mouse_x > x_tienda+1 && mouse_x < x_tienda+470 && mouse_y > y_tienda+32+(numi*34) && mouse_y < y_tienda+64+(numi*34)) { rect(buffer, x_tienda+2, y_tienda+34+(numi*34), x_tienda+468, y_tienda+63+(numi*34),0xffff00 ); tdesc.cambia_texto( descripcion_objeto(vlista_tienda[it].nid) ); tdesc.pinta(buffer); textprintf_ex( buffer, (FONT *)datosjuego[dftextos].dat, x_tienda+274,

y_tienda,

0xFFFFFF, -1, "Tienes:%2d", jugador.cuantos_objetos(vlista_tienda[it].nid) ); if ( {

mouse_b&1 ) // // // if

intenta comprar objeto comprobar que existe hueco para la compra tiene dinero suficiente ? ( jugador.num_inventario() < 12 && jugador.getdinero() > vlista_tienda[it].precio )

{ sel_obj = it; }else{ if ( swerror == 0 ) { swerror = 1; } } }// fin se pulso el raton }// fin dentro de un objeto }

Con este bucle for se van mostrando los 10 posibles objetos que se pueden comprar. La variable ty contiene la posición absoluta donde se irán pintando cada una de las lineas. Con el comando masked_blit se pinta la imagen del objeto. Con los dos textprintf_ex se muestran el nombre y el precio del objeto. La variable numi se utiliza para obtener un valor que vaya de 0 a 10.

La condición comprueba si el ratón esta situado encima de alguno de los objetos, en ese caso se pinta un rectángulo que rodea al objeto y muestra la descripción del objeto utilizando la variable tdesc. En el caso de que se haga clic se comprueba que se tenga hueco en el inventario y dinero para comprarlo, en el caso que se pueda sel_obj toma el valor de it, que nos indica que objeto se a comprado. Todo esto se repite para la venta de objetos que tiene el jugador en el inventario, cambiando la lista de objetos que se mira, ahora se comprueba todos los objetos que se tiene en el inventario. if ( comprar.ratonb() && tienda_estado != 1) { tienda_estado = 1; inicia_objeto = 0; sel_obj = -1; sonido_boton(); } if ( vender.ratonb() && tienda_estado != 2) { tienda_estado = 2; inicia_objeto = 0; sel_obj = -1; sonido_boton(); } if ( salir.ratonb() ){ // oculta la flecha y sale swtienda = 0; muestra_tienda = false; sonido_boton(); }

Estas tres condiciones, se encarga de comprobar si se pulsa alguno de los botones, y realiza la acción correspondiente, ya sea cambiar a la ventana de compra, a la de venta o salir de la tienda. Espero que con esto haya quedado un poco mas claro en funcionamiento de la función. Seguimos con los cambios o añadidos que se deben hacer al código para tener operativo la tienda.

En el archivo audio.h, se añade un nuevo sonido. void sonido_error(){ play_sample ( (SAMPLE *)datosjuego[dserror].dat, 110,128, 1000, 0 ); }

En el archivo global.h se declara la nueva variable datotiendas. DATAFILE *datotiendas; int swtienda; int sel_obj; int swerror;

En el archivo mijuego.h. se quita la declaración del jugador, y se pone en el archivo players.h, después de la definición de la clase. player jugador;

En la función carga_juego(), se añade lo siguiente, justo después de cargar los otros archivos DAT. datotiendas = load_datafile("datostienda.dat"); if ( !datobjetos ){ allegro_message("Error: archivo datostienda.dat no encontrado\n%s\n", allegro_error); }

Y se modifica la cantidad de NPC, y enemigos, y se inicializan las nuevas variables. cambio = 0; npersonaje = 10; personajes[0].crea( personajes[1].crea( personajes[2].crea( personajes[3].crea( personajes[4].crea(

(BITMAP (BITMAP (BITMAP (BITMAP (BITMAP

*)datosjuego[diper001].dat, *)datosjuego[diper005].dat, *)datosjuego[diper005].dat, *)datosjuego[diper003].dat, *)datosjuego[diper005].dat,

1300,700, 1,1,3); 280, 450, 0,2,3); 230, 280, 3,2,3); 960, 310, 2,3,3); 1120, 450, 0,4,3);

personajes[5].crea( personajes[6].crea( personajes[7].crea( personajes[8].crea( personajes[9].crea(

(BITMAP (BITMAP (BITMAP (BITMAP (BITMAP

*)datosjuego[diper004].dat, *)datosjuego[diper006].dat, *)datosjuego[diper001].dat, *)datosjuego[diper007].dat, *)datosjuego[diper008].dat,

900, 850, 530, 334, 142,

650, 800, 280, 170, 170,

1,5,3); 0,0,3); 1,5,3); 0,0,4); 0,0,4);

nmalos = 3; 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); malos[2].crea( (BITMAP *)datosjuego[diene001].dat, 380, 240, 0,5,2,100); texto.crea("Demo tiendas. Ejemplo del Curso Crea tu juego RPG en C++ y Allegro ", font, 5,5,230,60 ); dialogo.crea("", PANTALLA_ALTO-10); hablando = 0;

(FONT

*)datosjuego[dftextos].dat,

10,

PANTALLA_ALTO-100,

mision = 1; swraton=-1; swinv=0; muestra_tienda = false; swtienda=0;

Este código sustituye al anterior que va desde la inicialización a cero de cambio hasta el final de la función. En la función carga_escenario(), se añade el nuevo escenario de la tienda. case 4:// tienda1 fondo = (BITMAP *)datosjuego[ditienda1].dat; choque = (BITMAP *)datosjuego[ditienda1choque].dat; cielo = (BITMAP *)datosjuego[ditienda1sup].dat; desplaza=false; sonido_abrirpuerta(); break;

PANTALLA_ANCHO-10,

Se añade un nuevo case, para el escenario de la tienda. Para indicar las imagenes del nuevo escenario. En la función cambia_escenario(), se sustituye el código a partir del case 3, hasta el final de la función, incluida el cierre de la llave ( } ). 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; } // color amarillo que existen muchos if ( cambio == 3 && desplazamiento_map_x > 800 ) { // cambiamos a otro lugar // tienda1 lugar = 4; carga_escenario(); // situamos al prota en el camino del bosque jugador.posiciona( 376,460 ); desplazamiento_map_x=-170; desplazamiento_map_y=-100; cambio = 0; } break; case 4: // tienda1 if ( cambio == 1) {

// cambiamos a la ciudad lugar=3; carga_escenario(); jugador.posiciona( 400,300 ); desplazamiento_map_x=1090; desplazamiento_map_y=85; cambio = 0; sonido_abrirpuerta(); } default: break; } }

Estos cambios, son para que desde la ciudad se pueda ir a la tienda el nuevo escenario, y desde la tienda se pueda volver a la ciudad. Todas las posiciones del personaje y desplazamiento del mapa depende del mapa, y solo son válido para nuestros mapas. Recuerda que si deseas añadir nuevos mapas, estos datos de posición del personaje y desplazamiento debe calcularlo uno mismo según las imagenes de los escenarios. En la función evento_escenario(), se añade un nuevo case, para controlar los eventos que hay dentro de la tienda. case 4: // en la tienda if ( personajes[8].getestado() == 6 && cambio == 0 && !personajes[8].posicion_cerca()) { personajes[8].cambia_estado(0); } if ( personajes[9].getestado() == 6 && cambio == 0 && !personajes[9].posicion_cerca()) { personajes[9].cambia_estado(0); } if ( cambio == 2 && jugador.getx()< 400 ) { personajes[9].cambia_estado(6);

cambio = 0; } if ( cambio == 2 && jugador.getx()> 400 ) { personajes[8].cambia_estado(6); cambio = 0; } if ( personajes[8].posicion_cerca() ) { personajes[8].cambia_estado(6); } if ( personajes[9].posicion_cerca() ) { personajes[9].cambia_estado(6); } if ( personajes[8].posicion_cerca(9) && personajes[8].alineado_vertical() && jugador.accion() && !personajes[8].posicion_cerca(2) ) { lee_tienda(2); muestra_tienda = true; } if ( personajes[9].posicion_cerca(9) && personajes[9].alineado_vertical() && jugador.accion() && !personajes[9].posicion_cerca(2) ) { lee_tienda(1); muestra_tienda = true; } if (

personajes[8].frente() && jugador.accion() && !jugador.hablando() && personajes[8].posicion_cerca())

{ dialogo.cambia_texto(" Y tu que estas mirando!! " ); hablando = 1; }

if ( personajes[9].frente() && jugador.accion() && !jugador.hablando() && personajes[9].posicion_cerca()) { dialogo.cambia_texto(" Vienes a comprar ? " ); 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;

Esto de los eventos de los personajes, se puede mejorar pero por el momento lo dejamos así. En este código anterior se controla los dos personajes que hay en la tienda, si nos colocamos delante de ellos detrás del mostrador se abrirá la tienda, en el caso de que nos acerquemos a ellos nos dirán unas frases. En la función pinta_juego(), al igual que en otras funciones se añade un nuevo case, para la tienda. case 4: // tienda1 bx = 170; by = 100; ancho = 448; alto = 416;

break;

También se tiene que añadir la llamada a la función pinta_tienda(), justo después de pinta_inventario(), para que de este modo la tienda lo oculte todo. En el archivo players.h, en la clase player se añade una nueva variable privada: int dinero;

Y se añaden nuevas funciones int getdinero(){ return dinero; }; void masdinero(int n){ dinero = dinero + n; }; void menosdinero(int n){ dinero = dinero - n; }; // devuelve cuantos objetos tienes en el inventario int num_inventario(); int cuantos_objetos(int id);

La función player::cuantos_objetos(), se encarga de recorrer todo el vector inventario y devuelve cuantos objetos hay en el inventario con el mismo id. Y la funcion player::num_inventario() devuelve la cantidad de objetos que hay en el inventario. int player::cuantos_objetos(int id) { int n=0; for ( int i=0; i < 12; i++) { if ( inventario[i] == id ) n++; } return n; } int player::num_inventario() { int n=0; for ( int i=0; i < 12; i++)

{ if ( inventario[i] != 0 ) n++; } return n; }

En la función player::inicia(), se inicializa la nueva variable precio. dinero = 50000;

En el archivo npc.h, en la clase npc se añaden nuevas funciones. bool posicion_cerca(int num=0); void cambia_estado(int _estado){ estado = _estado; }; int getestado(){ return estado; }; bool frente(); bool alineado_vertical();

Y a continuación tienen las nuevas funciónes: bool npc::alineado_vertical(){ int jx = jugador.getx() + desplazamiento_map_x; int jy = jugador.gety() + desplazamiento_map_y; return (

y+desplazamiento*2 < jy

&& abs(jx-x) 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; } // num distancia considerada cercana en pasos bool npc::posicion_cerca(int num) { int _x = jugador.getx() + desplazamiento_map_x; int _y = jugador.gety() + desplazamiento_map_y; int d = 32 + (desplazamiento*(2+num)); int d2 =abs ( _x - x ) + abs ( _y - y ); return d2 max_op) op=1; 55 if (op < 1) op=max_op; 56 57 // comprueba si esta en la opcion de salir 58 if ((key[KEY_ENTER] || key[KEY_ENTER_PAD]) && op == max_op ) salidamenu = true; 59 60 if (key[KEY_ESC]) op=max_op; 61 62 return op; 63}; 64 65bool MIMENU::salir(){ 66 return salidamenu; };

La variable NUMOP contiene el numero de opciones que tiene el menu, en este caso 3. Se crea una clase llamada MIMENU, que se encarga de mostrar el menu, y controlar si se pulsa las teclas del cursor para cambiar la opcion seleccionada. op: contiene la opcion que esta actualmente seleccionada. max_op: contiene el máximo de opciones que tiene el menu. salidamenu: indica si se ha salido o no del menu. En este caso se sale cuando se pulsa la tecla ENTER o INTRO. pulsa: indica si se ha pulsado algunas de las teclas que controla el menu, la tecla del cursor arriba y la tecla abajo. La función MIMENU::MIMENU(), inicializa las variables. La función MIMENU::pinta(), se encarga de pintar según el valor de op una de las opciones. La función MIMENU::teclado(), se encarga de controlar las teclas del cursor, para que cambien el valor de op. Si se pulsa la tecla abajo aumenta en 1 el valor de op, y si se pulsa arriba disminuye en 1, cuando el valor de op es menor que 1 se cambia a valer el max_op que es 3. Y cuando el valor de op supera max_op, op vale 1. De esta forma solo tendras tres posibles valores 1,2 y 3. Si se pulsa la tecla ESC, el valor de op es 3. Lo que indica que se selecciona la ultima opción que en este caso es SALIR. Si se pulsa alguna de las teclas ENTER o INTRO y op es igual a 3, entonces pone a true salidamenu, para indicar que se a pulsado SALIR. La función MIMENU::salir(), se utiliza para obtener el valor de la variable salidamenu, que es la que nos indica si se ha pulsado SALIR. Haz clic aqui para descargar en RAR el archivo menu.h En el archivo global.h, se declaran nuevas imagenes. BITMAP *cbuffer; BITMAP *menufondo; BITMAP *menufondo2;

La imagen cbuffer, contendrá el mapa de choque actual que se está mostrando por pantalla. La imagen menufondo, y menufondo2, estas dos variables BITMAP tendrán las imágenes que se utilizan para el menú inicio del juego. En el archivo players.h, se añade una nueva función llamada pon_choque(), que se encargará de añadir un rectángulo de choque en la imagen de choque cbuffer. void player::pon_choque() { rectfill( cbuffer, x+4, y+17, x+28, y+30, 0xffffff); }

La funcion player::choca(), cambia la forma de controlar el choque, se realiza mediante la imagen cbuffer, y por tanto se simplifica, ya que no es necesario calcular la posición con respecto al mapa, sino que se realiza con respecto a la pantalla. bool player::choca() { bool resp=false; for (int i=2; i < 30; i++ ) { for (int j=16; j < 32; j++) { // color rojo if ( getpixel( cbuffer, x+i, y+j) == 0xff0000 ) resp=true; // color verde if ( getpixel( cbuffer, x+i, y+j) == 0x00ff00 ) { cambio = 1; resp=true; } // color azul if ( getpixel( cbuffer, x+i, y+j) == 0x0000ff ) {

cambio = 2; resp=true; } // color amarillo if ( getpixel( cbuffer, x+i, y+j) == 0xffff00 ) { cambio = 3; resp=true; } } } return resp; }

En la función player::teclado(), se ha quitado el control del mapa de choque. void player::teclado() { int ax = x; int ay = y; if ( !hablar && !swtienda ) { // 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 ( key[KEY_SPACE] && ataca == 0 ) { ataca = 1; } if ( !key[KEY_SPACE] && ataca == 1 ) { ataca = 2; sonido_espada_aire(); } } if ( ax != x || ay != y ) { if ( choca() ) { x =ax; y =ay; }else{ 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; } } } if ( ataca > (FRAME_RATE / 4) ) ataca = 0; }

La función player::cambia_escenario(), se elimina ya que no es necesario.

Haz clic aqui para descargar RAR del archivo players.h En el archivo npc.h, se ha cambiado la clase npc. class npc { protected: // posicion int x,y; int ax,ay; int direccion; int animacion; int escena; int estado; int img; BITMAP* imagen; public: void void void bool bool void bool bool

crea( int i, int _x, int _y, int dir, int _estado, int _lugar ); pinta(); actualiza(); chocanpc(); posicion_cerca(int num=0); cambia_estado(int _estado){ estado = _estado; }; frente(); alineado_vertical();

const const const const const

int int int int int

getimg() { getx() { gety() { getdir() { getestado(){

return return return return return

img; }; x; }; y; }; direccion; }; estado; };

const int getlugar() { return escena; }; };

Se ha eliminado las variables mifondo y primer. Se ha cambiado la función crear y se ha añadido algunas funciones para poder obtener los datos del npc para guardarlos. void npc::crea( int i, int _x, int _y, int dir, int _estado, int _lugar) { x = _x; y = _y; direccion = dir; animacion = 0; escena = _lugar; img = i; BITMAP *_img = (BITMAP *)datosjuego[i].dat; imagen = create_bitmap(_img->w, _img->h); blit( _img, imagen, 0,0, 0,0, _img->w, _img->h); estado = _estado; }

Antes la función npc::crea(), recibía una imagen bitmap. Ahora recibe un índice, mediante el cual accede a la imagen utilizando (BITMAP *)datosjuego[i].dat; La función npc::pinta() cambia por completo. void npc::pinta() { if ( lugar == escena ) { actualiza(); int vx = x - desplazamiento_map_x;

int vy = y - desplazamiento_map_y; rectfill( cbuffer, vx+2, vy+1, vx+30, vy+31, 0xff0000); masked_blit(imagen, buffer, animacion*32, direccion*32, vx, vy, 32,32); } }

La función npc::chocanpc(), tambien cambia ya que se utiliza la imagen de choque cbuffer. 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; } int vx; int vy; // comprobar si colisiona con el mapa for ( int ci=ninix; ci < nfinx; ci++) { for (int cj=niniy; cj < nfiny; cj++) { vx = x - desplazamiento_map_x; vy = y - desplazamiento_map_y; // color rojo if ( getpixel( cbuffer, vx+ci, vy+cj) == 0xff0000 || getpixel( choque, x+ci, y+cj) == 0xff0000 ){ return true; } // color blanco prota if ( getpixel( cbuffer, vx+ci, vy+cj) == 0xffffff ){ return true; } } } return false; }

La función npc::actualiza() cambia, ahora no tiene que controlar el manejo de la imagen de choque. 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; } }

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; } } 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; } } 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; } } 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; } } break; default: // parado if ( tiempo_total % 300 == 0 ) { direccion = rand()%4; } break; } } }

Al igual que en la clase npc, la clase enemigo se cambia a la hora de crear. No requiere una imagen, sino un id de la imagen. Y se cambia el manejo del choque, se utiliza cbuffer. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

class enemigo : public npc { int vida; int v_actual; bool muerto; int golpeado; int exp; public: void crea( int i, 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(); void daexp(int e){ exp = e; }; const int getvida() { return vida; }; }; void enemigo::crea( int i, int _x, int _y, int dir, int _estado, int _lugar, int v ) { x = _x; y = _y; direccion = dir; animacion = 0; escena = _lugar; img=i; BITMAP *_img = (BITMAP *)datosjuego[i].dat; imagen = create_bitmap(_img->w, _img->h); blit( _img, imagen, 0,0, 0,0, _img->w, _img->h); estado = _estado; vida = v; v_actual = vida; muerto = false; golpeado=0; exp = 50; };

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

void enemigo::herida( int d ) { if ( !muerto ) { int num = FRAME_RATE / 2; v_actual-=d; if ( v_actual x ) 147 { 148 direccion = 2; 149 }else{ 150 direccion = 1; 151 } 152 153 if ( jy > y ) 154 { 155 if ( abs ( jx - x ) < desplazamiento*3 ) 156 {

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

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; } }

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231}

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; } } 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; } } }

Ya no se utiliza directamente la imagen choque, para poner el choque del npc y el enemigo. Esto se controla con la nueva imagen cbuffer, que almacena el mapa de choque de lo que se ve por pantalla.

Haz clic aqui para descargar en RAR el archivo npc.h En el archivo main.cpp, se añade dos archivos nuevos menu.h y partidas.h. #include #include #include #include #include #include #include #include #include #include #include #include #include #include

< iostream > < allegro.h > "log.h" "datosjuego.h" "botones.h" "global.h" "audio.h" "menu.h" "misobjetos.h" "dialogos.h" "players.h" "tiendas.h" "mijuego.h" "partidas.h"

En la función inicia_allegro(), se añade la inicialización de la nueva variable cbuffer, justo despues de inicializar buffer. cbuffer = create_bitmap(PANTALLA_ANCHO, PANTALLA_ALTO);

El main() cambia por completo, para añadir el menu. ? 1 2 3 4 5 6 7 8 9

int main() { inicia_allegro(); carga_inicio(); musica_menu();

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

int op; MIMENU menu; do{ blit(menufondo, buffer, 0, 0, 0, 0, PANTALLA_ANCHO, PANTALLA_ALTO); menu.pinta(); op = menu.teclado(); // se a seleccionado una opcion if (key[KEY_ENTER] || key[KEY_ENTER_PAD]) { if ( op == 2 ) { // carga partida carga_partida(); sonido_sube_nivel(); } if ( op == 1 ) { jugador.inicia(); lugar = 1; desplazamiento_map_x=-160; desplazamiento_map_y=-160; sonido_sube_nivel(); npersonaje = 10; personajes[0].crea( personajes[1].crea( personajes[2].crea( personajes[3].crea( personajes[4].crea( personajes[5].crea( personajes[6].crea( personajes[7].crea( personajes[8].crea( personajes[9].crea(

diper001, diper005, diper005, diper003, diper005, diper004, diper006, diper001, diper007, diper008,

1300,700, 1,1,3); 280, 450, 0,2,3); 230, 280, 3,2,3); 960, 310, 2,3,3); 1120, 450, 0,4,3); 900, 650, 1,5,3); 850, 800, 0,0,3); 530, 280, 1,5,3); 334, 170, 0,0,4); 142, 170, 0,0,4);

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87

nmalos = 3; malos[0].crea( malos[1].crea( malos[2].crea( malos[3].crea(

diene001, diene001, diene001, diper005,

380, 400, 380, 440,

280, 720, 240, 720,

3,5,2,100); 0,5,2,100); 0,5,2,100); 3,5,2,100);

} if ( op == 1 || op == 2 ) { carga_juego(); // juego salir = false; while ( !salir ) { // tecla de salida if ( key[KEY_ESC] ) salir = true; 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); } }

88 if ( lugar == 2 ) para_sonido_ambiente(); 89 // fin juego 90 guarda_partida(); 91 musica_menu(); 92 op=2; 93 } 94 } 95 96 pintar_pantalla(); 97 98 }while ( !menu.salir() ); 99 100 101 destroy_bitmap(fondo); 102 destroy_bitmap(choque); 103 destroy_bitmap(cielo); 104 105 destroy_bitmap(menufondo); 106 destroy_bitmap(menufondo2); 107 destroy_bitmap(cbuffer); 108 destroy_bitmap(buffer); 109 110 return 0; 111} 112END_OF_MAIN();

Se hace que cuando salgas de la partida, se guarde automáticamente. Haz clic aqui para descargar en RAR el archivo main.cpp Y hasta aquí llega este curso, aun falta un próximo curso en el que detalla los cambios realizados en el archivo mijuego.h. Por tanto, aun no se puede compilar pues no se tiene todo.

Crear juego RPG en C++ y Allegro 4 (28) Menu II Aquí esta una nueva entrega del curso crea tu juego RPG en C++ y Allegro. Continuamos con el menu.

Ya solo nos queda actualizar el archivo mijuego.h. Se crea una nueva función llamada carga_inicio(), que contiene la carga de los ficheros .DAT, la lectura de los objetos y las imagenes del menú. ? 1 void carga_inicio() 2 { 3 packfile_password(evalc);

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25}

datosjuego = load_datafile("datosjuego.dat"); if ( !datosjuego ){ allegro_message("Error: archivo datosjuego.dat no encontrado\n%s\n", allegro_error); } datobjetos = load_datafile("objetos.dat"); if ( !datobjetos ){ allegro_message("Error: archivo objetos.dat no encontrado\n%s\n", allegro_error); } datotiendas = load_datafile("datostienda.dat"); if ( !datobjetos ){ allegro_message("Error: archivo datostienda.dat no encontrado\n%s\n", allegro_error); } lee_objetos(); menufondo = (BITMAP *)datosjuego[dimenu].dat; menufondo2 = (BITMAP *)datosjuego[dimenu2].dat;

Se crea una función para actualizar el contenido de la variable cbuffer, que contendrá el mapa de choque de lo que se muestra por pantalla, que se llama carga_mapa_choque(). ? 1void carga_mapa_choque() 2{ 3 clear_to_color( cbuffer, makecol(0, 0, 0)); 4 blit ( choque, cbuffer, desplazamiento_map_x, desplazamiento_map_y,0,0, PANTALLA_ANCHO, PANTALLA_ALTO); 5 jugador.pon_choque(); 6}

Se reduce el contenido de la función carga_juego(), ya que parte de lo que tenía se ha trasladado a la función carga_inicio(). ?

void carga_juego() 1 { 2 cambio = 0; 3 carga_escenario(); 4 5 dialogo.crea("", (FONT *)datosjuego[dftextos].dat, 10, PANTALLA_ALTO-100, PANTALLA_ANCHO-10, 6 PANTALLA_ALTO-10); 7 hablando = 0; 8 9 mision = 1; 10 swraton=-1; 11 12 swinv=0; 13 muestra_tienda = false; 14 swtienda=0; 15 }

Se realizan algunos cambios en la función carga_escenario(). ? 1 // carga los datos del escenario segun lugar 2 void carga_escenario() 3 { 4 switch ( lugar ) 5 { 6 case 1:// casa 7 fondo = (BITMAP *)datosjuego[dicasa].dat; 8 choque = (BITMAP *)datosjuego[dicasachoque].dat; 9 cielo = (BITMAP *)datosjuego[dicasasup].dat; 10 11 carga_mapa_choque(); 12 desplaza = false; 13 if ( cambio != 0 ) sonido_abrirpuerta(); 14 musica_casa(); 15 break; 16 17 case 2:// bosque 18 fondo = (BITMAP *)datosjuego[dibosque].dat;

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47}

choque = (BITMAP *)datosjuego[dibosquechoque].dat; cielo = (BITMAP *)datosjuego[dibosquesup].dat; desplaza=true; carga_mapa_choque(); 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; carga_mapa_choque(); if ( cambio == 0 ) musica_ciudad1(); break; case 4:// tienda1 fondo = (BITMAP *)datosjuego[ditienda1].dat; choque = (BITMAP *)datosjuego[ditienda1choque].dat; cielo = (BITMAP *)datosjuego[ditienda1sup].dat; carga_mapa_choque(); desplaza=false; if ( cambio != 0 ) sonido_abrirpuerta(); if ( cambio == 0 ) musica_ciudad1(); break; }

En la función cambia_escenario(), se suprime algunos sonidos, y se añade la llamada a la función carga_mapa_choque(). ? 1 void cambia_escenario() 2 { 3 4 switch ( lugar ) 5 {

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

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

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

jugador.posiciona( 500,540 ); desplazamiento_map_x=950; desplazamiento_map_y=508; para_sonido_ambiente(); musica_ciudad1(); 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; } // color amarillo que existen muchos if ( cambio == 3 && desplazamiento_map_x > 800 ) { // cambiamos a otro lugar // tienda1 lugar = 4; carga_escenario(); // situamos al prota en el camino del bosque jugador.posiciona( 376,460 ); desplazamiento_map_x=-170; desplazamiento_map_y=-100; cambio = 0; } break; case 4: // tienda1 if ( cambio == 1) {

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99}

// cambiamos a la ciudad lugar=3; carga_escenario(); jugador.posiciona( 400,300 ); desplazamiento_map_x=1090; desplazamiento_map_y=85; cambio = 0; sonido_abrirpuerta(); } default: break; } carga_mapa_choque();

Se realizan algunos cambios en la función pinta_juego(). ? 1 // Se encarga de pintar todo sobre el buffer 2 void pinta_juego() 3 { 4 int ancho, alto; 5 int ax=0; 6 int ay=0; 7 int bx=0; 8 int by=0; 9 10 switch ( lugar ) 11 { 12 case 1: // casa 13 bx=160; 14 by=160; 15 ancho = 480; 16 alto = 325; 17 break; 18 case 2: // bosque

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

ax = desplazamiento_map_x; ay = desplazamiento_map_y; ancho = PANTALLA_ANCHO; alto = PANTALLA_ALTO; break; case 3: // ciudad1 ax = desplazamiento_map_x; ay = desplazamiento_map_y; ancho = PANTALLA_ANCHO; alto = PANTALLA_ALTO; break; case 4: // tienda1 bx = 170; by = 100; ancho = 448; alto = 416; break; default: break; } blit( fondo, buffer, ax, ay, bx, by, ancho, alto); for ( int z=0; z < npersonaje; z++ ) { personajes[z].pinta(); } for ( int z=0; z < nmalos; z++ ) { malos[z].pinta(); } jugador.pinta(); masked_blit( cielo, buffer, ax, ay, bx, by, ancho, alto); if ( hablando > 1 ) {

58 59 60 61 62 63 64 65 66 67}

dialogo.pinta(buffer); hablando++; } pinta_barra_vida(); pinta_lvlup(); pinta_inventario(); pinta_tienda();

Y llegado a este punto ya estaría todos los cambios realizados en el archivo mijuego.h. Haz clic aqui para descargar en RAR el archivo mijuego.h Para que todo vaya correctamente debe de tener los siguientes archivos: Archivo datosjuego.dat y datosjuego.h, que contienen imagenes, sonidos, etc. Descargar Archivos de los objetos y la tienda, que incluye objetos.dat, objetos.txt, datostienda.dat, tiendas.txt, list01.txt y list02.txt. Descargar

Y si todo esta correcto debes tener algo similar a lo que se muestra en el siguiente video.

Crear juego RPG en C++ y Allegro 4 (29) Control de Errores Aquí esta la última entrega del curso crea tu juego RPG en C++ y Allegro, el control de errores. Se debe de minimizar la cantidad de posibles errores.

Para reducir la cantidad de posibles errores se utiliza la herramienta grabber para unificar todos los archivos multimedia en un archivo .DAT, de esta manera es mas fácil de comprobar su existencia.

A medida que se ha ido haciendo el juego se han añadido nuevos archivos, por ejemplo, los archivos datostienda.dat y objetos.dat. Si alguno de estos archivos falta el juego no funciona y da un error.

Este error esta contemplado, es decir, te avisa de que no existe el archivo .dat, pero no se realiza nada mas. Ya que el programa no funciona en el caso de que falte alguno de los archivos, se debe de hacer que en cuanto falte algún archivo aparte de avisar, que se salga del programa, evitando así otros posibles errores incontrolables. El juego consta de los siguientes archivos: RPG.exe - El ejecutable de la aplicación datostienda.dat - Imagenes, sonidos, etc. objetos.dat - Imagenes de los objetos. objetos.txt - Información de todos los objetos que aparecen en el juego. datostienda.dat - Imagenes de fondos para las tiendas. tiendas.txt - tipos de tiendas que hay en el juego. list01.txt - lista de objetos de una tienda list02.txt - lista de objetos de una tienda Esta lista es según el ejemplo del curso, pero si se han añadido mas mapas, y mas tiendas es posible que se aumente el numero de archivos que utiliza el juego. Por cada uno de los archivos, deberíamos de comprobar su existencia, y en el caso de no encontrarlo salir del juego.

¿ Que otros tipos de errores se pueden tener ? Como existen varios archivos de texto (.txt), que se hicieron así para que cada uno pueda añadir sus objetos, tiendas, etc. Se puede dar el caso que aunque exista el archivo, este archivo no contenga una información correcta para el programa. En ese caso cuando el juego intente acceder a la información es muy probable que salte un error.

Este tipo de errores es muy difícil de detectar, por ello es recomendable tener siempre un orden, y un formato a la hora de acceder a la información, para intentar detectar si se a leído algún dato erróneo. De esta manera, poder parar la ejecución del juego e indicar que existe algún problema al leer el archivo. También pueden contener información errónea si el usuario del juego intenta manipular dichos archivos, por ello es recomendable que estén protegidos. Los errores de ejecución son los más difíciles de detectar, ya que requiere de que alguien testee el juego, y compruebe todas las posibles opciones. Evidentemente no se pueden controlar todos los posibles errores, pero cuantos mas tengamos en cuenta esto determinará la estabilidad de nuestro juego. Otro error que no se controla es ... ¿ que ocurre si la primera vez le damos a cargar partida ? ... El programa no controla si existe el archivo, y debería comprobar si existe, en el caso de no existir debería ser igual que cuando das a iniciar partida.

Programación Se crea una variable que contenga si existe algún error, en nuestro caso se ha llamado la variable sys_error, esta variable como se va a utilizar en todos las funciones nos interesa que sea global, por tanto su definición se realiza en global.h. int sys_error;

Se considera sin error en el caso de que la variable tenga como valor cero. Se debe añadir en todas las condiciones que comprueban la existencia de los ficheros, de modo de que cuando no existan alguno, la variable sys_error tenga una valor, y dependiendo de este valor se sabe que tipo de error es. En este caso, pondré un ejemplo de como hacerlo. En el archivo mijuego.h, en la función carga_inicio(). datosjuego = load_datafile("datosjuego.dat"); if ( !datosjuego ){ allegro_message("Error: archivo datosjuego.dat no encontrado\n%s\n", allegro_error); sys_error = 7; }

En este ejemplo, se carga el archivo datosjuego.dat. En el caso de no existir da un mensaje de error: "Error: archivo datosjuego.dat no encontrado", y además se le asigna el valor 7 a la variable sys_error que en mi caso es el que indica que el archivo datosjuego.dat no existe. De esta forma, mirando el valor de la variable sys_error el programa puede saber si ha habido un error, ya que siempre debe valer cero en el caso de no tener ningún error. Una vez detectado el error, se debe evitar que continúe el programa. Para ello se añade lo siguiente en el archivo main.cpp en la función main().

// programa principal int main() { sys_error = 0; inicia_allegro(); carga_inicio(); musica_menu(); int op; MIMENU menu; while ( !menu.salir() && sys_error == 0 ) {

De esta forma el bucle principal se detiene en cuanto la variable sys_error sea distinto de cero, evitando así que se generen mas errores. if ( sys_error != 0 ) { allegro_message("Se ha encontrado un Error: %d\n", sys_error); } return 0; } END_OF_MAIN();

Añadiendo esto último, se mostrará por pantalla una ventana indicando el error que se ha detectado. En el caso de querer cargar partida cuando no existe ninguna partida, es un error que se puede solucionar, si se da este caso se debe iniciar partida de forma normal. Por un lado hay que controlar si existe la partida. En el archivo partidas.h en la función carga_partida(), se añade una condición que controle si ha habido algún error al cargar la partida.

fichero = pack_fopen("partida.sav","r"); if ( fichero ) { ... pack_fclose(fichero); }else{ // no existe partida guardada sys_error = 6; }

En el caso de no existir el archivo, sys_error toma el valor 6, que en este ejemplo es el que representa que no existe la partida guardada. Luego se debe añadir el control del error, para que se inicie una partida nueva. En el archivo main.cpp, en la función main(). if ( op == 2 ) { // carga partida carga_partida(); if ( sys_error == 6 ) { // pasamos a partida nueva op = 1; sys_error = 0; } } if ( op == 1 ) { // partida nueva

De este modo, cuando sys_error vale 6, se asigna a op el valor 1 para indicar que se inicia una partida nueva, y se pone el valor cero a la variable sys_error para indicar que se ha solucionado el error.

Introduccion Curso RPG Este curso es una nueva edición del curso "Crea tu juego RPG". Se trata de un curso que irá paso a paso explicando como crear tu propio juego. Concretamente un juego al mas puro estilo RPG, utilizando el lenguaje de programación C++ y la librería Allegro 4.4.

Aprende a programar tu propio juego RPG. Para facilitar la creación del juego y poder centrarnos en la programación, las imágenes que se utilizan para este curso son sacadas del programa RPG-Maker. Es por ello, que se utilizará para la imagen del protagonista un "character set" del RPG Maker.

Creando un personaje Se programará la creación un personaje principal, que se pueda desplazar con las teclas del cursor. Para facilitar la creación de este ejemplo y poder centrarnos en la programación se utilizará un "Character set" del RPG Maker.

¿ Que es un Character set del RPG Maker ? Es una imagen, donde esta dibujado un personaje según las cuatro direcciones: abajo, izquierda, derecha y arriba. Y cada una de ellas tiene tres variantes para hacer una pequeña animación. En nuestro ejemplo utilizaremos la siguiente imagen.

Haga Click para Descargar La librería Allegro 4.4 trabaja con imágenes BMP, el color rosa es el que utiliza Allegro para las transparencias.

Programación ? 1 #include

2 3 int main() 4 { 5 allegro_init(); 6 install_keyboard(); 7 8 set_color_depth(32); 9 set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0); 10 11 BITMAP *buffer = create_bitmap(800, 600); 12 BITMAP *prota = load_bmp("personaje.bmp",NULL); 13 14 bool salir; 15 int x,y; 16 17 // inicializar vbles 18 x = 10; 19 y = 10; 20 salir = false; 21 22 23 while ( !salir ) 24 { 25 clear_to_color(buffer, 0xaaaaaa); 26 27 masked_blit(prota, buffer, 0,0, x, y, 32,32); 28 29 // teclas control usuario 30 if ( key[KEY_UP] ) 31 { 32 y--; 33 } 34 if ( key[KEY_DOWN] ) 35 { 36 y++; 37 } 38 if ( key[KEY_LEFT] ) 39 { 40 x--;

41 } 42 if ( key[KEY_RIGHT] ) 43 { 44 x++; 45 } 46 47 blit(buffer, screen, 0, 0, 0, 0, 800, 600); 48 49 rest(10); 50 51 // tecla de salida 52 if ( key[KEY_ESC] ) salir = true; 53 54 } 55 56 destroy_bitmap(prota); 57 destroy_bitmap(buffer); 58 59 return 0; 60} 61END_OF_MAIN();

Paso a Paso #include Incluye la libreria Allegro, para poder hacer uso de sus funciones y comandos. int main(){ Se inicia la función principal allegro_init(); Inicializa los valores de las variables de la libreria Allegro. install_keyboard();

Instala el controlador de interrupciones del teclado Allegro. Antes de utilizar cualquier rutina de entrada del teclado debe llamarse a esta función. set_color_depth(32); Establece el formato de profundidad de los pixels. Los valores posibles son 8,15,16,24 y 32 bits. set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0); Establece la resolución y el tipo de pantalla utilizada. En este caso se indica que será modo ventana, con unas dimensiones de 800x600 pixel. BITMAP *buffer = create_bitmap(800, 600); Se crea una imagen bitmap de 800x600 y se guarda en el variable buffer. Esta imagen almacena todo antes de mostrarse por pantalla. BITMAP *prota = load_bmp("personaje.bmp",NULL); Se carga un BMP, llamado personaje.bmp. Y se guarda en la variable prota. bool salir; Se crea una variable del tipo booleano llamada salir. Se utiliza para el bucle principal. int x,y; Se crea dos variables enteras: x,y. Se utilizan para posicionar la imagen en la pantalla. // inicializar vbles x = 10; y = 10; salir = false; Cuando esta precedido de "//" indica que es un comentario. Las variables son inicializadas a un valor, ya que hasta el momento pueden contener cualquier valor. while ( !salir ) {

Bucle que se repite mientras se cumpla la condición. En este caso se repite mientras no sea salir. clear_to_color(buffer, 0xaaaaaa); Borra el contenido de la imagen buffer, rellenandolo con un color gris (0xaaaaaa). masked_blit(prota, buffer, 0,0, x, y, 32,32); Pinta la imagen prota sobre la imagen buffer, en la posicion x,y dentro de la imagen buffer. Con un tamaño de la imagen prota de 32x32. // teclas control usuario if ( key[KEY_UP] ) { y--; } if ( key[KEY_DOWN] ) { y++; } if ( key[KEY_LEFT] ) { x--; } if ( key[KEY_RIGHT] ) { x++; } Este conjunto de condiciones se encarga de controlar cuando se pulsa alguna de las cuatro teclas de dirección y en el caso de ser pulsadas modifica la posición (x,y). blit(buffer, screen, 0, 0, 0, 0, 800, 600); Vuelca el contenido de la imagen buffer sobre screen, que es la pantalla, con un tamaño que previamente se a definido, 800x600. Es en este instante en el que se muestra todo por pantalla.

rest(10); Hace una pausa de 10 milisegundos. // tecla de salida if ( key[KEY_ESC] ) salir = true; Se define la tecla ESC como tecla para salir, es por ello que se controla si se ha pulsado, en ese caso cambia el valor de la variable salir. } Fin del bucle principal destroy_bitmap(prota); destroy_bitmap(buffer); Elimina las variables prota y buffer, liberando la memoria ocupada por dichas imagenes. return 0; } END_OF_MAIN(); Devuelve 0 la funcion principal. Y finaliza el programa.

Animación Personaje Para crear la animación de nuestro personaje debemos tener varias imágenes del personaje en distintas posiciones.

Esta entrada es la continuación de "Creando un personaje", por tanto para completar el programa es necesario haber hecho antes el anterior. La imagen contiene tres imágenes para cada una de las posibles direcciones. El tamaño del personaje es de 32x32. Empezando de arriba a abajo las direcciones son: abajo, izquierda, derecha y arriba. Esto es importante ya que la imagen se va a tratar como una matriz de 4x3. Mediante una variable llamada direccion se controla la dirección del personaje, y puede tener los siguientes valores: 0 : Abajo 1 : Izquierda 2 : Derecha 3 : Arriba Y para el control de la animación se crea la variable animacion, que puede tener 3 valores, del cero al 2. Con estas dos variables se consigue que mediante una única sentencia de pintar seamos capaz de mostrar cualquier imagen del personaje. Quedando de la siguiente forma:

? 1 masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32);

Con el comando masked_blit pinta al personaje con el fondo transparente. Pinta de la imagen prota "un trozo" de 32x32, que esta definida por los dos últimos parámetros, sobre la imagen buffer, en la posicion x,y. "El trozo" esta definido segun: animacion*32, direccion*32. Ambas variables se multiplican por 32 ya que es el tamaño del personaje, y de este modo se selecciona la imagen deseada.

Ejemplos: Ejemplo 1. animacion = 0; direccion = 2; Se ha seleccionado el personaje mirando hacia la derecha.

Ejemplo 2. animacion = 2; direccion = 0; Se ha seleccionado el personaje mirando hacia abajo.

Se añade a cada condición que controla el teclado, el uso de la variable direccion, asignandole el valor que le corresponde. ? 1 if ( key[KEY_UP] ) 2 { 3 y-=desplazamiento; 4 direccion = 3; 5 } 6 if ( key[KEY_DOWN] ) 7 { 8 y+=desplazamiento; 9 direccion = 0; 10} 11if ( key[KEY_LEFT] ) 12{ 13 x-=desplazamiento; 14 direccion = 1; 15}

16if ( key[KEY_RIGHT] ) 17{ 18 x+=desplazamiento; 19 direccion = 2;

Y a continuación para la animacion, para que vaya cambiando su valor pero de forma controlada podemos hacerlo de la siguiente forma: ? 1animacion++; 2if ( animacion > 2 ) animacion = 0;

De este modo conseguimos que siempre este su valor entre 0 y 2. Recuerda que al principio del programa deberás de declarar las dos variables e inicializarlas a cero.

El primer Escenario (1) En esta entrega se hará un escenario que pueda recorrer nuestro personaje.

Para crear nuestro escenario se utilizarán tres imágenes. Una imagen del escenario, que se utilizará de fondo y se almacenará en la variable "fondo". Una segunda imagen que contendrá las partes de nuestro escenario que están por encima del personaje, que se guarda en la variable "cielo". Y una tercera imagen para controlar la colisión, de esta manera nuestro personaje no podrá atravesar todo por donde quiera. Esta imagen se guarda en la variable "choque". A continuación tenéis un ejemplo de las tres imágenes.

1. Choque. 2. Fondo. 3. Cielo Para la imagen de choque tendrá los siguientes requisitos, el fondo de la imagen debe ser de color negro, y las partes que se quiera que el personaje colisione debe pintarse de rojo. Para la imagen del cielo todo lo que se quiera hacer transparente debe de pintarse de color rosado, concretamente el color (0xff00ff).

Programación ? 1 #include 2 #include "global.h" 3 #include "players.h" 4 #include "mijuego.h" 5 6 7 8 void inicia_allegro() 9 { 10 allegro_init(); 11 install_keyboard(); 12 13 set_color_depth(32); 14 set_gfx_mode(GFX_AUTODETECT_WINDOWED, PANTALLA_ANCHO, PANTALLA_ALTO, 0, 0); 15 16 buffer = create_bitmap(PANTALLA_ANCHO, PANTALLA_ALTO); 17 18 LOCK_VARIABLE(contador_tiempo_juego); 19 LOCK_FUNCTION(inc_contador_tiempo_juego); 20 21 // Iniciamos el limitador de FPS 22 install_int_ex(inc_contador_tiempo_juego, BPS_TO_TIMER( FRAME_RATE )); 23}

24 25 26 27// programa principal 28int main() 29{ 30 inicia_allegro(); 31 32 carga_juego(); 33 34 salir = false; 35 36 37 while ( !salir ) 38 { 39 if ( contador_tiempo_juego ) 40 { 41 while ( contador_tiempo_juego ) 42 { 43 actualiza_juego(); 44 45 contador_tiempo_juego--; 46 } 47 48 clear_to_color(buffer, 0x00000); 49 50 pinta_juego(); 51 52 pintar_pantalla(); 53 54 }else{ 55 56 rest(1); 57 } 58 // tecla de salida 59 if ( key[KEY_ESC] ) salir = true; 60 } 61 62 destroy_bitmap(buffer);

63 64 return 0; 65} 66END_OF_MAIN();

Para este programa se utilizan cuatro archivos, y este código anterior se debe de guardar con el nombre main.cpp. Este es el código principal, en el se incluyen las librerías que se utilizan como allegro y tres archivos mas: global, player y mijuego. Estos códigos serán explicados en las próximas entradas. Tiene una función llamada inicia_allegro(), que se encarga de inicializar el entorno de la librería allegro, define una ventana con el tamaño dado por las variable pantalla_ancho y pantalla_alto. Inicializa la variable buffer con un bitmap del mismo tamaño que la ventana. Y prepara las funciones para el control de los frames por segundo (FPS). En el programa principal se repite todo mientras no sea salir. Para salir debes de pulsar la tecla ESC. Mientras se estará actualizando el juego, borra la pantalla, pinta el juego y lo muestra por pantalla. Todo esto se repetirá según los FPS que se hayan puesto, que para nuestro ejemplo será 30 veces por segundo. Para que no se haga muy largo lo he dividido en varias partes. Continúa en la próxima entrega.

GLOBAL.H El siguiente código corresponde al archivo global.h, que contiene todas las variables y funciones globales de nuestro programa. ? 1 2 3 4 5 6

/* GLOBAL.H */ // Ancho y alto de la pantalla const int PANTALLA_ANCHO = 800;

7 const int PANTALLA_ALTO = 600; 8 9 // En este BITMAP dibujaremos todo 10BITMAP *buffer; 11 12// es el espacio en pixel que recorre el jugador al andar 13const int desplazamiento=4; 14 15 16// Copiar el buffer a la pantalla del juego (screen) 17void pintar_pantalla() 18{ 19 blit(buffer, screen, 0, 0, 0, 0, PANTALLA_ANCHO, PANTALLA_ALTO); 20} 21 22// controla el bucle principal 23bool salir; 24 25// Variable usada para la velocidad 26volatile unsigned int contador_tiempo_juego = 0; 27 28const int FRAME_RATE = 30; 29 30// Función para controlar la velocidad 31void inc_contador_tiempo_juego() 32{ 33 contador_tiempo_juego++; 34} 35END_OF_FUNCTION(inc_contador_tiempo_juego)

Se define dos constantes para indicar el tamaño de la pantalla, PANTALLA_ANCHO y PANTALLA_ALTO. Se define la variable buffer del tipo BITMAP, que se utilizará para almacenar las imagenes antes de mostrar por pantalla. La constante desplazamiento indica el numero de pixel que se desplaza el personaje cada vez que se pulsa alguna tecla de dirección.

La función pintar_pantalla, es la que se encarga de volcar el contenido de la variable buffer a la pantalla ( screen ). Se define la variable salir como un tipo boolean. Y el resto se crea para el control de los FPS, la variable contador_tiempo_juego cuenta los ticks o veces que se llama la función.

PLAYER.H ? 1 // players.h 2 3 // Esta clase se encarga del manejo del jugador 4 class player 5 { 6 BITMAP *prota; 7 int x,y; 8 int direccion; 9 int animacion; 10 11 public: 12 void inicia(); 13 void pinta(); 14 void teclado(); 15 int getx(){ return x; }; 16 int gety(){ return y; }; 17 void posiciona( int _x, int _y); 18}; 19 20 21void player::inicia() 22{ 23 prota = load_bmp("personaje.bmp",NULL); 24 // inicializar vbles 25 direccion = 0;

26 animacion = 0; 27 x = 540; 28 y = 280; 29} 30 31 32void player::pinta() 33{ 34 masked_blit(prota, buffer, animacion*32, direccion*32, x, y, 32,32); 35} 36 37void player::teclado() 38{ 39 int ax = x; 40 int ay = y; 41 // teclas control usuario 42 if ( key[KEY_UP] ) 43 { 44 y-=desplazamiento; 45 direccion = 3; 46 } 47 if ( key[KEY_DOWN] ) 48 { 49 y+=desplazamiento; 50 direccion = 0; 51 } 52 if ( key[KEY_LEFT] ) 53 { 54 x-=desplazamiento; 55 direccion = 1; 56 } 57 if ( key[KEY_RIGHT] ) 58 { 59 x+=desplazamiento; 60 direccion = 2; 61 } 62 if ( ax != x || ay != y ) 63 { 64 // entra si a cambiado alguna de las variables x,y

65 animacion++; 66 if ( animacion > 2 ) 67 } 68 69 // limites globales 70 if ( x < 0 ) x = 0; 71 if ( x > PANTALLA_ANCHO ) 72 if ( y < 0 ) y = 0; 73 if ( y > PANTALLA_ALTO ) 74} 75 76void player::posiciona( int _x, 77{ 78 x=_x; 79 y=_y; 80}

animacion = 0;

x = PANTALLA_ANCHO; y = PANTALLA_ALTO; int _y)

En este archivo esta definida la clase player, que se encarga de todas las funciones del jugador. La función inicia se encarga de dar valores a las variables del personaje para situarlo dentro de nuestro escenario. La función pinta se encarga de pintar el personaje, sobre el buffer, según las variables sitúa al personaje en una posición concreta. La función teclado se encarga de controlar todas las teclas que manejan al personaje. En este caso se utilizan las teclas de flechas de cursor.

mijuego.h En este archivo se pondrá lo mas importante que es nuestro juego. ? 1 /* 2 mijuego.h 3

4 */ 5 6 BITMAP *fondo; 7 BITMAP *choque; 8 BITMAP *alto; 9 10player jugador; 11 12 13// carga todo lo necesario antes de empezar el juego 14void carga_juego() 15{ 16 jugador.inicia(); 17 // cargamos imagenes del primer escenario 18 fondo = load_bmp("casa.bmp",NULL); 19 choque = load_bmp("casa-choque.bmp",NULL); 20 alto = load_bmp("casa-sup.bmp",NULL); 21} 22 23 24// actualiza el estado del juego 25void actualiza_juego() 26{ 27 int ax,ay; 28 ax = jugador.getx(); 29 ay = jugador.gety(); 30 jugador.teclado(); 31 32 // comprobar si colisiona con el mapa 33 bool choca = false; 34 int px = jugador.getx()-160; 35 int py = jugador.gety()-160+16; 36 for ( int ci=0; ci < 32; ci++) 37 { 38 for (int cj=0; cj < 16; cj++) 39 { 40 41 if ( getpixel( choque, px+ci, py+cj) == 0xff0000 ){ 42 choca = true;

43 ci = 32; 44 cj = 16; 45 } 46 if ( getpixel( choque, px+ci, py+cj) == 0x00ff00 ) salir = true; 47 } 48 } 49 if ( choca ){ 50 // vuelve al estado anterior 51 jugador.posiciona( ax,ay ); 52 } 53 54} 55 56 57 58// Se encarga de pintar todo sobre el buffer 59void pinta_juego() 60{ 61 blit( fondo, buffer, 0,0, 160, 160, 480,325); 62 jugador.pinta(); 63 masked_blit( alto, buffer, 0,0, 160, 160, 480,325); 64}

Como ya se explicó al principio, se declaran las variables fondo, choque, alto para almacenar las imagenes, y se crea una variable jugador del tipo player. La función carga_juego se encarga de cargar las imágenes que se van a utilizan para nuestro escenario e inicializar al jugador. La función actualiza_juego se encarga de llamar a la función jugador.teclado() para actualizar la posición del jugador y a continuación comprueba si colisiona con el mapa. En el caso de colisionar vuelve al estado anterior. Y aquí acaba todo, si no ha habido ningún problema deberías tener a tu personaje paseando por el primer escenario tal y como se muestra en el siguiente vídeo.

Las imágenes que se utilizan para este ejemplo las puedes descargar haciendo click aquí o en la sección de descargas.

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF