16 mayo 2008

Programar videojuegos con la librería SDL (4)

Hoy vamos a ver como mover el sprite del avión por pantalla utilizando el teclado. Lo primero que vamos a hacer es centrar el avión en pantalla modificando el procedimiento CargarSprites:

procedure CargarSprites;
begin
Avion := TSprite.Create;
Avion.x := 273;
Avion.y := 204;
Avion.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) + 'avion.bmp' );
end;

Ahora vamos a crear un procedimiento llamado ControlarEventos que va a encargarse de mover el avión según las pulsaciones del teclado:

procedure ControlarEventos;
begin
if Teclado.bDerecha and ( Avion.x < 545 ) then
Inc( Avion.x );

if Teclado.bIzquierda and ( Avion.x > 0 ) then
Dec( Avion.x );

if Teclado.bArriba and ( Avion.y > 0 ) then
Dec( Avion.y );

if Teclado.bAbajo and ( Avion.y < 407 ) then
Inc( Avion.y );
end;

Después tenemos que llamar a este procedimiento desde el bucle principal del juego dentro de la unidad juego.dpr:

Al ejecutar el juego nos va a surgir uno de los grandes problemas en los juegos 2D:

Al moverse el sprite por pantalla va dejando rastro como los caracoles. Para solucionar este problema necesitamos un fondo que se dibuje antes que el sprite. Por ejemplo un fondo de dimensiones 640 x 480:

Para esta textura vamos a crear un sprite llamado Fondo a nivel global dentro de la unidad UArcade:

var
Avion, Fondo: TSprite;

Y ahora volvemos a modificar el procedimiento CargarSprites para cargar el fondo:

procedure CargarSprites;
begin
Avion := TSprite.Create;
Avion.x := 273;
Avion.y := 204;
Avion.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) + 'avion.bmp' );

Fondo := TSprite.Create;
Fondo.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) + 'fondo.bmp' );
end;

Se supone que el archivo fondo.bmp es un bitmap de 640 x 480 que está al lado de nuestro ejecutable. Otra cosa importante es liberar el fondo cuando termina el juego. Para ello ampliamos el procedimiento DestruirSprites:

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

Y por último, el procedimiento DibujarSprites debe dibujar el fondo antes que el avión:

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

Al ejecutar el juego este sería el resultado:


LA TÉCNICA DEL DOBLE BUFFER

Con esto quedan solucionadas las manchas a la hora de dibujar el avión. Primero dibuja el fondo y después el avión. Lo normal es estos casos es que se produjera un parpadeo (ya que se mezclarían el avión con el fondo), pero no es así.

La librería SDL utiliza una técnica de doble buffer donde utiliza dos pantallas realizando los pasos siguientes:

Primero dibuja el fondo y el avión en la pantalla 1.

Al ejecutar el comando SDL_Flip( Pantalla ) intercambia la pantalla 1 por la 2.

Después dibuja el fondo y el avión en la pantalla 2.

Intercambia la pantalla 2 por la 1.

Vuelve al paso .

De este modo se provoca el engaño de parecer una animación cuando realmente la tarjeta de vídeo va intercambiando ambas pantallas 25 veces por segundo sin que el usuario se percate de lo ocurrido:


ACELERANDO EL MOVIMIENTO DEL AVIÓN

Inicialmente podemos ver que el avión no se mueve muy rápido que digamos (así no esquivamos ni una bala). Para acelerar su movimiento tenemos que modificar los incrementos en el procedimiento ControlarEventos:

procedure ControlarEventos;
begin
if Teclado.bDerecha and ( Avion.x < 545 ) then
Inc( Avion.x, 5 );

if Teclado.bIzquierda and ( Avion.x > 0 ) then
Dec( Avion.x, 5 );

if Teclado.bArriba and ( Avion.y > 0 ) then
Dec( Avion.y, 5 );

if Teclado.bAbajo and ( Avion.y < 407 ) then
Inc( Avion.y, 5 );
end;

Al mover el avión de 5 en 5 pixels ya se le puede considerar a este juego un arcade.

MOVER EL SPRITE CON EL RATÓN

Últimamente se está haciendo común el mover los sprites de este tipo de juegos con el ratón. Para no perder la costumbre vamos a hacer también una clase en la unidad UJuego:

TRaton = class
x, y, ux, uy: Integer; // coordenadas del ratón y última posición

constructor Create;
procedure Leer;
end;


En las variables x e y guardo las coordenadas actuales del ratón. Las variables ux, uy las voy a utilizar para detectar si el usuario ha movido el ratón. Voy a utilizar el constructor de la clase TRaton para ocultar la flecha del cursor que por defecto me muestra la librería SDL:

constructor TRaton.Create;
begin
SDL_ShowCursor( 0 );
end;

La función SDL_ShowCursor oculta o muestra el cursor del ratón dependiendo de si le pasamos un 0 o un 1. Y para leer el estado del ratón aquí tenemos el método:

procedure TRaton.Leer;
begin
SDL_GetMouseState( x, y );
end;

La función SDL_GetMouseState toma como parámetro dos números enteros que hay que pasar como variables y almacena ahí la posición del ratón. Ahora tenemos que crear una variable global dentro de la misma unidad para almacenar el objeto Raton:

var
bSalir: Boolean;
Pantalla: PSDL_Surface;
Teclado: TTeclado;
Raton: TRaton;
Temporizador: TTemporizador;

Y en el bucle principal del juego inicializamos el ratón, lo leemos y posteriormente lo eliminamos:

begin
InicializarSDL;
ModoVideo( 640, 480, 16, True );
Teclado := TTeclado.Create;
Raton := TRaton.Create;
Temporizador := TTemporizador.Create;
CargarSprites;

while not bSalir do
begin
Temporizador.Actualizar;

if Temporizador.Activado then
begin
Teclado.Leer;
Raton.Leer;
ControlarEventos;
DibujarSprites;
ActualizarPantalla;
Temporizador.Incrementar;
end
else
Temporizador.Esperar;
end;

DestruirSprites;
Temporizador.Free;
Teclado.Free;
Raton.Free;
FinalizarSDL;
end.

Para terminar sólo hay que modificar el procedimiento ControlarEventos para leer el teclado, o el ratón indistintamente:

procedure ControlarEventos;
begin
if Teclado.bDerecha and ( Avion.x < 545 ) then
Inc( Avion.x, 5 );

if Teclado.bIzquierda and ( Avion.x > 0 ) then
Dec( Avion.x, 5 );

if Teclado.bArriba and ( Avion.y > 0 ) then
Dec( Avion.y, 5 );

if Teclado.bAbajo and ( Avion.y < 407 ) then
Inc( Avion.y, 5 );

// Movemos el avión por el ratón sólo si lo ha movido el usuario
if ( Raton.ux <> Raton.x ) or ( Raton.uy <> Raton.y ) then
begin
Avion.x := Raton.x;
Avion.y := Raton.y;

// Si el avión se sale de pantalla lo corregimos
if Avion.x > 545 then
Avion.x := 545;

if Avion.y > 407 then
Avion.y := 407;

// Guardamos las últimas coordenadas del ratón
Raton.ux := Raton.x;
Raton.uy := Raton.y;
end;
end;

Con esto ya tenemos el control absoluto de los dispositivos de entrada. En el próximo artículo veremos como hacer que dispare el avión controlando la multitarea.

Pruebas realizadas en Delphi 7.

Publicidad