06 junio 2008

Programar videojuegos con la librería SDL (7)

CREANDO UN SCROLL VERTICAL

Al igual que nos juegos clásicos de arcade, vamos a crear un scroll vertical hacia abajo para que parezca que estamos avanzando sobre el mar. Realmente vamos a utilizar la misma textura del mar, pero la vamos a dividirla en dos trozos: uno que baja y otro que lo alimenta por arriba:


Mientras hacemos que el fondo baje por la pantalla, arriba se nos va quedando un hueco que tenemos que completar con el mismo fondo. Para hacer esto vamos a implementar en nuestra unidad genérica UJuego.pas un nuevo procedimiento para copiar trozos de imagen de una superficie a otra:

procedure CopiarImagen( Origen, Destino: PSDL_Surface;
x1, y1, x2, y2, iAncho, iAlto: Integer );
var
RO, RD: TSDL_Rect; // rectángulos origen y destino
begin
RO.x := x1;
RO.y := y1;
RO.w := iAncho;
RO.h := iAlto;
RD.x := x2;
RD.y := y2;
RD.w := iAncho;
RD.h := iAlto;
SDL_BlitSurface( Origen, @RO, Pantalla, @RD );
end;

También necesitamos una variable glogal en la unidad UArcade.pas para controlar la posición del scroll:

var
iScroll: Integer;

Y por último modificamos el procedimiento DibujarSprites para conseguir el efecto deseado:

procedure DibujarSprites;
begin
// Parte inferior del scroll
CopiarImagen( Fondo.Superficie, Pantalla, 0, 0, 0, iScroll, 640, 480 - iScroll );

// Parte superior del scroll
CopiarImagen( Fondo.Superficie, Pantalla, 0, 480 - iScroll, 0, 0, 640, iScroll );

// Movemos el fondo hacia abajo
Inc( iScroll );

if iScroll > 480 then
iScroll := 0;

Avion.Dibujar;
ControlarDisparos;
ControlarEnemigos;
end;

Así quedaría al ejecutar el juego:


Si os fijáis en la imagen se puede apreciar el corte de donde empieza y termina la textura del mar verticalmente. Lo ideal sería conseguir una textura simétrica para que no se note dicho escalón. En las páginas sobre texturas 3D que hay por Internet se puede encontrar gran cantidad de material.

ACCEDIENDO A LAS FUENTES PARA ESCRIBIR TEXTO

Renderizar texto en una superficie no es tan fácil como imprimir un mensaje en Windows utilizando el canvas. La librería SDL dispone de dos formas de escribir texto: utilizando una fuente predefinida en un bitmap o utilizar las fuentes reales de Windows mediante archivos TTF.

Yo me he decantado por la segunda opción, ya que aunque es más difícil, sí que nos da más libertad al escribir texto (cambiando la fuente, el estilo, etc.). Para cumplir este objetivo vamos a utilizar una librería asociada a la SDL llamada SDL_TTF. Esta librería se encuentra en un subdirectorio donde hemos instalado la SDL:


Así que ese directorio tenemos que añadirlo a los directorios de búsqueda del proyecto:


También necesitamos bajarnos de Internet la DLL donde está esta librería y guardarla en el directorio de nuestra aplicación:

http://www.libsdl.org/projects/SDL_ttf/release/SDL_ttf-2.0.9-win32.zip

Ese zip lleva estos archivos:

La versión de esa DLL es la 2.0.9, mientras que nuestras cabeceras de SDL del proyecto Delphi son de la versión 1.5, aunque todo funciona a la perfección.

Ahora vamos a hacer una serie de rutinas estándar en nuestra unidad UJuego.pas para poder imprimir texto encima de cualquier superficie. Lo primero es añadir la nueva unidad sdl_ttf en la sección uses:

uses Windows, SysUtils, Dialogs, SDL, sdl_ttf;;

Para poder utilizar las fuentes hay que llamar a los procedimientos TTF_Init y TTF_Quit. Esto lo vamos a encapsular en dos procedimientos:

