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.

21 comentarios:

Alejandro dijo...

muy buen blog, espero q siga creciendo.
Sabes algo de mapeo de objetos en delphi? estuve leyendo algo y me interesa el tema.
Saludos

Taxylon Software dijo...

Hace tiempo estube investigando sobre el mapeo de objetos en Delphi utilizando la librería RTTI. Esta librería permite inspeccionar en tiempo real
un objeto instanciado y poder ver sus variables. El problema estaba en que no
me diferenciaba bien los tipos de variable (un boolean me lo daba como integer).
Creo en los últimas versiones de Delphi se ha mejorado bastante la librería
RTTI.

Yo lo que quería era crear un motor de persistencia para Interbase, al estilo Struts o Hibernate en Java, pero nunca lo conseguí por estos problemas. Delphi también tiene las clases TPersistent para este cometido, que es realmente como se graban los componentes visuales de los formularios en disco, pero no me convence mucho porque los guarda en su propio formato y a mi me gustaría guardarlo en mi propio formato XML. Hay un artículo de Ian Marteens donde habla sobre persistencia y colecciones:

http://www.marteens.com/trick08.htm

CodeGear lo ha conseguido realmente con la tecnología ECO, pero creo que sólo funciona para .NET. Eso si que es persistencia pura y dura. Ojalá se pudiera hacer lo mismo en Delphi para Win32.

Algun día me pondré a fondo con el tema...

Alejandro dijo...

Gracias por la ayuda, estube leyendo algo de ECO y me resulto muy interesante. Estoy comenzando un proyecto, cualquier cosa les muestro mas adelante como va. Saludos

Abismo Neo dijo...

oye..... tengo una duda, mira en la parte donde pones el procedure de controlareventos, me marca error al ejecutar, lo he movido de diferentes maneras entre las unidades, pero me sigue marcando error o no me detecta alguna variable. estoy con delphi 5 y voy paso a paso con el tutorial... alguna recomendacion?

Administrador dijo...

Si no me dices que error es ni en que línea de código no te puedo ayudar.

El código que he puesto aquí esta copiado directamente del editor de Delphi por lo que descarto que sea un error sintáctico.

Abismo Neo dijo...

Bueno es un error de aplicacion de esos ke dice "Exception EAccess Violation in module....", pero esto lo hace cuando declaro una variable Teclado: TTeclado, en Uarcade, ke es donde puse el procedure de Controlareventos, si lo kito de ahi, no me reconoce la variable Teclado, y si lo muevo de Uarcade a Ujuegos, no me detecta la variable Avion......ke es el sprite.

Administrador dijo...

Revisa bien el código porque los access violations siempre se deben a que intentas acceder a la propiedad o método de un objeto que no ha sido creado (.Create) o que ya ha sido destruido.

Para verificar si tienes vacía la referencia a un objeto puedes hacer esto:

if Teclado = nil then
ShowMessage( 'el teclado no existe' )

o bien esto:

if not Assigned( Teclado ) then
ShowMessage( 'el teclado no existe' )

Suerte y mucha paciencia.

Abismo Neo dijo...

claro, no vengo a preguntar luego de nomas poner el codigo, sino depsues de revisar bien, gracias y seguiremos en esta tarea :)

Abismo Neo dijo...

mira, despues de dar muchas vueltas, kreo ke sigo perdido en el procedure controlar eventos...ya que no se donde debe ir bien colocado y posiblemente la declaracion de la variable en la otra unidad me este creando este error

Si coloco el procedure en Uarcade tengo ke krear "Teclado", y si es en Ujuegos tengo ke krear "Avion"..... porke sino hago esto, me dice que no tengo declarada esa variable.

Y si pongo las variables esas en la unidad es donde si se ejecuta pero marca el error ke te mencione antes.

Abismo Neo dijo...

bueno , yo se ke diras ke soy un fastidio y ke eso ya lo habras explikado o ke sino puedo ke lo deje, pero pues sigo con la misma duda, no logro ke me funcione el controlareventos, he intentado de muchas formas pero si declaro variables me marka error y sino ke no las tengo declaradas....y logre ke uname funcionara y se kedo ciclado rebotando el avion kon la parte de arriba de la ventana....... :S

Administrador dijo...

Mándame tu proyecto por correo y así tardamos menos:

taxylon@gmail.com

