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
srand (time(NULL));
Este comando sirve para inicializar los valores aleatorios.
En el archivo players.h en la función de teclado() se elimina los cuatro if que controlan los limites globales de la pantalla.
// limites globales if ( x < 0 ) x = 0; if ( x > PANTALLA_ANCHO-32 ) x = PANTALLA_ANCHO-32; if ( y < 0 ) y = 0; if ( y > PANTALLA_ALTO-32 ) y = PANTALLA_ALTO-32;
Estas condiciones se habian puesto con la intención de que el jugador no pueda desaparecer de la pantalla saliendo por uno de sus bordes. Se elimina ya que esto es controlado por los mapas de choque, y si estan bien hechos el jugador no se sale de pantalla.
Se añade una nueva función llamada cambia_escenario(). Esta nueva función se encarga de borrar el rectangulo blanco creado por el jugador, y esto debe hacerse antes de cambiar a otro escenario. Con el comando rectfill() se crea un rectangulo relleno de un color, en nuestro caso se rellena del color negro (0x000000).
void player::cambia_escenario() { // siempre antes de cambiar a otro escenario se debe de borrar // el cuadro de choque int mx = desplazamiento_map_x; int my = desplazamiento_map_y; rectfill( choque, x+4+mx, y+17+my, x+28+mx, y+30+my, 0x000000); }
En el archivo npc.h se añaden nuevas funciones para que nuestro npc pueda moverse. Se añaden dos nuevas variables privadas:
BITMAP* mifondo; bool primer;
Y tambien se añaden dos nuevas funciones:
void actualiza(); bool chocanpc();
La funcion chocanpc(), se encarga de comprobar las colisiones. Y la función actualiza() se encarga del movimiento del npc.
En la función crea(), se añade la inicialización de las nuevas variables:
mifondo = create_bitmap(32, 32); primer = false;
La función chocanpc():
bool npc::chocanpc() { int ninix,niniy; int nfinx,nfiny; if ( direccion == 0 ) { // abajo ninix = 0; niniy = 32 - desplazamiento; nfinx = 32; nfiny = 32; } if ( direccion == 1 ) { // izquierda ninix = 0; niniy = 0; nfinx = desplazamiento; nfiny = 32; } if ( direccion == 2 ) { // derecha ninix = 32 - desplazamiento; niniy = 0; nfinx = 32; nfiny = 32; } if ( direccion == 3 ) { // arriba ninix = 0; niniy = 0; nfinx = 32; nfiny = desplazamiento; } // comprobar si colisiona con el mapa for ( int ci=ninix; ci < nfinx; ci++) { for (int cj=niniy; cj < nfiny; cj++) { // color rojo if ( getpixel( choque, x+ci, y+cj) == 0xff0000 ){ return true; } // color blanco prota if ( getpixel( choque, x+ci, y+cj) == 0xffffff ){ return true; } } } return false; }
Esta función se encarga de controlar la colisión entre el NPC y el escenario controlando el color rojo, y la colisión entre el NPC y el jugador con el color blanco. El sistema de colisión es igual que el del jugador mediante el comando getpixel() extrae el color del pixel que hay en el mapa choque en una posición concreta y la compara con los colores de choque, pero debido a que habrá mas cantidad de NPC para evitar que se relentice todo se ha mejorado un poco el sistema de colisión, ya que se ha reducido drasticamente el espacio que se compara para comprobar si hay un pixel de color rojo o blanco. Según la dirección se obtiene un rango de acción según las variables (ninix,niniy) y (nfinx,nfiny).
Como muestra la imagen, la zona pintada en verde indica el espacio que se comprueba para la colisión y según el número indica la dirección a la que pertenece. Por tanto si el NPC va hacia abajo se comprobará si existe colisión en el rectángulo verde con el numero 0.
Aqui tienen un video, donde se muestran las colisiones.
La función actualiza(), se encarga del movimiento del npc según el valor de la variable estado:
- 0 : parado.
- 1 : movimiento horizontal.
- 2 : movimiento vertical.
- 3 : movimiento giro derecha. Camina en una misma dirección y cuando colisiona gira a la derecha.
- 4 : movimiento giro izquierda. Camina en una misma dirección y cuando colisiona gira a la izquierda.
- 5 : movimiento aleatorio. Camina en una misma dirección y cuando colisiona gira de forma aleatoria, y además cada un determinado tiempo tiene la posibilidad de cambiar de dirección.
Aqui tienen un video donde se muestran todos los tipos de movimiento que se van a crear en este tutorial.
void npc::actualiza() { // para indicar que se ejecuta dos veces int num = FRAME_RATE / 6; if ( tiempo_total % num == 0 ) { if ( estado != 0 ) { animacion++; if ( animacion > 2 ) animacion = 0; } switch ( estado ) { case 1: // camina horizontal ax = x; ay = y; if ( direccion == 1 ) { // camina izquierda x-=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = 2; } } if ( direccion == 2 ) { // camina derecha x+=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = 1; } } if ( ax != x ) { // borrar antiguo choque rectfill( choque, ax+2, ay+1, ax+30, ay+31, 0x000000); // pinta el nuevo choque rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000); } // cambie o no se cambia el fondo por la animacion // restaura fondo anterior antes de pintar la nueva imagen blit( mifondo, fondo, 0,0, ax,ay, 32,32); // obtiene una copia del nuevo fondo que va a ser ocupado blit( fondo, mifondo, x,y,0,0,32,32); break; case 2: // camina vertical ax = x; ay = y; if ( direccion == 0 ) { // camina abajo y+=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = 3; } } if ( direccion == 3 ) { // camina arriba y-=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = 0; } } if ( ay != y ) { // borrar antiguo choque rectfill( choque, ax+2, ay+1, ax+30, ay+31, 0x000000); // pinta el nuevo choque rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000); } // cambie o no se cambia el fondo por la animacion // restaura fondo anterior antes de pintar la nueva imagen blit( mifondo, fondo, 0,0, ax,ay, 32,32); // obtiene una copia del nuevo fondo que va a ser ocupado blit( fondo, mifondo, x,y,0,0,32,32); break; case 3: // camina giro derecha ax = x; ay = y; if ( direccion == 0 ) { // camina abajo y+=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = 1; } } if ( direccion == 1 ) { // camina izquierda x-=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = 3; } } if ( direccion == 2 ) { // camina derecha x+=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = 0; } } if ( direccion == 3 ) { // camina arriba y-=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = 2; } } if ( ax != x || ay != y ) { // se ha movido en una de las direcciones // borrar antiguo choque rectfill( choque, ax+2, ay+1, ax+30, ay+31, 0x000000); // pinta el nuevo choque rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000); } // cambie o no se cambia el fondo por la animacion // restaura fondo anterior antes de pintar la nueva imagen blit( mifondo, fondo, 0,0, ax,ay, 32,32); // obtiene una copia del nuevo fondo que va a ser ocupado blit( fondo, mifondo, x,y,0,0,32,32); break; case 4: // camina giro izquierda ax = x; ay = y; if ( direccion == 0 ) { // camina abajo y+=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = 2; } } if ( direccion == 1 ) { // camina izquierda x-=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = 0; } } if ( direccion == 2 ) { // camina derecha x+=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = 3; } } if ( direccion == 3 ) { // camina arriba y-=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = 1; } } if ( ax != x || ay != y ) { // se ha movido en una de las direcciones // borrar antiguo choque rectfill( choque, ax+2, ay+1, ax+30, ay+31, 0x000000); // pinta el nuevo choque rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000); } // cambie o no se cambia el fondo por la animacion // restaura fondo anterior antes de pintar la nueva imagen blit( mifondo, fondo, 0,0, ax,ay, 32,32); // obtiene una copia del nuevo fondo que va a ser ocupado blit( fondo, mifondo, x,y,0,0,32,32); break; case 5: // camina libre if ( tiempo_total % 200 == 0 ) { direccion = rand()%4; } ax = x; ay = y; if ( direccion == 0 ) { // camina abajo y+=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = rand()%4; } } if ( direccion == 1 ) { // camina izquierda x-=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = rand()%4; } } if ( direccion == 2 ) { // camina derecha x+=desplazamiento; if ( chocanpc() ) { // posicion no valida x = ax; direccion = rand()%4; } } if ( direccion == 3 ) { // camina arriba y-=desplazamiento; if ( chocanpc() ) { // posicion no valida y = ay; direccion = rand()%4; } } if ( ax != x || ay != y ) { // se ha movido en una de las direcciones // borrar antiguo choque rectfill( choque, ax+2, ay+1, ax+30, ay+31, 0x000000); // pinta el nuevo choque rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000); } // cambie o no se cambia el fondo por la animacion // restaura fondo anterior antes de pintar la nueva imagen blit( mifondo, fondo, 0,0, ax,ay, 32,32); // obtiene una copia del nuevo fondo que va a ser ocupado blit( fondo, mifondo, x,y,0,0,32,32); break; default: // parado if ( tiempo_total % 300 == 0 ) { direccion = rand()%4; } // pinta el nuevo choque rectfill( choque, x+2, y+1, x+30, y+31, 0xff0000); // restaura fondo anterior antes de pintar la nueva imagen blit( mifondo, fondo, 0,0, x,y, 32,32); // obtiene una copia del nuevo fondo que va a ser ocupado blit( fondo, mifondo, x,y,0,0,32,32); break; } } }Según la variable estado se define el tipo de movimiento. Tanto en el movimiento vertical como horizontal, tiene un pequeño inconveniente ya que no se definen dos direcciones, en el de horizontal no se tiene en cuenta las direcciones 0 y 3, de igual modo en la vertical no se tiene en cuenta las direcciones 1 y 2. Esto puede provocar que el NPC no se mueva si a la función de crear el NPC recibe como parámetro de dirección por ejemplo arriba o abajo, si le decimos que tiene movimiento horizontal. O también si se pone la dirección derecha o izquierda y se le ha asignado como movimiento el vertical.
Este código escrito anteriormente se puede mejorar, si os fijáis existe algunas lineas que se repiten mucho. Esto da a entender que hay cosas que no están donde deberían. Esta mejora se pondrá mas adelante.
Se modifica la función pinta(), ahora se añade el control del fondo donde se va a pintar el NPC y la llamada a la función actualiza().
void npc::pinta() { if ( lugar == escena ) { if ( !primer ) { // obtiene una copia de lo anterior blit( fondo, mifondo, x,y,0,0,32,32); primer = true; } actualiza(); masked_blit(imagen, fondo, animacion*32, direccion*32, x, y, 32,32); } }
En el archivo mijuego.h, se añade una nueva funcion llamada carga_escenario(), que se encargará de cargar las imagenes del escenario segun la variable lugar. Tambien se indicará si el escenario tiene scroll y se pondrá la musica de cada escenario.
// carga los datos del escenario segun lugar void carga_escenario() { jugador.cambia_escenario(); switch ( lugar ) { case 1:// casa fondo = (BITMAP *)datosjuego[dicasa].dat; choque = (BITMAP *)datosjuego[dicasachoque].dat; cielo = (BITMAP *)datosjuego[dicasasup].dat; desplaza = false; musica_casa(); break; case 2:// bosque fondo = (BITMAP *)datosjuego[dibosque].dat; choque = (BITMAP *)datosjuego[dibosquechoque].dat; cielo = (BITMAP *)datosjuego[dibosquesup].dat; desplaza=true; sonido_ambiente(); musica_bosque(); break; case 3:// ciudad fondo = (BITMAP *)datosjuego[dicity1].dat; choque = (BITMAP *)datosjuego[dicity1choque].dat; cielo = (BITMAP *)datosjuego[dicity1sup].dat; desplaza=true; musica_ciudad1(); break; } }Aquí se ve facilmente cuantos escenarios tiene el juego y facilita el querer añadir nuevos.
Al inicio de esta función se realiza una llamada a la función del jugador cambia_escenario(), para que borre su rectángulo de choque del escenario anterior.
En la función actualiza_juego(), se cambia la parte en la que se comprueba el lugar y se hace uso de la nueva función carga_escenario(), quedando el código de la siguiente forma:
switch ( lugar ) { case 1: // casa if ( cambio == 1 ) { // cambiamos a otro lugar // bosque lugar = 2; carga_escenario(); jugador.posiciona( 410,370 ); desplazamiento_map_x=0; desplazamiento_map_y=160; cambio = 0; } break; case 2: // bosque if ( cambio == 2 ) { // cambiamos a otro lugar // casa lugar = 1; carga_escenario(); // situamos al prota dentro de la casa jugador.posiciona( 290,440 ); desplazamiento_map_x=-160; desplazamiento_map_y=-160; para_sonido_ambiente(); cambio = 0; } if ( cambio == 3 ) { // cambiamos a otro lugar // ciudad lugar = 3; carga_escenario(); // situamos al prota dentro de la casa jugador.posiciona( 500,540 ); desplazamiento_map_x=950; desplazamiento_map_y=510; para_sonido_ambiente(); cambio = 0; } break; case 3: // ciudad if ( cambio == 1 ) { // cambiamos a otro lugar // bosque lugar = 2; carga_escenario(); jugador.posiciona( 650,30 ); desplazamiento_map_x=200; desplazamiento_map_y=0; cambio = 0; } break; }
En esta función se ha dejado el posicionamiento del personaje, ya que habrá mapas al cual se podrá acceder desde varios lugares. Debido a esto también se a mantenido las variables de desplazamiento_map ya que según la posición del personaje, se tiene que situar la pantalla.
Ya solo queda volver a carga_juego(), y cambiar las lineas que crean a los NPC.
npersonaje = 2; personajes[0].crea( (BITMAP *)datosjuego[diper004].dat, 160,120, 2,4,1); personajes[1].crea( (BITMAP *)datosjuego[diper002].dat, 50, 200, 3,5,1);
De esta forma se crean dos npc dentro de la casa, uno que se moverá con giro a izquierda, y el segundo personaje se moverá de una forma aleatoria.
Si quieren ver como se hace para añadir un NPC mas, vean el siguiente video.
disculpa como hago para ver las capas del juego como lo haces ??
ResponderEliminar