procedure InicializarFuentes;
begin
TTF_Init;
end;

procedure FinalizarFuentes;
begin
TTF_Quit;
end;

Estos procedimientos hay que llamarlos en bucle principal de programa, en la unidad juego.dpr. Lo llamamos después de inicializar el modo de vídeo:

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

y cuando nos salimos del juego:

...
FinalizarFuentes;
FinalizarSDL;
end.


Ahora necesitamos un procedimiento para abrir una fuente de Windows y que se quede en memoria y otro para cerrarla al salir de juego (en la unidad UJuego.pas):

function CrearFuente( sNombre: String; Tamano: Integer ): PTTF_Font;
begin
Result := TTF_OpenFont( PChar( 'c:\windows\fonts\' + sNombre ), Tamano );
end;

procedure EliminarFuente( Fuente: PTTF_Font );
begin
TTF_CloseFont( Fuente );
end;

Como puede verse he utilizado el puntero a una fuente PTTF_Font para crearla dando además la ruta del directorio de Windows. Aunque para crear un juego profesional independiente habría que copiar la fuente que vamos a utilizar en nuestro directorio local y abrirla desde ahí, no vaya a ser que el usuario no tenga esa fuente instalada en Windows.

El siguiente procedimiento que vamos a crear va a dibujar el texto en la superficie que le pasemos como parámetro:

procedure EscribirTexto( Superficie: PSDL_Surface; x, y: Integer; sTexto: String;
Fuente: PTTF_Font; Color: TSDL_Color );
var
Texto: PSDL_Surface;
begin
Texto := TTF_RenderText_Solid( Fuente, PChar( sTexto ), Color );
CopiarImagen( Texto, Superficie, 0, 0, x, y, Texto.w, Texto.h );
SDL_FreeSurface( Texto );
end;

La función TTF_RenderText_Solid escribe el texto en una superficie, pero hay que llevar cuidado, ya que lo que hace realmente es crear una nueva superficie y escribir texto en ella. Luego hay que acordarse de liberarla de memoria llamando al procedimiento SDL_FreeSurface.

DIBUJANDO LOS MARCADORES DE PUNTUACIÓN

Ahora que ya tenemos todo lo que necesitamos para escribir texto en una superficie vamos a crear la fuente en nuestra unidad UArcade.pas. Lo primero es vincular también la unidad sdl_ttf al principio de la unidad:

uses SysUtils, UJuego, SDL, sdl_ttf;

Después creamos las variables para almacenar la fuente, el color de la misma y la puntuación que llevamos acumulada en el juego:

var
...
Verdana: PTTF_Font;
Blanco: TSDL_Color = ( r: $FF; g: $FF; b: $FF; unused: 0 );
rPuntuacion: Real;

Los colores en la librería SDL se almacenan en la estructura de datos TSDL_Color especificando los colores rojo (red -> r), verde (green -> r ) y azul (blue -> b). En este caso, como voy a escribir la puntuación en color blanco, he puesto cada valor a $FF.

Lo siguiente es ampliar el procedimiento CargarSprites para que cargue la fuente verdana:

procedure CargarSprites;
begin
...
Verdana := CrearFuente( 'verdana.ttf', 20 );
end;

Y también nos encargamos de liberarla en el procedimiento DestruirSprites:

procedure DestruirSprites;
begin
EliminarFuente( Verdana );
...
end;

Para dibujar el marcador de puntuación en pantalla encima de todo lo demás, tenemos que hacerlo después de que se dibujen todos los sprites (fondo, aviones, etc.). Por ello vamos a dibujar el marcador de puntuación al final del procedimiento DibujarSprites:

procedure DibujarSprites;
begin
...
EscribirTexto( Pantalla, 450, 0, 'Puntuación: ' +
FormatFloat( '0000', rPuntuacion ), Verdana, Blanco );
end;

Por último, tenemos que modificar el procedimiento ControlarDisparos para que incremente la puntuación en 1 cuando un disparo alcanza al enemigo:

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;
rPuntuacion := rPuntuacion + 1;
end;
end;
end;

Y este es el resultado final al ejecutar el juego:


En este artículo tenía previsto cómo reproducir sonido y música utilizando la librería SDL, pero como es bastante extenso lo vamos a dejar para la semana que viene.

En el próximo artículo veremos también como cargar los gráficos utilizando los formatos JPG y PNG (los más avanzados) y veremos un ejemplo que como crear un juego de plataformas mediante piezas (tiles).

Aquí tenéis todo el proyecto en RapidShare, Megaupload y Hyperupload:

http://rapidshare.com/files/120451546/DelphiAlLimite_Arcade2_SDL.zip.html

http://www.megaupload.com/?d=SA72G66K

http://hyperupload.com/download/0258870139/DelphiAlLimite_Arcade2_SDL.zip.html

Pruebas realizadas en Delphi 7.

7 comentarios:

Juan dijo...

Genial el artículo!

Llevo unos días intentando configurar Lazarus con SDL ya que en casa sólamente tengo Ubuntu.

Saludos!

Taxylon Software dijo...

Llevo tiempo haciendo un seguimiento de las versiones que van saliendo de Lazarus y la verdad es que va mejorando poco a poco.

Aunque todavía está muy verde en algunos aspectos, tiene un gran futuro.

Además, programar videojuegos con la librería SDL en Lazarus creo que es más fácil que con Delphi, ya que puedes enlazar directamente la librerías estáticas con extensión .a sin tener que bajarte librerías dinámicas DLL de Internet.

Quién sabe si en un futuro crearé un blog llamado Lazarus al Límite. Me gustaría aprender a programar en Linux tan bien como en Windows sin utilizar máquinas virtuales ni lenguajes dinámicos. Al estilo GCC.

Saludos.

Juan dijo...

Yo sí que estoy muy verde en todo lo que refiere a Linux. Por eso decidí en casa trabajar únicamente con Ubuntu, así podría aprender más deprisa.

Ahora por fin he conseguido hacer trabajar las SDL. Pero aún no termino de hacer funcionar correctamente el proyecto de hoy con las fuentes.

Seguiré probando. Y espero que no te importe que esté por aquí con Lazarus ;)

Saludos!

Abismo Neo dijo...

oigan komo duda siguiendo la idea, y en kylix???

digo es el delphi para linux, komo seria aki todo esto? iwal? o desde delphi komo hago para kese ejecute desde linux????

y luego para ke se ejecute en mac se podriA???? seria buena idea hacer multiplataforma, :p

Administrador dijo...

Con Kylix no se como será pero con Lazarus se que es posible porque he visto en el código fuente de la librería SDL que está preparada para otras plataformas:

{$IFDEF UNIX}
{$IFDEF FPC}
{$IFNDEF SKYOS}
pthreads,
{$ENDIF}
baseunix,
{$IFNDEF GP2X}
{$IFNDEF DARWIN}
{$IFNDEF SKYOS}
unix,
{$ELSE}
unix;
{$ENDIF}
{$ELSE}
unix;
{$ENDIF}
{$ELSE}
unix;
{$ENDIF}
{$IFNDEF GP2X}
{$IFNDEF DARWIN}
{$IFNDEF SKYOS}
x,
xlib;
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ELSE}
Libc,
Xlib;
{$ENDIF}
{$ENDIF}

Lo que ya no se si será economicamente viable para hacer juegos casuales (indie) y venderlos online.

Abismo Neo dijo...

tengo duda, para referencias las fuentes haces ruta estatica "C:\windows\fonts", komo le harias para ponerla dinamica...

ya ke por ejemplo mi lap tiene instalador el windows en D: y si lo envio a alguien kon windows en C: marka error y no se ejekuta......
trate referenciando a komo los sprites, y kopiando las fuentes, pero no funciona...... alguna otra idea?

rover suarez dijo...

amigo tu trabajo es impresionate a menos para mi q me estoy introducciendo en este mundo
me parece fantastico, un favor si no es mucha molestia podrias volver a subir el juego hecho para descargarlo gracias y sigue asi xaval....

Publicidad