29 octubre 2008

Remake (parcial) de Fruity Frank... 10 - Un personaje que cambia de forma

Nuestro personaje siempre mira hacia la derecha, nos movamos hacia donde nos movamos. Como primer acercamiento a un movimiento "más real", vamos a hacer que mire hacia el lado en que se mueve.

Existen varias formas de hacerlo. Casi todas pasan por añadir un paso adicional cada vez que el usuario pulse una tecla. Si esa tecla es la de la izquierda, memorizaremos de alguna forma que ahora se deberá dibujar el personaje que se desplaza hacia la izquierda. Se podría hacer actualizando el valor de una variable "dirección", o bien simplemente cambiando el identificador de la imagen que mostraremos. Ésta es la alternativa que usaré yo: tener 3 imágenes del personaje leídas de fichero (derecha, izquierda y arriba/abajo), junto con una cuarta imagen que no se lee de fichero, sino que toma su valor a partir de las otras, y que es la que realmente se muestra en pantalla:

   SDLA_Imagen* personajeD;
SDLA_Imagen* personajeI;
SDLA_Imagen* personajeA;
SDLA_Imagen* personaje;


Como ya he comentado, tres de esas imágenes se leen de fichero:

   personajeD=SDLA_cargarImagen("personajed1.bmp");
personajeI=SDLA_cargarImagen("personajei1.bmp");
personajeA=SDLA_cargarImagen("personajea1.bmp");



Y la cuarta cambia de valor según la dirección en que deba moverse el personaje:

        if ( SDLA_teclaPulsada (TECLA_IZQ) )
{
if ((xPersonaje > 0)
&& (mapa1[yPersonaje][xPersonaje-1] != 'M'))
{
xPersonaje --;
personaje = personajeI;
}
}



Haríamos lo mismo para las demás direcciones, y dibujaríamos siempre la imagen llamada "personaje", que en cada caso reflejará nuestro personaje mirando hacia el lado que nos interesa. Con eso bastaría para tener un personaje "cambiante" básico.

Como siempre, puedes ver todo el proyecto en: code.google.com/p/fruityfrank

28 octubre 2008

Remake (parcial) de Fruity Frank... 09 - Obstáculos, premios y texto

Con la estructura que tenemos hasta ahora, es fácil comprobar si hay un obstáculo en algún punto de la pantalla de juego.

La diferencia con lo que hemos hecho para "recoger premios" es que antes comprobábamos lo que había en una casilla cuando acabábamos de entrar a ella. Por el contrario, si se trata de un obstáculo, no deberíamos movernos a esa casilla, sino comprobar antes. Pero es fácil: ya estábamos comprobando si podíamos desplazarnos a una cierta posición de la pantalla o si estaba fuera de los márgenes, así que ahora es sólo añadir una condición más:

        if ( SDLA_teclaPulsada (TECLA_ARR) ) {
if ((yPersonaje > 0)
&& (mapa1[yPersonaje-1][xPersonaje] != 'M'))
yPersonaje --;
}


En esta expresión hay algo que puede desconcertar: si estamos en la columna 0... ¿no sería peligroso mirar en "mapa1[yPersonaje-1][xPersonaje]", es decir, en la columna "-1"? La respuesta es que NO: los compiladores de C evalúan las condiciones usando la técnica de "evaluación en cortocircuito": si se trata de dos condiciones unidas por un "Y" (&&) y la primera condición es falsa, no se molestan en comprobar la segunda condición, porque ya se sabe que el resultado de la condición global será "falso". Por eso, si estamos en la columna 0, no se llegará a comprobar qué habría en la columna -1.

Las demás comparaciones son similares a la anterior:

        if ( SDLA_teclaPulsada (TECLA_ABA) ) {
if ((yPersonaje < MAXFILAS-1)
&& (mapa1[yPersonaje+1][xPersonaje] != 'M'))
yPersonaje ++;
}



De paso, podemos hacer otra mejora: mostrar los puntos que vamos obteniendo durante el juego.

Eso supone dos pequeños cambios. El primero es declarar y cargar nuestro tipo de letra al principio del programa:

    SDLA_Fuente* SDLA_arial14;
SDLA_arial14 = SDLA_cargarFuente("arial.ttf",14);


El segundo es usar la función de "EscribirTextoOculta" para escribir textos, o "EscribirLongOculta" para escribir números (enteros largos):

        SDLA_escribirTextoOculta("Puntos",20,60,0x88FFFF, SDLA_arial14);
SDLA_escribirLongOculta(puntos,70,60,0xFFFF88, SDLA_arial14);


Por supuesto, cada vez que recojamos una cereza deberíamos obtener puntos, para que ese marcador aumente... pero eso ya no es difícil de hacer a esta altura.

(Nota: eso de poder escribir números ha supuesto hacer un cambio en los ficheros auxiliares SDL_ASI.h y sdlasi.c; tendrás que incluir en el proyecto los ficheros actualizados, o no compilará...)

