30 mayo 2008

Programar videojuegos con la librería SDL (6)

CREANDO LOS ENEMIGOS

Lo siguiente que vamos a crear en nuestro pequeño videojuego son los aviones enemigos. El sprite del enemigo va a ser el siguiente:


Ahora vamos a crear un registro en la unidad UArcade.pas para controlar cada enemigo:

TEnemigo = record
x, y: Integer;
bActivo: Boolean;
end;

En la misma unidad también tenemos que declarar una variable para el sprite del enemigo así como un array de enemigos:

var
Avion, Fondo, Disparo, Enemigo: TSprite;
Disparos: array[1..10] of TDisparo;
iTmpDisparo: UInt32;
Enemigos: array[1..10] of TEnemigo;
iTmpEnemigos: UInt32;

La variable iTmpEnemigos la vamos a utilizar para contar el tiempo que va a tardar en salir en siguiente enemigo. Ahora tenemos que hacer que el procedimiento CargarSprites cargue el sprite del enemigo:

procedure CargarSprites;
begin
.....
Enemigo := TSprite.Create;
Enemigo.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) + 'enemigo.bmp' );
end;

Y como no podía ser menos, hay que liberar el sprite cuando termina el juego:

procedure DestruirSprites;
begin
Avion.Free;
Fondo.Free;
Disparo.Free;
Enemigo.Free;
end;

Ahora tenemos que hacer un procedimiento que muestra los enemigos en pantalla. Vamos a hacer que aparezcan por la parte superior de la pantalla y que vayan descendiendo hasta que desaparezca por la parte inferior:

procedure ControlarEnemigos;
var
i: Integer;
begin
// Cada 3 segundos saco un nuevo enemigo
if SDL_GetTicks - iTmpEnemigos > 1000 then
begin
Randomize;
iTmpEnemigos := SDL_GetTicks;

for i := 1 to 10 do
if not Enemigos[i].bActivo then
begin
Enemigos[i].bActivo := True;
Enemigos[i].x := Random( 550 ) + 10;
Enemigos[i].y := -73;
break;
end;
end;

for i := 1 to 10 do
if Enemigos[i].bActivo then
begin
Inc( Enemigos[i].y, 3 );

if Enemigos[i].y > 480 then
Enemigos[i].bActivo := False;

// Dibujamos el sprite del enemigo en pantalla
Enemigo.x := Enemigos[i].x;
Enemigo.y := Enemigos[i].y;
Enemigo.Dibujar;
end;
end;

Al principio del juego considero que los 10 enemigos están inactivos e invisibles. Entonces la primera parte del procedimiento anterior espera un segundo hasta sacar el siguiente enemigo (de la lista de 10 enemigos). Cuando un enemigo aparece en pantalla lo sitúo en una coordenada horizontal aleatoria y verticalmente lo escondo en la parte superior de la pantalla para que aparezca suavemente.

Después el segundo bucle se encarga de mover hacia abajo los enemigos que están activos y los dibuja en pantalla. Si se salen de la pantalla lo deja inactivos y listos para la siguiente aparición.

Ahora sólo hay que llamar al procedimiento ControlarEnemigos dentro del procedimiento DibujarSprites:

procedure DibujarSprites;
begin
Fondo.Dibujar;
Avion.Dibujar;
ControlarDisparos;
ControlarEnemigos;
end;

Al ejecutar el juego tienen que ir apareciendo nuevos enemigos cada segundo:


DESTRUYENDO LOS ENEMIGOS

Para que quede bien la destrucción de los enemigos necesitamos una serie de fotogramas que simulen el efecto de explosión. Buscando un poco con Google pude encontrar estos gráficos y los reajusté con el programa de dibujo Gimp:


En esta ocasión nuestro sprite va a tener 4 subsprites (4 verticales y 1 horizontal). Para ello definimos primero la variable del sprite de la explosión:

var
Avion, Fondo, Disparo, Enemigo, Explosion: TSprite;
...

Ampliamos el procedimiento CargarSprites:

