25 noviembre 2008

Remake (parcial) de Fruity Frank... 18 - Distintos niveles de juego

El siguiente paso es tener distintos niveles ("pantallas") en nuestro juego, de modo que cuando recojamos todos los "premios" del primer nivel, pasemos a un segundo nivel, y después a un tercero, y así sucesivamente.

En principio, la idea es sencilla: en vez de un único array para representar el mapa de un nivel, tendremos varios arrays, uno para cada para mapa (en realidad, sería una solución más razonable tener un array multidimensional, pero no lo haremos así por ahora):

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

string[] mapa2 = {
"YCYYMY YYYMCYYY",
"MYYCYY YYYMYCMM",
"YMCYYY YYYCPYYC",
"YYCYCY CYYYYYCY",
" ",
"YYMCYM YYYCYYYY",
"YYYYYY YYYYYMYY",
"YYYPYC YYYYYYYY",
"YYPYYC YYYYYCYY",
"YYYYYY YYCYCYYC"
};

string[] mapa3 = {
"ZCZZMZZZ ZPCZZM",
"MZZCZZPZ ZMZCMM",
"ZMCZZZZZ ZCPZZC",
" ",
"ZZCZCZCZ ZZZZCZ",
"ZZMCZMZZ ZCZZZZ",
"ZZZZZZZZ ZZZMZZ",
"ZZZPZCMZ ZZZZZZ",
"ZZPZZCZZ ZZMCZZ",
"ZZZZZZZZ CZCZZC"
};


Tendremos un cuarto mapa, vacío, que representará en cada momento al nivel actual. Antes de empezar cada nivel, volcaremos el mapa de ese nivel en el mapa "en uso". Lo podemos declarar con:

  string[] mapa = new string[MAXFILAS];


Y podemos volcar los datos en él así:

  for (i=0; i<MAXFILAS; i++)
mapa[i] = mapa1[i];


Para saber cuándo pasar de un nivel a otro, podemos contar los "premios" que quedan por recoger. Si no quedan premios en el nivel actual, es el momento de pasar al siguiente nivel:

  // Si no quedan frutas, avanzo de nivel
if (numFrutas == 0)
siguienteNivel();


Y esa función "siguienteNivel" mostraría un aviso o una animación, cargaría el mapa del nuevo nivel en el mapa en uso, restauraría las posiciones iniciales de los enemigos y de nuestro personaje, etc.


En nuestro caso, tenemos varios posibles mapas de nivel, así que tendremos que asignar el que corresponda. Si fuera un array multidimensional, usaríamos como índice el número de nivel; como no es nuestro caso, tendremos que usar varios "if" encadenados. De hecho, crearemos sólo 3 niveles, pero haremos que se puedan jugar en más de 3 pantallas, haciendo que tras la tercera se vuelva a la primera, así:

  // Por ahora solo hay tres niveles, así que alterno:
if (nivel % 3 == 1)
for (i=0; i<MAXFILAS; i++)
mapa[i] = mapa1[i];
else if (nivel % 3 == 2)
for (i=0; i<MAXFILAS; i++)
mapa[i] = mapa2[i];
else for (i=0; i<MAXFILAS; i++)
mapa[i] = mapa3[i];



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

20 noviembre 2008

Remake (parcial) de Fruity Frank... 17 - Varias vidas

Ya es el momento de tener varias vidas, que se pierdan al chocar con un enemigo. Además, cuando se acaben todas las vidas, deberemos volver a la pantalla de presentación, y poder empezar una nueva partida.

Lo de perder una vida cada vez que toquemos a un enemigo supone algunos cambios:


  • Deberemos llevar un contador de vidas.

  • Además, la cantidad de vidas restantes debería aparecer en la pantalla.

  • Cuando "nos maten", habrá que llevar a nuestro personaje y a nuestros enemigos a sus posiciones iniciales: si los dejamos donde estaban, posiblemente volverán a colisionar, y perderíamos otra vida más (o incluso varias).

  • Según sea la lógica de nuestro juego, también es frecuente que cuando choquemos con enemigo, éste también "muera" (no ocurre así en el caso concreto de Fruity Frank).

  • Deberíamos avisar al usuario de que ha perdido una vida, bien sea con una breve animación o con un cartel que se mantuviera en pantalla un par de segundos.