24 octubre 2008

Remake (parcial) de Fruity Frank... 08 - Comprobando colisiones con el mapa

Ahora que tenemos la estructura de la pantalla de nuestro juego (fondo, enemigos, "premios") diseñada usando un mapa, es fácil comprobar colisiones entre nuestro personaje y otros elementos.

En este primer acercamiento a las colisiones, nuestro personaje podrá recoger frutas de la pantalla.

Para ello, basta con varios pequeños detalles:


  • La posición inicial de nuestro personaje ya no será un punto de la pantalla gráfica, como el (100,300), sino un punto del mapa, como el (1,1).

  • Ya no se moverá saltando varios píxeles (xPersonaje += 4), sino de una casilla en una casilla (xPersonaje ++).

  • Cuando se mueva, no comprobaremos si ha llegado al final de la pantalla gráfica, sino al final del mapa: if ( SDLA_teclaPulsada (TECLA_DER) ) { xPersonaje ++; if (xPersonaje > MAXCOLS-1) xPersonaje --; }

  • No lo dibujaremos en coordenadas de la pantalla gráfica, sino de la misma forma que dibujamos todos los elementos del fondo: SDLA_dibujarImagenOculta(personaje, xIniPantalla + xPersonaje * anchoCasilla, yIniPantalla + yPersonaje * altoCasilla);

  • Antes de redibujar la pantalla, podemos comprobar si existe alguna fruta en la posición que ahora ocupará el personaje, y, si es así, podemos borrarla (en una etapa más avanzada del juego, no sólo haríamos eso, también aumentaríamos los puntos, etc.): if (mapa1[yPersonaje][xPersonaje] == 'C') mapa1[yPersonaje][xPersonaje] = ' ';



Apenas con esos cambios, nuestro personaje ya puede "comer frutas" del recorrido. El movimiento es más brusco que antes (avanzamos de "casilla" en "casilla"), pero de momento hemos ganado en versatilidad. Más adelante comentaremos cómo hacer que el movimiento vuelva a ser suave.

Como siempre, puedes ver todo el proyecto en: code.google.com/p/fruityfrank

21 octubre 2008

Remake (parcial) de Fruity Frank... 07 - Mapa para dibujar el fondo

Ahora vamos a aplicar nuestros conocimientos de "arrays" para que el fondo no sea prefijado, sino que se pueda crear y modificar "al vuelo".

En vez de tener una imagen con todo el tamaño del fondo (por ejemplo 640x480 puntos), tendremos varias imágenes pequeñas, por ejemplo de 40x30 píxeles.

La primera ventaja evidente es el ahorro de memoria: una pantalla de 640x480 puntos con sólo 256 colores necesitará 307.200 bytes para almacenarse. Si en nuestro mapa hay cinco tipos de elementos (dos tipos de fondo, un tipo de obstáculo, dos tipos de enemigo), necesitaremos 5 x 40 x30 = 6.000 bytes; además el mapa tendría 16 columnas (640/40) y 16 filas (480/30), lo que supone 16 x 16 = 256 bytes para guardar el mapa. En total, necesitamos 6.256 bytes para una pantalla... casi 50 veces menos que antes (y esta proporción puede ser aún mayor, si en la segunda pantalla hay elementos comunes con la primera).

Pero hay muchas más ventajas. Por ejemplo:


  • Es mucho más fácil modificar ese "mapa" que modificar una imagen, en caso de que queramos cambiar la estructura de la pantalla durante el juego (por ejemplo, si nuestro personaje "recoge un premio" o destruye un obstáculo).

  • Este mapa permite una forma muy fácil de comprobar colisiones entre objetos: si nuestro personaje se mueve a la casilla (2,3) y en esa casilla hay un "premio", deberemos aumentar la cantidad de puntos, etc.



¿Y cómo creamos ese mapa? Podría ser un "array de caracteres", formado por varias filas (un array de dos dimensiones), en el que cada letra tenga un significado especial:

    char mapa[10][16]={
"XCXXMXXX XMCXXX",
"MXXCXXXX XMXCMM",
"XMCXXXXX XCXXXC",
"XXCXCXCX XXXXCX",
" ",
"XXMCXMXX XCXXXX",
"XXXXXXXX XXXMXX",
"XXXXXCXX XXXXXX",
"XXXXXCXX XXXCXX",
"XXXXXXXX CXCXXC"
};


En este ejemplo, una X indicaría una casilla del fondo que se debe dibujar, una C sería una cereza, una M sería una manzana, y un espacio en blanco sería una casilla en la que no hay que dibujar nada.

Siendo prácticos, sería mejor no usar esos valores 10 y 16 para indicar el tamaño, sino usar dos constantes, llamadas MAXFILAS y MAXCOLS (por ejemplo), de modo que podamos revisar el mapa con dos bucles "for", así:

        for (int i=0; i<MAXFILAS; i++)