procedure CargarSprites;
begin
...
Explosion := TSprite.Create;
Explosion.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) + 'explosion.bmp' );
Explosion.bSubsprites := True;
Explosion.iAnchoSub := 95;
Explosion.iAltoSub := 73;
Explosion.iSubX := 0;
Explosion.iSubY := 0;
end;

A diferencia de los sprites simples, le hemos dicho que va a tener subsprites de tamaño 95 x 73 cada uno. El subsprite seleccionado va a ser el primero comenzando por la izquierda (iSubX=0, iSubY = 0).

Y nos encargamos también de destruirlo:

procedure DestruirSprites;
begin
Avion.Free;
Fondo.Free;
Disparo.Free;
Enemigo.Free;
Explosion.Free;
end;

Cuando disparemos al enemigo lo que tenemos que hacer es que explote. Para ello voy a ampliar el registro del enemigo para introducir una variable booleana que controla si el enemigo está explotando:

TEnemigo = record
x, y: Integer;
bActivo, bExplotando: Boolean;
iTmpExplosion: UInt32;
end;

También he añadido la variable iTmpExplosion para controlar los milisegundos que han pasado desde que empezó a explotar. Esto nos va a permitir el ir cambiando cada fotograma de la explosión cada 100 milisegundos.

Lo siguiente que toca es modificar el procedimiento ControlarEnemigos para añadirle el efecto de la explosión:

procedure ControlarEnemigos;
var
i: Integer;
iTmpTranscurrido: UInt32;
begin
// Cada 3 segundos saco un nuevo enemigo
if SDL_GetTicks - iTmpEnemigos > 1000 then
begin
Randomize;
iTmpEnemigos := SDL_GetTicks;

for i := 1 to 10 do
if not Enemigos[i].bActivo and not Enemigos[i].bExplotando then
begin
Enemigos[i].bActivo := True;
Enemigos[i].x := Random( 550 ) + 10;
Enemigos[i].y := -73;
break;
end;
end;

for i := 1 to 10 do
begin
if Enemigos[i].bActivo then
begin
Inc( Enemigos[i].y, 3 );

if Enemigos[i].y > 480 then
Enemigos[i].bActivo := False;

// Dibujamos el sprite del enemigo en pantalla
Enemigo.x := Enemigos[i].x;
Enemigo.y := Enemigos[i].y;
Enemigo.Dibujar;
end;

if Enemigos[i].bExplotando then
begin
iTmpTranscurrido := SDL_GetTicks - Enemigos[i].iTmpExplosion;