Además, cuando se acaben las vidas deberán ocurrir varias cosas:


  • Deberá terminar la partida actual, y volver a la pantalla de presentación.

  • Al igual que antes, deberemos avisar al usuario (en este caso, de que la partida ha terminado).

  • Deberemos comparar los puntos obtenidos con la máxima puntuación hasta el momento, y actualizarla si procede.

  • Deberemos restaurar el mapa de juego inicial, con todos sus obstáculos y sus premios.

  • Todos los enemigos deberán volver a estar activos y en sus posiciones iniciales.



De paso, la pantalla de presentación deberá permitir volver a jugar una partida, o bien abandonar el juego por completo.

Esto supone unos cuantos cambios en el fuente:


  • Ahora el cuerpo del juego debe ser repetitivo, por ejemplo así:


      Juego juego = new Juego();
// Primero inicializo (variables, imágenes, etc)
juego.inicializar();
do {
// Después, pantalla de presentacion
juego.presentacion();
// Y luego, la partida en sí
if (! juego.finDelJuego)
juego.buclePrincipal();
} while (! juego.finDelJuego);


  • Es decir, desde dentro de la "presentación" anoto si el usuario ha elegido la opción de salir de la partida ("finDelJuego") o la contraria (empezar a jugar).



  • En la presentación se puede tener que entrar al juego (jugar una partida) o tener que abandonarlo. Yo uso una variable "bool" para indicar si hay que seguir repitiendo la presentación o si hay que salir de ella, y otra variable "bool" para indicar si hay que salir de todo el juego:


      while (! salirPresentacion )
{
if (TeclaPulsada (TECLA_ESP)) {
salirPresentacion = true;
nuevaPartida();
}
if (TeclaPulsada (TECLA_S)) {
salirPresentacion = true;
finDelJuego = true;
}
...


  • (Mi función "nuevaPartida" es la que prepara las variables antes de comenzar una partida: número de vidas, posición de personaje y enemigos, etc).



  • La otra dificultad posible es para regenerar el mapa: Hay que tener el mapa guardado en un array, y copiarlo en el array "de uso" antes de empezar cada partida. No se puede hacer "mapa = mapa1;", porque entonces los dos arrays están en la misma posición de memoria, y al modificar uno, se modifica el otro automáticamente, y ya no se podría regenerar el mapa original. Tenemos que hacerlo fila por fila, con un "for":


      for (i=0; i<MAXFILAS; i++)
mapa[i] = mapa1[i];


  • El resto de cambios en el fuente deberían ser sencillos...




Para más detalles, puedes ver el estado del proyecto en: code.google.com/p/fruityfrank


12 noviembre 2008

Remake (parcial) de Fruity Frank... 16 - Un fuente más modular

Nuestro fuente empieza a tener un cierto tamaño. Para ganar en legibilidad, va siendo el momento de hacer un pequeño replanteamiento, para convertirlo en algo más modular.

El "Main" del programa ya era razonablemente breve. Sólo le vamos a añadir una llamada a función "inicializar", que sea la que se encargue de cargar las imágenes, tipos de letra, etc (las variables se declararán antes, al principio del programa, como veremos un poco más adelante):

public static void Main ()
{
Juego juego = new Juego();
// Primero inicializo (variables, imágenes, etc)
juego.inicializar();
// Después, pantalla de presentacion
juego.presentacion();
// Y luego, la partida en sí
juego.buclePrincipal();
}


Por su parte, el bucle de juego (la función "buclePrincipal"), debería ser mucho más modular de lo que era, y más legible. Resultaría más fácil de mantener se pareciera más a la idea intuitiva de lo que debe hacer cada "pasada" de nuestro juego: dibujar la pantalla, comprobar qué teclas pulsa el usuario (y actuar en consecuencia), comprobar colisiones entre elementos del juego, y calcular la nueva posición de los demás elementos del juego (por ahora, sólo nuestros enemigos), por ejemplo así:

do {
dibujarPantalla();
comprobarTeclas();
comprobarColisiones();
moverEnemigos();
// Pausa de 40 ms, para velocidad de 25 fps (1000/40 = 25)
Pausa(40);
// Fin de la parte repetitiva
} while (! TeclaPulsada (TECLA_ESC));


Eso sí, estas funciones comparten información entre ellas. Podríamos hacer que esa información se pasara como parámetros, o bien que simplemente se tratase de "variables globales" (realmente, atributos de nuestra clase "Juego"), que declaremos antes que todas las funciones:

// Constantes que se usarán en el juego
const int NUMENEMIGOS = 5;
// Tipos de enemigos
const int TIPOENEMIGOPEPINO = 1;
const int TIPOENEMIGONARIZ = 2;

// Variables auxiliares para bucles
short i;
// Limites de la pantalla
short xIniPantalla = 12, xFinPantalla = 638;
short yIniPantalla = 82, yFinPantalla = 448;


A partir de ahora, los cambios que se acercan afectarán menos a otras partes del juego. Se tratará de cambios como: como perder vidas cuando choquemos con un enemigo, un movimiento más inteligente de estos enemigos, poder "disparar", que ciertos obstáculos sean móviles, que haya un tabla de records, etc.

Para más detalles, puedes ver el estado del proyecto en: code.google.com/p/fruityfrank

11 noviembre 2008

Huelga de estudiantes y profesionales de informatica

Convocada para el 19 de Noviembre (en España).

Para reclamar competencias profesionales. Nada más y nada menos que cualquier otro ingeniero o cualquier otro titulado.

A cualquiera (menos a nuestro gobierno) le parece absurdo que entre las competencias de un Ingeniero en Telecomunicaciones se incluyan redes, sistemas operativos, programación, bases de datos y otras tantas... mientras que para un Ingeniero en Informática no existen competencias definidas.

Si quieres más detalles:

http://www.huelgainformatica.es/

Y si quieres difundirlo, incluye este banner (o cualquier otro similar) en tu página web:


09 noviembre 2008

Remake (parcial) de Fruity Frank... 15 - Colisiones por coordenadas

Sabemos cómo comprobar colisiones usando nuestro "mapa" de juego. Es interesante como primera aproximación, pero generalmente querremos algo "más fino": que el movimiento vuelva a a ser suave (algo que haremos pronto) y que las colisiones se puedan comprobar antes, en cuanto un elemento "roce" al otro (eso sí que lo haremos ahora).

Vamos a mejorar las colisiones que se basan exactamente en posiciones del mapa, para ver una forma de comprobar colisiones entre dos imágenes. Aun así, lo que haremos tampoco será perfecto: comprobaremos si el rectángulo de una imagen se solapa con el rectángulo de otra imagen, pero habitualmente esto será totalmente preciso, porque la mayoría de imágenes (personaje, enemigos, frutas) no ocupan por completo su rectángulo, así que puede que consideremos que ha habido una colisión cuando realmente se estén solapando dos "zonas negras" de las imágenes.

Otras alternativas más precisas incluirían el descomponer la imagen en una serie de rectángulos que sí contengan elementos visibles (lo que complicaría la forma de comprobar colisiones), o bien mirar los píxeles que se solapan para ver si son "transparentes", y entonces no dar la colision como válida (lo que también complicaría, además de ralentizar ligeramente). Por ahora no afinaremos tanto, y nos quedaremos con una aproximación más precisa que la de antes, que funcionará cuando un enemigo no se encuentre justo en una casilla del mapa, sino pasando de una a otra, pero que tampoco es totalmente precisa, especialmente con personajes que tengan formas irregulares.

Una forma de comprobar si se solapan dos rectángulos es ver qué ocurre con sus extremos: en horizontal, si el extremo izquierdo de una imagen está más a la derecha que el extremo derecho de la otra, no se solapan; para que se solapen, el extremo derecho de una imagen (x1) debe ser estar a la izquierda del extremo izquierdo de la otra (x1 < x2+ancho2), y ocurrir lo mismo a la inversa (x2 < x1+ancho1), y algo similar debe ocurrir en vertical:

if ((x2+an2 > x1)
&& (y2+al2 > y1)
&& (x1+an1 > x2)
&& (y1+al1 > y2))
colision = true;
else
colision = false;


Pero en nuestro caso, parte del trabajo ya está hecho: esa comprobación forma parte de la clase "SdlN":

public bool 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 true;
else
return false;
}