for (int j=0; j<MAXCOLS; j++) {
if (mapa1[i][j] == 'X')
SDLA_dibujarImagenOculta(fondoNivel1, ...


Entonces, el primer paso, antes de este doble "for", no sería dibujar el fondo, que ya no existe como un único elemento, sino borrar la pantalla oculta, para luego dibujar sobre ella elemento a elemento:

        SDLA_borrarPantallaOculta(0,0,0); // Borro en negro


El siguiente paso que daremos será usar este mapa para comprobar "colisiones", pero eso llegará dentro de poco, en la próxima entrega...

Como siempre, puedes ver todo el proyecto en: code.google.com/p/fruityfrank

16 octubre 2008

Remake (parcial) de Fruity Frank... 06 - Transparencia y tiempo "fiable"

Vamos a hacer dos pequeñas mejoras:


  • Al mover nuestro personaje, se mueve también el recuadro negro que lo rodea. Quedaría mucho mejor si ese recuadro negro no se viera, sino que nuestro personaje pareciera estar directamente sobre el fondo. Eso es fácil de conseguir: no podemos usar imágenes en formatos que tengan transparencia, como el PNG o el GIF (al menos por ahora), porque la versión básica de la biblioteca SDL sólo soporta el formato BMP, pero sí podemos indicar que uno de los colores que forman la imagen sea tratado como transparente.

  • La velocidad de nuestro juego depende de la velocidad del ordenador en el que lo probemos, hasta el punto de que puede resultar "injugable" en un ordenador muy rápido, y eso no debería ocurrir.




Para el primero de esos puntos, la transparencia, tenemos una orden en SDL ("ocultada" dentro de las funciones en español que tenemos listas para usar) que permite indicar qué color será considerado transparente, a partir de sus componentes RGB (rojo, verde, azul, indicados entre 0 y 255). Por ejemplo, si queremos que los puntos negros de la imagen sean considerados como transparentes, usaremos el color (0,0,0), o si queremos que lo sea el rojo puro, emplearemos (255,0,0).

En el caso de Fruity Frank, nuestro personaje tiene el pelo rojo y ropa azul, así que podemos usar el verde puro (0,255,0) para indicar los puntos transparentes. Editamos nuestra imagen desde cualquier editor gráfico (incluso el propio "Paint" de Windows serviría) y pintamos en color verde intenso el contorno de nuestro personaje, que antes era negro. Después le indicamos a nuestro juego que no dibuje ese verde, con:

    personaje=SDLA_cargarImagen("personaje1.bmp");
SDLA_colorTransparente(personaje, 0,255,0);


De igual modo, nuestro primer enemigo tiene zonas rojas, blancas, negras... así que el verde volvería a ser una buena elección para hacer su contorno transparente.

Por otra parte, para que la velocidad sea "casi igual" en cualquier ordenador, añadiremos una pausa entre un "fotograma" del juego y el siguiente. Si queremos que la visualización del juego tenga la velocidad habitual, 25 fotogramas por segundo, y suponemos que el tiempo que se pierde en redibujar la pantalla y en el análisis de la lógica de juego es despreciable, deberíamos hacer una pausa de 40 milisegundos tras cada redibujado de pantalla (1 segundo = 1000 milisegundos; 1000 ms / 25 fps = 40 ms entre un "fotograma" y otro):

    // Pausa de 40 ms, para velocidad de 25 fps (1000/40 = 25)
SDLA_pausa(40);



Como siempre, todo el proyecto está en: code.google.com/p/fruityfrank

13 octubre 2008

Remake (parcial) de Fruity Frank... 05 - Un primer enemigo

Vamos a añadir un primer enemigo, cuyo movimiento esté prefijado y sea sencillo. Todavía no comprobaremos si nuestro personaje y el "enemigo" colisionan.

La idea básica es sencilla: tendremos una nueva imagen, que dibujaremos en sus propias coordenadas x e y, y al final de cada pasada calcularemos cual será su nueva posición.

La imagen la extraeremos de una de las capturas del juego, igual que hicimos con la de nuestro personaje. Para cargarla y dibujarla será muy parecido a lo que ya hemos hecho:

    int xEnemigo1=250, yEnemigo1=250;
SDLA_Imagen* enemigo1;
enemigo1=SDLA_cargarImagen("enemigo1i1.bmp");
...
SDLA_dibujarImagenOculta(enemigo1, xEnemigo1, yEnemigo1);


Moverlo no es mucho más difícil: basta con cambiar su coordenada X o su coordenada Y (o ambas) en cada pasada del bucle "do..while". Por ejemplo, como primer acercamiento podríamos hacer que se moviera hacia la derecha con "xEnemigo1 += 3;"

Lo razonable es que no siempre se mueva hacia la derecha (o al menos que lo haga sólo hasta llegar al margen de la pantalla, en vez de seguir avanzando incluso entonces). Un movimiento un poco más real sería que fuera hacia un lado de la pantalla y, cuando llegara a él, "rebotara" para volver hacia el lado contrario. Hay varias formas de conseguir este efecto. Por ejemplo:


  • Tener una variable auxiliar llamada "dirección" que nos permita memorizar hacia qué lado nos movemos. Cuando la "dirección" sea hacia la derecha, aumentaremos la X del personaje; cuando la "dirección" sea hacia la izquierda, disminuiremos la X del personaje; cuando llegue al margen derecho, cambiaremos "dirección" para que sea "hacia la izquierda"; cuando llegue al margen izquierdo, cambiaremos "dirección" para que sea "hacia la derecha".

  • Una alternativa es sumar siempre un incremento a xPersonaje. Este incremento empezará siendo +3 (por ejemplo) para que se mueva hacia la derecha, y cada vez que llegue a un extremo de la pantalla, cambiaremos el signo a ese incremento, para que empiece a desplazarse en sentido contrario. Podría ser algo así:



        xEnemigo1 += incrEnemigo;

if (xEnemigo1 < xIniPantalla)
{
xEnemigo1 = xIniPantalla;
incrEnemigo = -incrEnemigo;
}

if (xEnemigo1 > xFinPantalla-anchoEnemigo1)
{
xEnemigo1 = xFinPantalla-anchoEnemigo1;
incrEnemigo = -incrEnemigo;
}


Si eso está dentro del do..while del "bucle de juego", acabamos de conseguir que nuestro enemigo se mueva en cada "fotograma" del juego, tanto si movemos nuestro personaje como si no lo hacemos.

Como siempre, todo el proyecto está en: code.google.com/p/fruityfrank


(Volver al índice)

09 octubre 2008

Remake (parcial) de Fruity Frank... 04 - Movimiento limitado

En la entrega anterior podíamos mover el personaje, pero nada impedía que no saliéramos de los límites de la pantalla de juego o incluso de la pantalla del ordenador. Vamos a hacer una pequeña mejora para evitarlo.

La idea es que cada vez que cambie una coordenada, por ejemplo xPersonaje, comprobaremos que su valor sea "razonable". Por ejemplo, la x no debería ser negativa, lo que podríamos conseguir con:

  if (xPersonaje < 0)
xPersonaje = 0;


Siendo estrictos, quizá la pantalla de nuestro juego no coincida por completo con la pantalla del ordenador, y, por tanto, no empiece justo en la posición 0. Entonces, una forma más genérica de conseguir lo anterior sería:

  if (xPersonaje < xIniPantalla)
xPersonaje = xIniPantalla;


En el lado contrario, la condición no es exactamente la misma: no nos basta con
if (xPersonaje > xFinPantalla) xPersonaje = xFinPantalla;
porque si situamos nuestro personaje a partir de xFinPantalla... ¡¡¡quedará fuera de la pantalla!!! Lo que debe coincidir con xFinPantalla es el último pixel de nuestro personaje, de modo que la comparación correcta sería:

  if (xPersonaje > xFinPantalla - anchoPersonaje)
xPersonaje = xFinPantalla - anchoPersonaje;


Como se ve, estamos utilizando más de una variable, que nos permitan controlar detalles como el ancho de nuestro personaje o los límites de la pantalla, de la forma más legible posible:

  int xPersonaje=340, yPersonaje=100;
int anchoPersonaje = 32, altoPersonaje = 32;
int xIniPantalla = 2, xFinPantalla = 638;
int yIniPantalla = 82, yFinPantalla = 448;


Ahora, cada vez que el usuario pulsa una tecla deberemos comprobar que el valor de x o y sea correcto antes de mover nuestro personaje a esa posición:

  if ( SDLA_teclaPulsada (TECLA_ARR) ) {
yPersonaje -= 4;
if (yPersonaje < yIniPantalla)
yPersonaje = yIniPantalla;
}


Para ver todo el proyecto en conjunto: code.google.com/p/fruityfrank

08 octubre 2008

Remake (parcial) de Fruity Frank... 03 - Moviendo el personaje

Un juego real no se queda parado hasta que pulsemos una tecla, sino que debe proseguir continuamente: por ejemplo, aunque nosotros no hagamos nada, los enemigos deberán moverse. Normalmente, incluso en la pantalla de presentación habrá algún tipo de animación.

Por eso, lo habitual será que no usemos "SDLA_esperarTecla" sino "SDLA_teclaPulsada". La forma de emplear esta orden será indicarle entre paréntesis la tecla que queremos comprobar: if ( SDLA_teclaPulsada (TECLA_ABA) ) ..."

Hay varias constantes predefinidas, para representar las teclas más habituales:


  • TECLA_ABA para la flecha hacia abajo del teclado.

  • TECLA_ARR para la flecha hacia arriba.

  • TECLA_DER para la flecha hacia la derecha.

  • TECLA_IZQ para la flecha hacia la izquierda.

  • TECLA_ESC para la tecla de ESCAPE (Esc).

  • TECLA_ESP para la barra espaciadora.

  • ...



Lo único que quedar para que el personaje se mueva es que su posición no sean unas coordenadas fijas, como el (300,100) del ejemplo anterior, sino usar variables (por ejemplo: xPersonaje, yPersonaje) que modifiquemos cuando comprobemos que se pulsa una tecla.

Finalmente, el propio cuerpo del programa ya no deberá ejecutarse sólo una vez, sino repetirse hasta que se pulse cierta tecla (por ejemplo ESC).

La nueva apariencia de nuestro mini-juego será:

#include "SDL_ASI.h"

int main (int argc, char** argv)
{
int xPersonaje=340, yPersonaje=100;

SDLA_inicializar(640,480,24);

SDLA_Imagen* fondoPantalla;
SDLA_Imagen* personaje;

fondoPantalla=SDLA_cargarImagen("fondo.bmp");
personaje=SDLA_cargarImagen("personaje1.bmp");

do {

SDLA_dibujarImagenOculta(fondoPantalla,96,0);
SDLA_dibujarImagenOculta(personaje, xPersonaje, yPersonaje);
SDLA_visualizarOculta();

if ( SDLA_teclaPulsada (TECLA_ABA) )
yPersonaje += 4;
if ( SDLA_teclaPulsada (TECLA_ARR) )
yPersonaje -= 4;
if ( SDLA_teclaPulsada (TECLA_DER) )
xPersonaje += 4;
if ( SDLA_teclaPulsada (TECLA_IZQ) )
xPersonaje -= 4;

} while (! SDLA_teclaPulsada (TECLA_ESC));
return 0;

}


Pero si nos acercamos mucho a los extremos, veremos que el personaje se puede salir de la pantalla de juego... o incluso de la pantalla del ordenador. Ésa será la próxima mejora.

Para ver todo el proyecto en conjunto: code.google.com/p/fruityfrank

06 octubre 2008

Remake (parcial) de Fruity Frank... 02 - Mostrar fondo y personaje

En un juego real no existe sólo la imagen de fondo. Típicamente existirá también al menos un personaje que se mueva por ella. Hoy vamos a dibujar ese personaje...

En primer lugar, hacemos una copia de la imagen de fondo inicial, la que mostraba tanto la pantalla de fondo del juego como nuestro personaje (o incluso dos copias, si queremos conservar el original). En una de las imágenes borramos el personaje, de modo que sólo se vea realmente el fondo. En la otra "recortamos" el personaje, eliminando todo lo demás (usando cualquier editor gráfico, como por ejemplo XnView). Ambas imágenes deben estar en formato BMP. Por ejemplo, el fondo podría ser "fondo.bmp" y el personaje podría ser "personaje1.bmp" (el 1 es porque más adelante tendremos más de una imagen del personaje, para dar impresión de movimiento).

Una vez preparadas las imágenes, los cambios a realizar en nuestro "juego.cpp" son mínimos:.


  • Declararemos la variable que representará la imagen del personaje: SDLA_Imagen* personaje;
  • Cargaremos esa imagen desde fichero: personaje=SDLA_cargarImagen("personaje1.bmp");

  • Y la dibujaremos en la pantalla oculta, en ciertas coordenadas (por ejemplo, x=300, y=100), antes de mostrar ésta: SDLA_dibujarImagenOculta(personaje,300,100);



Es decir, el fuente sería algo como:

#include "SDL_ASI.h"

int main (int argc, char** argv)
{
SDLA_inicializar(640,480,24);

SDLA_Imagen* fondoPantalla;
SDLA_Imagen* personaje;
fondoPantalla=SDLA_cargarImagen("fondo.bmp");
personaje=SDLA_cargarImagen("personaje1.bmp");

SDLA_dibujarImagenOculta(fondoPantalla,0,0);
SDLA_dibujarImagenOculta(personaje,300,100);
SDLA_visualizarOculta();

SDLA_esperarTecla();
return 0;
}


Pero ese personaje todavía no se mueve... Por pocas horas, mañana sí lo hará.

Para ver todo el proyecto en conjunto: code.google.com/p/fruityfrank

05 octubre 2008

Remake (parcial) de Fruity Frank... 01 - Mostrar el fondo

En este primer acercamiento vamos a preparar las herramientas que usaremos para crear el juego, y las probaremos para mostrar la imagen de fondo del juego.


  • Como entorno de desarrollo usaremos CodeBlocks, que incluye el compilador GNU de C y C++, junto con entorno integrado (editor y depurador). Se podría usar cualquier otro, pero en el apartado de "Descargas" dejaré un proyecto de CodeBlocks listo para ser usado simplemente con un "doble clic" desde Windows. Quien elija usar otro entorno de desarrollo tendrá que hacer un poco más de "trabajo manual".

  • Como biblioteca que nos proporcione las funcionalidades básicas usaremos SDL (Simple DirectMedia Layer)

  • Nos apoyaremos en un par de ficheros auxiliares que se encargan de ocultar los detalles de SDL y de permitirnos usar órdenes traducidas al español, algo más sencillas.  Todas estas órdenes comenzarán por "SDLA_", para distinguirlas de las órdenes "normales" de C++ y/o de SDL.



Usando estos ficheros auxiliares, un primer fuente de prueba sería así:

#include "SDL_ASI.h"

int main (int argc, char** argv)
{
SDLA_inicializar(640,480,24);

SDLA_Imagen* fondoPantalla;
fondoPantalla=SDLA_cargarImagen("fondo.bmp");

SDLA_dibujarImagenOculta(fondoPantalla,0,0);
SDLA_visualizarOculta();

SDLA_esperarTecla();
return 0;
}


Vamos a ver línea por línea lo que hace, porque entenderlo será vital para poder irlo ampliando...

  • #include "SDL_ASI.h" - Carga uno de los ficheros auxiliares que necesitamos, el fichero de cabecera "SDL_ASI.h".

  • int main (int argc, char** argv) - Comienzo del cuerpo del programa ("main").

  • SDLA_inicializar(640,480,24);
    - Inicializa la pantalla gráfica, escogiendo el modo de 640 x 480 puntos (ancho x alto) y 24 bits de color (16 millones de colores).

  • SDLA_Imagen* fondoPantalla; - Declara una variable llamada "fondoPantalla", que será una imagen (tipo SDLA_Imagen*)

  • fondoPantalla=SDLA_cargarImagen("fondo.bmp");
    - Prepara el valor de "fondoPantalla": será una imagen que se cargue de disco, llamada "fondo.bmp".

  • SDLA_dibujarImagenOculta(fondoPantalla,0,0); - Dibuja la imagen "fondoPantalla" en las coordenadas (0,0) de la pantalla oculta, es decir en la esquina superior izquierda. Para evitar parpadeos en pantalla, siempre dibujaremos los elementos de nuestro juego uno a uno en una pantalla oculta, y finalmente haremos que toda esa pantalla oculta pase a ser visible.

  • SDLA_visualizarOculta();
    - Hace que la pantalla oculta se pueda ver.

  • SDLA_esperarTecla(); - Esperamos a que se pulse una tecla.

  • return 0; - Termina el cuerpo del programa.



Para probar todo esto, bastaría con descargar el entorno CodeBlocks, añadirle los paquetes de SDL (al menos SDL y SDL_ttf) desde el menú hacer doble clic en el fichero del proyecto: "fruity.cbp". Sólo hace falta que hayamos preparado una imagen de fondo del juego, que se llame "fondo.bmp", que podríamos haber capturado del juego original, usando un emulador (esta imagen forma parte del proyecto que se puede descargar).

Para poner en marcha el "supuesto juego" (que todavía es poco jugable), entraríamos al menú "Build" (construir) de CodeBlocks y escogeríamos la opción "Build & Run" (construir y ejecutar). Si todo es correcto (debería ser así), debería aparecer la pantalla de fondo del juego y esperar que pulsemos una tecla, momento en el que se acaba "el juego".




Para los que tengan más conocimientos técnicos, voy a dar algún detalle más de cómo son los otros dos ficheros auxiliares que forman parte de nuestro proyecto.

El primero de ellos, el que incluimos desde nuestro juego, se llama SDL_ASI.h, y es un fichero de cabecera que tiene los resúmenes de las posibilidades de nuestra "mini-biblioteca" SDL_ASI, junto con las declaraciones de constantes:

#ifndef SDL_ASI_H

#define SDL_ASI_H

#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>

/* Adaptacion de SDL, ocultando lo basico y
traduciendo al castellano */

/* =====================================
Versiones anteriores:

Numero Fecha Cambios
------- ---------- ----------------

0.04 18/05/2007 Permite escribir texto en la pantalla oculta
(necesita SDL_TTF)
Añadida una funcion para vaciar buffer de
teclado

0.03 18/05/2007 Añadido "esperaTecla", para poder esperar a
que se pulse una tecla sin necesidad de "while".
Añadidas varias constantes más para teclas habituales:
números del 0 al 5, espacio.

0.02 27/04/2007 Añadido "colision", corregido "teclaPulsada",
que usaba parametro "char" en vez de "int",
separados prototipos (.h) de codigo (.c)

0.01 26/04/2007 Versión inicial, que enmascara SDL: permite
inicializar, borrar la pantalla oculta,
cargar una imagen, dibujar una imagen en la
pantalla oculta, visualizar la pantalla
oculta, ver si se ha pulsado el ratón, ver
si se ha pulsado una tecla, y define las teclas
del cursor y ESC.


===================================== */

#define SDLA_Imagen SDL_Surface
#define SDLA_Fuente TTF_Font
#define SDLA_Color SDL_Color

#define TECLA_ESC SDLK_ESCAPE
#define TECLA_DER SDLK_RIGHT
#define TECLA_ARR SDLK_UP
#define TECLA_ABA SDLK_DOWN
#define TECLA_IZQ SDLK_LEFT

#define TECLA_J SDLK_j
#define TECLA_A SDLK_a
#define TECLA_C SDLK_c
#define TECLA_R SDLK_r
#define TECLA_V SDLK_v
#define TECLA_1 SDLK_1
#define TECLA_2 SDLK_2
#define TECLA_3 SDLK_3
#define TECLA_4 SDLK_4

#define TECLA_ESP SDLK_SPACE

/*---------------------------*/


int SDLA_inicializar(int ancho, int alto, int colores);

SDLA_Imagen* SDLA_cargarImagen(char *fichero);

void SDLA_colorTransparente(SDLA_Imagen* imagen,
int r, int g, int b);

int SDLA_borrarPantallaOculta(int r, int g, int b);

int SDLA_dibujarImagenOculta(SDLA_Imagen *imagen,
int x,int y);

int SDLA_escribirTextoOculta(char *frase,
int x,int y, int color, SDLA_Fuente *fuente);

int SDLA_visualizarOculta();

int SDLA_teclaPulsada(int c);

int SDLA_esperarTecla();

void SDLA_vaciarBufferTeclado();

int SDLA_ratonPulsado(int *x, int *y);

int SDLA_pausa(long milisegundos);

int SDLA_colision(int x1, int y1, int an1, int al1,
int x2, int y2, int an2, int al2);

#endif



El segundo fichero, que no incluimos directamente desde "juego.cpp", pero es parte del proyecto, se llama "sdlasi.c", y contiene los detalles de esas funciones:

#include "SDL_ASI.h"


/* Adaptacion de SDL, ocultando lo basico y
traduciendo al castellano */

SDLA_Fuente *SDLA_arial12;
SDLA_Fuente *SDLA_arial24;

SDLA_Imagen* pantallaOculta;
SDLA_Imagen* pantallaVisible;


int SDLA_inicializar(int ancho, int alto, int colores) {
// Inicializo SDL
if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
exit(1);

// Me aseguro de que SDL limpia al salir
atexit(SDL_Quit);

// Creo la pantalla visibl
pantallaVisible = SDL_SetVideoMode(
ancho, alto, colores,
SDL_HWSURFACE|SDL_DOUBLEBUF);
if ( !pantallaVisible )
exit(2);

// Y la oculta
pantallaOculta = SDL_CreateRGBSurface(SDL_SWSURFACE,
ancho,alto,colores, 0,0,0,0);
if ( !pantallaOculta )
exit(3);

// Inicializo SDL_TTF
if ( TTF_Init() < 0 )
exit(5);
atexit(TTF_Quit);

SDLA_arial12 = TTF_OpenFont("arial.ttf",12);
SDLA_arial24 = TTF_OpenFont("arial.ttf",24);

return 0;
}

SDLA_Imagen* SDLA_cargarImagen(char *fichero) {
SDLA_Imagen* imagen;
imagen = SDL_LoadBMP(fichero);
if (imagen == NULL)
exit(4);
return imagen;
}


void SDLA_colorTransparente(SDLA_Imagen* imagen,
int r, int g, int b){

SDL_SetColorKey(imagen,
SDL_SRCCOLORKEY|SDL_RLEACCEL,
SDL_MapRGB(imagen->format, r,g,b));
}

int SDLA_borrarPantallaOculta(int r, int g, int b) {
SDL_FillRect(pantallaOculta,
0,SDL_MapRGB(pantallaOculta->format,
r,g,b));
}

int SDLA_dibujarImagenOculta(SDLA_Imagen *imagen,
int x,int y){

SDL_Rect posicion;
posicion.x = x;
posicion.y = y;
SDL_BlitSurface(imagen, 0,
pantallaOculta, &posicion);
}

int SDLA_escribirTextoOculta(char *frase,
int x,int y, int color, SDLA_Fuente *fuente){

SDLA_Color bgcolor,fgcolor;
SDLA_Imagen *textoComoImagen;

/* El color de primer plano sera el escogido */
fgcolor.r = (color & 0xff0000)>>16;
fgcolor.g = (color & 0x00ff00)>>8;
fgcolor.b = color & 0x0000ff;

/* El fondo sera negro (transparente) */
bgcolor.r=0;
bgcolor.g=0;
bgcolor.b=0;

/* Preparo el bitmap */
textoComoImagen = TTF_RenderText_Shaded(fuente,
frase,fgcolor,bgcolor);
SDLA_colorTransparente(textoComoImagen,0,0,0);

/* Y dibujo */
SDLA_dibujarImagenOculta(textoComoImagen, x,y);
}

int SDLA_visualizarOculta() {
SDL_BlitSurface(pantallaOculta, 0,
pantallaVisible, 0);
SDL_Flip( pantallaVisible );
}

int SDLA_teclaPulsada(int c) {
SDL_PumpEvents();
Uint8 *keys = SDL_GetKeyState(NULL);
if (keys[c] == 1)
return 1;
else
return 0;
}

int SDLA_esperarTecla() {
SDL_Event suceso;

/* Imitacion de "getch()" para SDL */
while (1) { /* Repetimos indefinidamente */
while (SDL_PollEvent(&suceso)) { /* Comprobamos sucesos */
if (suceso.type == SDL_KEYDOWN) /* Si es tecla pulsada */
return suceso.key.keysym.sym; /* La devolvermos */
}
}

}

void SDLA_vaciarBufferTeclado() {
SDL_Event suceso;

while ((SDL_PollEvent(&suceso)) &&
(suceso.type == SDL_KEYDOWN))
;
}


int SDLA_ratonPulsado(int *x, int *y) {
SDL_PumpEvents();

return(SDL_GetMouseState(x,y)&SDL_BUTTON(1));
}

int SDLA_pausa(long milisegundos) {
SDL_Delay(milisegundos);
}

int SDLA_colision(int x1, int y1, int an1, int al1,
int x2, int y2, int an2, int al2) {
if ((x2+an2 > x1)
&& (y2+al2 > y1)
&& (x1+an1 > x2)
&& (y1+al1 > y2))
return 1;
else
return 0;
}


Siendo estrictos, necesitamos dos cosas más (que también se pueden descargar desde la página del proyecto): las DLL llamadas SDL.DLL y SDL_TTF.DLL, que deberían estar accesibles (al menos en el PATH, pero para distribuir nuestro juego puede ser cómodo que se encuentren en la misma carpeta que el ejecutable) y el tipo de letra ARIAL.TTF (en las msimas condiciones).

¿Y cual es la página del proyecto? code.google.com/p/fruityfrank

04 octubre 2008

Un "remake" paso a paso

Este es otro proyecto que iré dejando accesible por si a alguien más le interesara seguirlo...

Va a ser un "remake" de un juego de los 80. Sólo será un remake parcial, no pretendo reconstruir el juego original por completo, sino:

  • Crear un juego sencillo paso a paso, de forma que pueda seguirlo cualquier persona que apenas tenga unas nociones básicas de programación.

  • Usar herramientas estándar, como el lenguaje C++ y la biblioteca SDL, de forma que el juego se pueda hacer funcionar tanto en Windows como en Linux o en otros sistemas operativos.

  • Que todos los fuentes queden disponibles en algún servidor de proyectos, como Google Code, de forma que quien esté interesado pueda seguir su evolución.



El juego escogido debería tener estas características:

  • Unos gráficos sencillos, que permitan que nos podamos centrar en la lógica de juego más que en la parte visual.

  • Un personaje que el usuario deba controlar.

  • Tener "premios" que conseguir y "obstáculos" que esquivar.

  • Enemigos que se muevan de forma independiente.

  • Distintas pantallas que recorrer.

  • Una tabla con las mejores puntuaciones.

  • Una pantalla de presentación.

  • ...


Además, es deseable que se trate de un juego que esté disponible para alguna consola u ordenador doméstico, de modo que se puean capturar pantallas, para no perder tiempo en crear gráficos nuevos. También es preferible que sea un juego para el que ya no exista copyright, para que se puedan publicar esas capturas de pantallas. Por eso, en vez de elegir uno de los muchos juegos disponibles para máquinas recreativas, he optado por un juego poco conocido, que se desarrolló en el año 1984 para la gama de ordenadores Amstrad CPC, el Fruity Frank:



Aun así, los pasos que daré se podrán aplicar a otros muchos juegos, tanto nuevos como "remakes" de clásicos.

Si alguien quiere probar el juego original, lo puede descargar (junto con emuladores del Amstrad CPC) en:

www.amstrad.es/juegosamstrad/decargajuegos/fruityfrank.php

Los pasos previstos (el orden puede variar) son:

  1. Ocultar los detalles de SDL, creando ciertas funciones alternativas en español.

  2. Mostrar una primera imagen (la pantalla de fondo inicial de nuestro juego).

  3. Mostrar el personaje sobre esa imagen.

  4. Mover el personaje por encima del fondo, al pulsar las flechas del teclado.

  5. Hacer que el personaje "cambie al andar" y que no se pueda salir de los límites de la pantalla.

  6. Añadir "premios" que el personaje pueda recoger y "obstáculos" que no pueda atravesar

  7. Añadir un enemigo que se mueva al azar.

  8. La partida acaba si tocamos al enemigo.

  9. Tabla de mejores puntuaciones.

  10. Hacer que el movimiento sea más suave.

  11. Una pantalla de presentación.

  12. Añadir varios enemigos de distintos tipos.

  13. Diferentes niveles de dificultad.

  14. ...



Todo eso, muy pronto aquí mismo...   ;-)