if iTmpTranscurrido <>= 100 ) and ( iTmpTranscurrido <>= 200 ) and ( iTmpTranscurrido <> 400 then
Enemigos[i].bExplotando := False;
end;
end;
end;

En el segundo bucle encargado de dibujar a cada enemigo también controlo si está explotando, con lo cual según los milisegundos que han pasado voy asignando el fotograma correspondiente:

De 0 a 99 milisegundos -> fotograma 0
De 100 a 199 milisegundos -> fotograma 1
De 200 a 299 milisegundos -> fotograma 2
De 300 en adelante -> fotograma 3

Ahora vamos a ampliar el procedimiento de ControlarDisparos para que compruebe si cada disparo choca con cada avión activo:

procedure ControlarDisparos;
var
i, j: Integer;
begin
for i := 1 to 10 do
if Disparos[i].bActivo then
begin
Disparo.x := Disparos[i].x;
Disparo.y := Disparos[i].y;
Disparo.Dibujar;

Dec( Disparos[i].y, 20 );

if Disparos[i].y < -20 then
Disparos[i].bActivo := False;

for j := 1 to 10 do
if Enemigos[j].bActivo then
if ( Disparos[i].x + 16 >= Enemigos[j].x ) and
( Disparos[i].x + 16 <= Enemigos[j].x + 95 ) and
( Disparos[i].y + 9 >= Enemigos[j].y ) and
( Disparos[i].y + 9 <= Enemigos[j].y + 73 ) then
begin
Enemigos[j].bActivo := False;
Enemigos[j].bExplotando := True;
Enemigos[j].iTmpExplosion := SDL_GetTicks;
Disparos[i].bActivo := False;
end;
end;
end;

Con el bucle i recorro todos los disparos y con el bucle j controlo si ese disparo choca con algún enemigo. En el caso de que choque lo pongo como inactivo y activo su estado de explotando. También me apunto el tiempo inicial (los tics del reloj del sistema) de cuando ha empezado a explotar ese enemigo.

Al ejecutar el juego ya podemos acribillar a los enemigos:


CAMBIANDO EL TITULO DE LA VENTANA

Una cosa que se me olvidó mencionar en artículos anteriores es que podemos modificar el título de la ventana de nuestro juego. Para ello se utiliza el siguiente procedimiento de la librería SDL:

procedure SDL_WM_SetCaption( const title : PChar; const icon : PChar);

El primer parámetro es el título de la ventana y el segundo es el icono del juego. Yo sólo he utilizado el primero, ya que cuando terminemos el juego lo vamos a poner a pantalla completa y el icono no se va a ver.

Ahora sólo queda modificar el procedimiento InicializarSDL:

procedure InicializarSDL( sTitulo: String );
begin
// Inicializamos la librería SDL
if SDL_Init( SDL_INIT_VIDEO or SDL_DOUBLEBUF or SDL_INIT_JOYSTICK ) < 0 then
begin
ShowMessage( 'Error al inicializar la librería SDL.' );
SDL_Quit;
bSalir := True;
Exit;
end;

SDL_WM_SetCaption( PChar( sTitulo ), 0 );
bSalir := False;
end;

Luego en la unidad principal juego.dpr tenemos que llamar a esta función con el título de la ventana:

InicializarSDL( 'Mi primer juego en SDL' );
ModoVideo( 640, 480, 16, True );
...


EL PROYECTO SUBIDO A INTERNET

Como el proyecto se va volviendo más complicado, os he subido a Internet el proyecto con el ejecutable incluido. Si queréis compilarlo ya sabéis que necesitáis la librería SDL como mencioné en capítulos anteriores, aunque podéis ejecutarlo conforme está.

Lo he subido a tres servidores distintos, por si acaso no os funciona alguno:

http://rapidshare.com/files/118766119/DelphiAlLimite_Arcade_SDL.zip.html
http://www.megaupload.com/?d=FH1N2A8A
http://hyperupload.com/download/02d38e5e73/DelphiAlLimite_Arcade_SDL.zip.html

Si lo descargáis del último servidor (hyperupload) veréis que le ha quitado la extensión zip. Sólo tenéis que ponérsela y listo.

En el próximo artículo veremos como crear un scroll de pantalla, cómo dibujar los marcadores para la puntuación y veremos como reproducir sonidos.

Pruebas realizadas en Delphi 7.

5 comentarios:

Anónimo dijo...

sos groso sabelo

Abismo Neo dijo...

oye en esta linea de codigo ke pusiste en el procedimiento CONTROLARENEMIGOS, tengo duda lo pusiste asi a proposito o porke esta asi?

if iTmpTranscurrido <>= 100 ) and ( iTmpTranscurrido <>= 200 ) and ( iTmpTranscurrido <> 400 then

porke asi no deja con el "<>=" , digo es kuriosidad :D

Administrador dijo...

La expresión <>= no existe.

>= mayor o igual
<= menor o igual
<> distinto
> mayor
< menor

Simplemente tienes que indicar que intervalo de tiempo quieres controlar.

Administrador dijo...

Mirando el código fuente es un fallo mío o mas bien del puto blogger.

Cada vez que paso el código fuente de Delphi al blog me hace unos solares impresionantes.

Realmente es <> (distinto). Cuando tenga tiempo corregiré esto.

Abismo Neo dijo...

ahi estuve probando algunas variaciones para ver ke podia ser, pero pues ya se pudo, aunke revise un poco tmabine del koigo del proyecto ke subiste y tiene muchas kosas ke no incluiste ahi (o kosa del blogger).

okomo el Explosion.dibujar. :p

saludos, seguimos checando el tuto :D

Publicidad