De modo que si nuestro juego se apoya en SdlN, basta con que comprobemos las colisiones así:

if (Colision(enemigos[i].x, enemigos[i].y, 
enemigos[i].ancho, enemigos[i].alto,
posXPersonaje, posYPersonaje,
anchoPersonaje, altoPersonaje) )
enemigos[i].activo = false;


En este caso, como primera aproximación, pero que todavía no sigue una lógica de juego real, cada vez que tocamos un enemigo, éste desaparece (activo = false). En un juego real, nuestro enemigo perdería una vida y volveríamos a comenzar la partida en el nivel actual. Si la colisión fuera entre un disparo y un enemigo, deberíamos dibujar una explosión antes de hacerlo desaparecer. Esos detalles pertenecen a la lógica de cada juego, y nos iremos acercando a ellos poco a poco.


De paso, vamos a hacer otra pequeña mejora en esta versión: la línea superior, que muestra la puntuación, el record y la cantidad de vidas que nos quedan, va a dejar de ser "predibujada". En vez de cargar la imagen que contiene casi todo eso fijo, vamos a ser nosotros los que mostremos los puntos (ya lo hacíamos), el record (que era fijo), la cantidad de vidas (aunque todavía no nos puedan matar nuestros enemigos) y un recuadro alrededor de todo ello:

// Tabla de records y vidas restantes
RectanguloRGBA(15,45,625,80, // Marco para records
255, 109, 8, // Naranja
255); // Opaco
EscribirTextoOculta("Puntos",20,60,
0x88, 0xFF, 0xFF, fuenteSans14);
EscribirTextoOculta(puntos.ToString("000000"),70,60,
0xFF, 0xFF, 0x88, fuenteSans14);
EscribirTextoOculta("Mejor puntuac",200,60,
0x88, 0xFF, 0xFF, fuenteSans14);
EscribirTextoOculta(mejorPunt.ToString("000000"),300,60,
0xFF, 0xFF, 0x88, fuenteSans14);
EscribirTextoOculta("Vidas",450,60,
0x88, 0xFF, 0xFF, fuenteSans14);
for (i=0; i<vidas-1; i++)
DibujarImagenOculta(personajeD, (short)(490+i*30),48);


(Por supuesto, esto supone algunos pequeños cambios en el juego, como añadir la variable "vidas" o la variable "mejorPunt").


Para más detalles, puedes ver todo el proyecto en: code.google.com/p/fruityfrank

07 noviembre 2008

Remake (parcial) de Fruity Frank... 14 - Movimiento animado

Nuestro personaje ya cambia de forma: según si nos movemos hacia la derecha, la izquierda o arriba/abajo, muestra una imagen distinta. Ahora vamos a hacer que cambie ligeramente de forma mientras se mueve en una misma dirección.

Existen varias formas de hacerlo.

Una forma podría ser basarnos en su coordenada X (por ejemplo), de modo que si X es par, se muestre una imagen, y si X es impar, se muestre otra forma distinta. Esto tiene inconvenientes. Por ejemplo, si el incremento con el que se mueve nuestro personaje es 2, siempre se vería la misma imagen.

Otra forma más eficiente podría ser llevar cuenta de qué imagen acabamos de dibujar. Por ejemplo, tener dos imágenes "derecha1" y "derecha2" de nuestro personaje moviéndose a la derecha. Si la última que hemos dibujado es "derecha1", la siguiente que toca es "derecha2" y viceversa. Se podría conseguir así:

if ( TeclaPulsada (TECLA_DER) )
{
if ((xPersonaje < MAXCOLS-1)
&& (mapa1[yPersonaje][xPersonaje+1] != 'M'))
{
xPersonaje ++;
// Alterno entre dos imagenes a la derecha
if (posicion == DERECHA1)
{
personaje = personajeD;
posicion = DERECHA2;
}
else
{
personaje = personajeD2;
posicion = DERECHA1;
}
}
}


En ese ejemplo, DERECHA1 y DERECHA2 podrían ser constantes simbólicas, con valores 1 y 2, respectivamente.

Si tenemos más de dos "fotogramas" de nuestro personaje, podemos hacer que "posición" tome más valores, por ejemplo de 0 a 5 (si tenemos 6 fotogramas en cada dirección), y guardar las imágenes en un array, y entonces haríamos:

    // La posicion va rotando
posicion ++;
if (posicion == DERECHA5)
posicion = DERECHA0;
personaje = imagenPersonaje[posicion];


De la misma forma, podríamos hacer que hubiera animaciones en cada una de las posibles direcciones del movimiento, o en el movimiento de los enemigos.


Para más detalles, puedes ojear todo el proyecto en: code.google.com/p/fruityfrank

05 noviembre 2008

Remake (parcial) de Fruity Frank... 13 - Migrando a C#

