Versión: 0.47, de 10-Jul-2011


36. Añadiendo funcionalidades a "MiniMiner" (2): mejora del movimiento, recoger objetos (*)

En la séptima entrega de nuestro Miner vimos cómo hacer un movimiento de salto, tanto vertical como parabólico. Es un tipo de movimiento aplicable a muchos juegos de plataformas, pero que no funciona bien en nuestro caso, por un par de detalles:

Vamos a intentar mejorar ese comportamiento, y, de paso, recoger objetos en la pantalla...


Eso de que el personaje de comporte de forma incorrecta cuando choca con algo en medio salto se debe, como ya hemos comentado, a que el personaje es mucho más alto que las casillas del fondo, y se puede solucionar de dos formas:

bool Nivel::esPosibleMover(int x1, int y1, int x2, int y2)
{
	// Coordenadas de las esquinas del personaje
	int xCasilla1 = (x1 - margenIzq) / anchoImagen;
	int yCasilla1 = (y1 - margenSup) / altoImagen;
	int xCasilla2 = (x2 - margenIzq) / anchoImagen;
	int yCasilla2 = (y2 - margenSup) / altoImagen;
	// Tambien zona a media altura, porque el personaje es mucho 
	// más alto que las casillas del fondo
	int yCasilla3 = (yCasilla1 + yCasilla2) / 2;
 
	// En vez de comprobar todas las casillas del fondo,
	// miro solo las que están en las esquinas el personaje
	if (mapa[yCasilla1][xCasilla1] != ' ')
		return false;
 
	if (mapa[yCasilla1][xCasilla2] != ' ')
		return false;
 
	if (mapa[yCasilla2][xCasilla2] != ' ')
		return false;
 
	if (mapa[yCasilla2][xCasilla1] != ' ')
		return false;
 
	// Y las zonas a media altura, porque el personaje es alto
	if (mapa[yCasilla3][xCasilla2] != ' ')
		return false;
 
	if (mapa[yCasilla3][xCasilla1] != ' ')
		return false;
 
	return true;
}


Para que choque sólo cuando caiga y no cuando suba, podemos hacer dos cosas:

void Personaje::mover(Nivel n) {
    // Tamaño vertical: personaje + 20% aprox
    // Tamaño horizontal: 4.5 casillas aprox
    if (saltando)
    {
 
        int xProxMov = posX + incrXSalto;
        int yProxMov = posY + pasosSaltoArriba[ fotogramaMvto ];
        bool subiendoSalto = (pasosSaltoArriba[ fotogramaMvto ] < 0);
 
        // Si todavía se puede mover, avanzo
        if (n.esPosibleMover(xProxMov, yProxMov+altura-4, 
           xProxMov+anchura, yProxMov+altura)
           || subiendoSalto )
        {
           posX = xProxMov;
           posY = yProxMov;        
        }
        // Y si no, quizá esté cayendo
        else
        {
            saltando = false;
            cayendo = true;
        }
 
        fotogramaMvto ++;    
        if (fotogramaMvto >= MVTOS_SALTO)
        {
           saltando = false;
           cayendo = true;
        }
    }
    else if (cayendo)
    {
        if (n.esPosibleMover(posX, posY+desplazVertical+altura-4, 
           posX+anchura, posY+desplazVertical+altura))
        {
           posY += desplazVertical;
        }
        else
            cayendo = false;         
    }
}


Finalmente, para recoger objetos, basta comprobar colisiones con otros objetos del fondo, de tipos distintos: en vez de ser ladrillos o fragmentos de suelo, serán llaves. Por tanto, la rutina de ver si hay algún objeto que recoger será muy similar a la "esPosibleMover".

De hecho, en el juego original también hay objetos que nos matan si chocamos con ellos. Son como "estalactitas", que nos hacen perder una vida si las rozamos al saltar. Para controlar ambos casos a la vez (objetos a recoger y objetos que nos maten), podríamos hacer una única rutina "obtenerPuntos" que, a partir de unas coordenadas, devuelva 0 si no hay nada, un valor positivo (por ejemplo, 10) si hay una llave, o un valor negativo (como -1) si hay una estalactita que nos haría perder una vida.