Y no te aburras. Para aprender primero hay que equivocarse.

Abismo Neo dijo...

no me aburro, sigo buscando, dame unos dias mas para probar ke puedo hacer y empezar de nuevo paso a paso para ver y sino pues ya te lo envio, :)

me gusta mucho aprender kosas nuevas y mas si se trata de videojuegos :D

Abismo Neo dijo...

resuelto el problema, ya avance esa parte, y ya estoy con los disparos, entendiendo ke tanto hacer, :D

saludos y gracias por la paciencia.

Administrador dijo...

Me alegro. Dale caña.

MAXIUM dijo...

El movimiento del mouse es muy bruto, es como si cambiara el puntero por el sprite y ya.

Tampoco me queda claro esta parte:
SDL_SetColorKey(Superficie, SDL_SRCCOLORKEY, 0);

Ya que no importa la profundidad de color que use en function SDL_SetVideoMode(width, height, bpp: Integer; flags: UInt32): PSDL_Surface;

Tendría que usar sprites de 8 bit para poder usar de 0 a 255 en SDL_SetColorKey(Superficie, SDL_SRCCOLORKEY, 0<->255);

Administrador dijo...

La funcion SDL_SetColorKey le dice a la librería SQL cual va a ser el color RGB que hará de transparente (en este caso el 0 - negro).

La profundad de color que yo utilizo es 16 bits, ya que me funciona igual que en 32 bits y va más rápido.

Pero una cosa es que la pantalla sea de 16 bits y otra que nuestro sprites sean de 32 bits. Si son así entonces si yo quisiera hacer que el color transparente fuera el rosa haría esto:

SDL_SetColorKey( Superficie, SDL_SRCCOLORKEY or SDL_RLEACCEL,
SDL_MapRGBA( Pantalla.format, 255, 0, 255, 1 )

Esto lo he descubierto hace poco a base de buscar código fuente en C/C++ con ejemplos.

Lo que si tengo claro es que hay que utilizar imágenes PNG con 32 bits y con canal Alpha transparente. De este modo se evitan los bordes dentados en los sprites.

Cuando tenga mi motor 2D bien fino puede que escriba nuevos artículos con estos descubrimientos.

Saludos.

MAXIUM dijo...

Muchisimas gracias, ya tenia la duda yo con el SDL_MapRGBA( Pantalla.format, 255, 0, 255, 1 ); que no funcionaba el canal alfa, buen dato eso del PNG para ello.

Para el formato BMP se puede usar SDL_MapRGB(Pantalla.format, 255, 0, 255);

Gracias, tienes muy buenos artículos y ojalas sogas así ;)

piragna5 dijo...

hola buenas!!! estoy empezando el curso y soy nuevo en esto de la progamacion. tengo delphi 2007 y hasta ahora todo me habia salido bien pero cuando hice lo del procedimiento de contolareventos me marca el mismo error que a Abismo Neo la unidad de UArcade no me reconoce la variable Teclado tengo que crear esa variable en esa unidad y si ese procedimiento lo paso a la unidad de UJuego el error me lo marca con la variable Avion aparte me pide que en ese procedimiento cree el objeto sino me marca un error de Exception EAcces...

piragna5 dijo...

hola buenas!!! soy nuevo en esto de la programacion y apenas estoy empezando este curso (por cierto muy bueno) y tengo instalado delphi 2007 y hasta ahora todo me habia funcionado bien, pero en este procedimiento de controlar eventos me marca un error en la Unidad UArcade, el mismo error que a Abismo neo, no me detecta la variable Teclado que se declara en la unidad principal de juego 'Juego' tengo que declarar esa variable en UArcade para que me la detecte y aparte en el procedimiento de controlareventos tengo que creaar el objeto teclado=tteclado.create para que no me marque un error de exception eacces .... si alguien me pudiera ayudar con la solucion o abismo neo decirme como lo soluciono, si quieren les puedo mandar mi codigo para que lo vean!!! gracias

Eduardo Vargas dijo...

tengo el mismo eroor sobre la violacion con el controlareventos

enmanuelB dijo...

saludos buen articulo sobre SDL con Delphi lo acabo de encontrar cuando me estaba dando por vencido buscando como programar juegos con delphi.. :S

soy novato en lo de programar y me gustaria aprender mucho en especial 'juegos'

Publicidad