Antes de seguir ampliando las posibilidades del juego, llega el momento de hacer un cambio drástico: usar otro lenguaje de programación. Vamos a usar C#, que permite despreocuparnos de ciertos detalles de bajo nivel, y además nos permitirá usar una metodología orientada a objetos para rediseñar el juego... dentro de no mucho tiempo.

A pesar de que C# deriva de C, y de que vamos a usar la librería Tao.SDL que deriva de SDL, habrá que hacer algún que otro cambio.

Como en la versión anterior, crearemos un fichero auxiliar que oculte un poco de SDL, simplifique algunos pasos, y que además nos traduzca las órdenes a español. En este caso, se llama "sdl_n.cs" y se puede ver en la página del proyecto.

Los cambios a nuestro juego, sin ser grandes, son unos cuantos:


  • Todo el programa pasará a formar parte de una clase: "public class Juego". En concreto, como se apoyará en la clase auxiliar SdlN, comenzaremos con "public class Juego: SdlN".


  • Los datos de nuestros enemigos están en un "struct"; los componentes de este "struct" ahora deben ser public.


  • Cambia la declaración del "array" que contiene nuestros enemigos: ahora deberá ser al estilo de C#: enemigo[] enemigos = new enemigo[NUMENEMIGOS];


  • El mapa del juego ahora será un "array" de "strings". Se declara casi igual, pero ahora tendremos una columna menos en MAXCOLS (ya no hace falta dejar un carácter adicional para indicar el final de cadena). Por ese mismo motivo, la comprobación de la columna en la que estamos no acaba en MAXCOLS-2, sino en MAXCOLS-1. De igual modo, como las cadenas en C# no son modificables, ya no podemos alterar el mapa con "mapa1[yPersonaje][xPersonaje] = ' ';", sino que deberemos usar un "remove" para quitar un carácter y luego un "insert" para introducir el nuevo carácter en su lugar.


  • Podemos aprovechar para cambiar el tipo de alguna variable. Por ejemplo, las coordenadas X e Y pueden ser un "short" en vez de un entero de 32 bits. Las componentes R, G y B de un color serán "byte". Por eso será necesario también cambiar algunos datos durante el desarrollo de juegos, forzando una conversión de tipos ("cast"), bien porque aprovechemos datos auxiliares que todavía sean "int" y debamos convertir a "short": datoShort = (short) datoEntero, o bien porque hagamos operaciones y el compilador no tenga la certeza de que el resultado vaya a caber en un entero corto: (short) (xIniPantalla + xPersonaje * anchoCasilla)


  • Todo el cuerpo del juego lo sacaremos de "Main" para meterlo en una nueva función, llamada (por ejemplo) "BuclePrincipal". Así nuestro "Main" contendrá apenas 3 órdenes: la declaración de un juego, su presentación y su bucle principal.


  • Como peculiaridad de la librería que usaremos (Tao.Sdl), y por no profundizar más por ahora, nuestros tipos de datos SDLA_Imagen* y SDLA_Fuente* ahora pasarán a ser un único tipo "IntPtr".


  • Todos los SDLA_xxx cambian de nombre, perdiendo ese "SDLA_" del principio, para abreviar un poco y tratar de ganar en legibilidad. Por ejemplo, en vez de "SDLA_teclaPulsada" usaremos "TeclaPulsada", y "SDLA_dibujarImagenOculta" se convertirá en "DibujarImagenOculta".



Como siempre, todos los fuentes están en: code.google.com/p/fruityfrank

03 noviembre 2008

Remake (parcial) de Fruity Frank... 12 - Una pantalla de presentacion

Nuestro juego, a pesar de estar apenas empezando a ser un borrador de lo que tendrá que ser finalmente, ocupa ya cerca de 230 líneas de código. Si esto sigue creciendo de esta manera, dentro de poco empezará a ser difícil de manejar. Va siendo el momento de empezar a hacerlo más modular.

Como primer acercamiento a esa modularidad, vamos a crear una pantalla de presentación para el juego, y esta pantalla estará en una nueva función, independiente de "main". La pantalla de presentación mostrará un animación sencilla (por ejemplo, un texto rebotando en la pantalla) hasta que pulsemos una cierta tecla (como la barra espaciadora). En ese momento será cuando realmente comience la partida.

De este modo, ahora el "main" comenzará así:


int main (int argc, char** argv)
{

// Antes de nada, pantalla de presentacion
presentacion();
...


Y esta función "presentacion" podrá ser de tipo "void" (no va a devolver ningún valor), y deberá comenzar por inicializar la pantalla y declarar sus propias variables locales (entre ellas puede estar incluso el tamaño de la pantalla, que ya estaban declaradas dentro de "main" y que se repetirán aquí a no ser que las hagamos "variables globales").

void presentacion()
{
// Limites de la pantalla
int xIniPantalla = 12, xFinPantalla = 638;
int yIniPantalla = 82, yFinPantalla = 448;

// Intentamos inicializar SDL
SDLA_inicializar(640,480,24);
...


Ahora, la llamada a "SDLA_inicializar" ya no deberá aparecer en "main", o habrá pasos que estaremos dando dos veces, y obtendremos mensajes de error.

En cuanto al contenido de esa pantalla de presentación, puede ser cualquier cosa que se repita hasta que pulsemos una tecla. Por ejemplo, el "cartel" representativo del juego rebotando en los extremos de la pantalla. Conviene que la tecla que se pulse para salir no sea ESC, que es la que utilizamos en el juego, o puede ocurrir que salgamos de la pantalla de presentación... pero también del juego, sin llegar a jugar...


Como siempre, todos los fuentes están en: code.google.com/p/fruityfrank

01 noviembre 2008

Remake (parcial) de Fruity Frank... 11 - Varios enemigos distintos

Si queremos tener varios enemigos, cada uno de los cuales tenga una posición, una velocidad distinta, etc., una forma sencilla de hacerlo es crear un "array de structs". El "struct" representará los datos que vamos a guardar de cada uno de nuestros enemigos. Por ejemplo:

struct enemigo
{
int x; // Posicion horizontal
int y; // Posicion vertical
int ancho; // Ancho en pixeles
int incrX; // Incremento horiz (velocidad)
int tipo; // Tipo de enemigo: Pepino, narizotas, etc
bool activo; // Vivo o muerto?
};


Y el array guardará esos datos para una serie de enemigos:

const int NUMENEMIGOS = 5;
enemigo enemigos[NUMENEMIGOS];


Antes preparábamos una serie de datos iniciales para un único enemigo:

    // Posicion inicial y tamaño del enemigo
int xEnemigo1=250, yEnemigo1=250;
int anchoEnemigo1=40;
int incrEnemigo=3;


Ahora tendremos que preparar datos iniciales para varios enemigos, así que lo podemos hacer de forma repetitiva, con un "for":

    // Posicion inicial y tamaño de los enemigos
enemigo enemigos[NUMENEMIGOS];
for (i=0; i<NUMENEMIGOS; i++)
{
enemigos[i].x = 250 + i*40;
enemigos[i].y = 250 + i*30;
enemigos[i].ancho = 40;
enemigos[i].incrX = 3;
if (i<3)
enemigos[i].tipo = TIPOENEMIGOPEPINO;
else
enemigos[i].tipo = TIPOENEMIGONARIZ;
enemigos[i].activo = true;
}


De igual modo, antes dibujábamos un único enemigo:

   SDLA_dibujarImagenOculta(enemigo1, xEnemigo1, yEnemigo1);


y ahora dibujaremos varios, que además pueden ser de distintos tipos, y estar activos ("vivos") o no ("muertos"):

        for (i=0; i<NUMENEMIGOS; i++)
{
if (enemigos[i].activo)
{
if (enemigos[i].tipo == TIPOENEMIGOPEPINO)
enemigo = enemigoPepino;
else
enemigo = enemigoNariz;
SDLA_dibujarImagenOculta(enemigo, enemigos[i].x,enemigos[i].y);
}
}


Y tras cada fotograma deberemos mover los enemigos, cada uno de ellos con su propia posición y velocidad (incremento de posición). Antes era así

        // Movimiento de enemigo (de momento: lado a lado)
xEnemigo1 += incrEnemigo;

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


Y ahora será parecido, pero repetitivo y accediendo a los campos de cada "struct", así:

        // Movimiento de enemigos (de momento: lado a lado)
for (i=0; i<NUMENEMIGOS; i++)
{
enemigos[i].x += enemigos[i].incrX;

if (enemigos[i].x < xIniPantalla) {
enemigos[i].x = xIniPantalla;
enemigos[i].incrX = -enemigos[i].incrX;
}
if (enemigos[i].x > xFinPantalla-enemigos[i].ancho) {
enemigos[i].x = xFinPantalla-enemigos[i].ancho;
enemigos[i].incrX = -enemigos[i].incrX;
}
}



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