Esta rutina podría ser así:

int Nivel::obtenerPuntosPosicion(int x1, int y1, int x2, int y2)
{
	// V indica una llave en el mapa (10 puntos)
	// T indica un trozo de techo que nos mata
	//     (estalactita, -1 puntos)
 
 
	int xCasilla1 = (x1 - margenIzq) / anchoImagen;
	int yCasilla1 = (y1 - margenSup) / altoImagen;
	int xCasilla2 = (x2 - margenIzq) / anchoImagen;
	int yCasilla2 = (y2 - margenSup) / altoImagen;
	// Tambien zona a media altura, como en esPosibleMover
	int yCasilla3 = (yCasilla1 + yCasilla2) / 2;
 
	// Primero veo si choca con una estalactita
	// (puntuacion -1: perder vida)
	if ((mapa[yCasilla1][xCasilla1] == 'T') 
	   || (mapa[yCasilla1][xCasilla2] == 'T')
	   || (mapa[yCasilla2][xCasilla2] == 'T')
	   || (mapa[yCasilla2][xCasilla1] == 'T')
	   || (mapa[yCasilla3][xCasilla2] == 'T')
	   || (mapa[yCasilla3][xCasilla1] == 'T'))
		return -1;
 
	// Despues veo si toca una llave
	// (puntuacion 10, y se borra la llave)
  if (mapa[yCasilla1][xCasilla1] == 'V')
	{
		fragmentoNivel[yCasilla1][xCasilla1] = imagenFondo;
		mapa[yCasilla1][xCasilla1] = ' ';
		return 10;
	}
 
	if (mapa[yCasilla1][xCasilla2] == 'V')
	{
		fragmentoNivel[yCasilla1][xCasilla2] = imagenFondo;
		mapa[yCasilla1][xCasilla2] = ' ';
		return 10;
	}
 
	if (mapa[yCasilla2][xCasilla2] == 'V')
	{
		fragmentoNivel[yCasilla2][xCasilla2] = imagenFondo;
		mapa[yCasilla2][xCasilla2] = ' ';
		return 10;
	}
 
	if (mapa[yCasilla2][xCasilla1] == 'V')
	{
		fragmentoNivel[yCasilla2][xCasilla1] = imagenFondo;
		mapa[yCasilla2][xCasilla1] = ' ';
		return 10;
	}
 
	if (mapa[yCasilla3][xCasilla2] == 'V')
	{
		fragmentoNivel[yCasilla3][xCasilla2] = imagenFondo;
		mapa[yCasilla3][xCasilla2] = ' ';
		return 10;
	}
 
	if (mapa[yCasilla3][xCasilla1] == 'V')
	{
		fragmentoNivel[yCasilla3][xCasilla1] = imagenFondo;
		mapa[yCasilla3][xCasilla1] = ' ';
		return 10;
	}
 
	// Si no ha pasado nada de lo anterior, no hay puntos que obtener	
	return 0;
}
 
 

Y su uso desde el programa principal sería:

void comprobarColisiones() {
  // Colisiones de personaje con fondo: obtener puntos o perder vida
  int puntosMovimiento = primerNivel->obtenerPuntosPosicion(
    personaje->leerX(), 
    personaje->leerY(),
    personaje->leerX()+personaje->leerAnchura(),
    personaje->leerY()+personaje->leerAltura());
 
  // Si realmente ha recogido un objeto, sumamos los puntos en el juego
  if (puntosMovimiento > 0)
	puntos += puntosMovimiento;
 
  // Y si es -1, ha chocaco con el fondo: igual caso que las
  // colisiones de personaje con enemigo: recolocar y perder vida
  if (personaje->colisionCon( *enemigo ) 
    || (puntosMovimiento < 0) )
    {
    ...

Como casi siempre, aquí puedes descargar toda esta versión, en un fichero ZIP, que incluye todos los fuentes, las imágenes, el proyecto de Dev-C++ listo para compilar en Windows, y un fichero "compila.sh" para compilar en Linux.

Anterior Siguiente