18 julio 2008

Pasando de Delphi 7 a RAD Studio 2007 (2)

Sigamos viendo las novedades de RAD Studio respecto a Delphi 7.

EL EDITOR DE CODIGO FUENTE

Esta es una comparativa entre el editor de código de Delphi 7 y RAD Studio 2007:


La primera característica que puede apreciarse es en la barra lateral situada a la izquierda del código fuente. Mientras que Delphi 7 sólo mostraba la posición de la línea y columna actual en la parte inferior del editor de código, en RAD Studio podemos ver siempre el número de línea donde estamos situados. Además, también muestra el número de línea cada 10 líneas.

Otra novedad que destaca se encuentra en la línea lateral de color verde. Esta línea nos informa en todo momento del código fuente que se ha modificado y si se ha guardado o no.

Por ejemplo, si yo creo una variable privada, me mostrará en amarillo las líneas afectadas por la modificación:


Cuando pulse el botón de guardar volverán a estar todas las líneas en color verde, preparado para más modificaciones:


También otra novedad importante que da más claridad al código fuente es la posibilidad en abrir y cerrar bloque de código. Por ejemplo este bloque código fuente está abierto:


Si pulsamos el botón [-] que se indica en la foto superior contraerá toda la definición de la clase TForm1 dejando menos líneas de código en pantalla:


El botón [-] ha sido sustituido por el botón [+] para poder desplegar el código de nuevo. El funcionamiento es similar a otros entornos de desarrollo como Visual Studio, NetBeans, Eclipse, etc.

Así mismo, podemos contraer o estirar todos los bloques de código de la unidad en la que estamos situados pulsando con el botón derecho del ratón sobre el código fuente y seleccionando la opción Fold:


Podemos contraer métodos, tipos de datos, etc.

Lo mismo para volver a desplegarlo todo:


La barra de estado inferior del editor de código también tiene algunas novedades:


En la parte izquierda de la barra incorpora tres botones para la grabación y la reproducción de macros. Una macro puede ser desde una acción con los menús de Delphi o incluso escribir un código fuente que luego tenemos que repetir. Es decir, graba todas las acciones de teclado o ratón que se realicen en Delphi para su posterior reproducción.

Después tiene las mismas coordenadas de fila y columna que tenía Delphi 7 y el estado al escribir: Insertar o Modificar. Por último, finaliza con tres pestañas:

Code: Muestra el editor de código.

Design: Muestra el formulario.

History: Muestra el histórico de versiones del código fuente de la unidad actual.

EL HISTORIAL DE VERSIONES

La pestaña History es otra de las novedades que incorporan las versiones Delphi para Win32 y superiores:


Para cada unidad de código, RAD Studio realiza automáticamente un control de versiones para poder recuperar el código fuente de modificaciones anteriores.

Cuando hemos pulsado las pestaña History ha dividido el editor de código fuente en dos partes. La parte superior muestra las distintas versiones que almacena de la unidad donde estamos:


La última versión es File y el resto va en orden descendente. En entorno de RAD Studio viene configurado por defecto para guardar hasta 10 versiones de cada unidad. Aunque podemos modificar esto seleccionando Tools -> Options y en la sección Editor Options tenemos el campo File backup limit:


Vamos a verlo con un ejemplo. Supongamos que añado la variable sDireccion a mi clase TForm1:


Si pulsamos la pestaña History podemos ver la última versión pulsando sobre la primera fila del histórico (File):


Pero si seleccionamos la versión 3 (la segunda fila) me mostrará como estaba el código fuente antes de realizar el último cambio:


Como sabemos por la configuración que guarda hasta los 10 últimos cambios, podemos modificar el programa a nuestro antojo sin miedo a rectificar el código actual. Todos estos cambios los va guardando realmente en una carpeta llamada __history que esta situada en el mismo directorio de nuestro proyecto.

Si queremos recuperar una versión anterior sólo hay que seleccionarla con el botón derecho del ratón y seleccionar Revert:


Nos pedirá confirmación por si acaso:


Una vez pulsemos el botón Yes dejará el código fuente exactamente como estaba en esa versión.

REFACTORIZANDO BLOQUES DE CÓDIGO

Esta es otra de las mejores características que incorpora respecto a Delphi 7. Como todos sabéis, refactorizar el código fuente significa crear funciones o procedimientos comunes que simplifiquen y reduzcan el número de líneas de código. Vamos a verlo con un ejemplo.

Supongamos que tengo un procedimiento con dos bucles que hacen casi lo mismo:

procedure TForm1.Facturar;
var
i: Integer;
begin
for i := 1 to iNumeroPedidos do
begin
MarcarDocumento( i );
CrearFactura;
ActualizarMovimientos;
end;

for i := 1 to iNumeroAlbaranes do
begin
MarcarDocumento( i );
CrearFactura;
ActualizarMovimientos;
end;
end;

Para simplificar este procedimiento habría que hacer otro procedimiento común llamado FacturarDocumento que ejecutará cada una de las tres líneas de cada bucle. Hasta ahora lo hemos hecho a mano, pero vamos a ver como se simplifica el proceso con RAD Studio.

Ahora podemos refactorizarlo de la siguiente manera:

1. Seleccionamos las tres líneas del primer bucle:


2. Pinchamos con el botón derecho del ratón sobre las líneas seleccionadas y elegimos Refactoring -> Extract Method:


Aparecerá esta ventana:


2. En el campo New Method Name escribimos FacturarDocumento y pulsamos el botón Ok. El código fuente quedará de este modo:

procedure TForm1.FacturarDocumento(i: Integer);
begin
MarcarDocumento(i);
CrearFactura;
ActualizarMovimientos;
end;

procedure TForm1.Facturar;
var i: Integer;
begin
for i := 1 to iNumeroPedidos do
begin
FacturarDocumento(i);
end;

for i := 1 to iNumeroAlbaranes do
begin
MarcarDocumento( i );
CrearFactura;
ActualizarMovimientos;
end;
end;

Lo que ha hecho es crear el nuevo método llamado FacturarDocumento que se ha llevado las tres líneas del bucle y las ha reemplazado por una llamada a este nuevo método. Ahora sólo queda quitar los begin y end del primer bucle y hacer lo mismo con el segundo:

procedure TForm1.FacturarDocumento(i: Integer);
begin
MarcarDocumento(i);
CrearFactura;
ActualizarMovimientos;
end;

procedure TForm1.Facturar;
var
i: Integer;
begin
for i := 1 to iNumeroPedidos do
FacturarDocumento(i);

for i := 1 to iNumeroAlbaranes do
FacturarDocumento(i);
end;

Como puede apreciarse, la refactorización ha sido un éxito y nos ha llevado poco tiempo. Quizás en programas tan pequeños como este no se note demasiado, pero en unidades de miles de líneas os puedo asegurar que es una bendición.

También se puede hacer más rápido del siguiente modo: seleccionas el bloque de código que quieres sacar a parte y luego pulsas la combinación de teclas Mayúsculas + CTRL + M. Después pulsas Intro y ya esta.

DECLARANDO VARIABLES AUTOMÁTICAMENTE

Otra de las características que incorporan las opciones dedicadas a la refactorización es la posibilidad de declarar automáticamente en la sección var las variables que utilizamos en un bloque de código.

Por ejemplo, supongamos que creo el siguiente bucle:

procedure TForm1.CrearFactura;
begin
for i := 0 to iLineasDetalle do
ActualizarMovimientosLinea( i );
end;

Vamos hacer que la variable i la declare RAD Studio automáticamente. Hay que seguir estos pasos:

1. Seleccionamos la varible i.

2. La pinchamos con el botón derecho del ratón y seleccionamos Refactoring -> Declare Variable:


Aparecerá esta ventana:


Como puede apreciarse, ya ha reconocido que la variable a declarar es de tipo Integer.

3. Pulsamos el botón Ok y este es el resultado:

procedure TForm1.CrearFactura;
var
i: Integer;
begin
for i := 0 to iLineasDetalle do
ActualizarMovimientosLinea( i );
end;

En procedimientos pequeños como este no suele apreciase mucho esta opción, pero en bloques de código de 50 o 60 líneas si que es un autentico coñazo tener que subir al principio del procedimiento a declarar la variable para luego volver a bajar y seguir por donde íbamos.

Este proceso todavía se puede hacer mucho más rápido si seleccionamos la variable i y luego pulsamos la combinación de teclas Mayúsculas + CTRL + V y luego pulsamos Intro. Cuando te aprendes esta combinación es una auténtica gozada y se gana mucha velocidad.

RENOMBRANDO LAS VARIABLES DE UN BLOQUE DE CÓDIGO

Las opciones de refactorización también permiten renombrar las variables de un bloque de código, ya sea de un procedimiento, función, clase o de toda la unidad.

Muchos os preguntaréis, ¿para que quiero esta opción si ya utilizo CTRL + R para reemplazar trozos de código? Pues con variables que tienen un nombre único no hay problema, pero ¿Qué pasa si necesitas renombrar la variable i de un bucle sin estropear la letra i de todo el resto del código?

Por ejemplo en este bloque de código:

procedure TForm1.Facturar;
var
i: Integer;
begin
for i := 1 to iNumeroPedidos do
FacturarDocumento(i);

for i := 1 to iNumeroAlbaranes do
FacturarDocumento(i);
end;

Si yo intento cambiar el nombre de la variable i por j utilizando CTRL + F me haría este desastre:

procedure TForm1.Facturar;
var
j: jnteger;
begjn
for j := 1 to jNumeroPedjdos do
FacturarDocumento(j);

for j := 1 to jNumeroAlbaranes do
FacturarDocumento(j);
end;

No sólo he ha reemplazado la variable i sino que me ha estropeado dos variables más y hasta el comando begin.

Para hacer esto correctamente hay que seguir estos pasos:

1. Seleccionamos cualquier variable i (ya sea la que está dentro de la sección var o la del bucle).

2. Pulsamos esta variable con el botón derecho del ratón y seleccionamos Refactoring -> Rename variable “i”:


Mostrará este cuadro de diálogo:


3. En el campo New Name podemos j y pulsamos Ok.

4. Como RAD Studio es muy precavido, nos mostrará en la parte inferior de la ventana de código como quedaría el bloque de código después de la operación:


Ahora sólo tenemos que pulsar el botón Refactor y luego cerramos la ventana de refactoring:


Lo bueno que tiene esto es que sólo nos reemplaza la variable dentro de ámbito donde estamos. Si estamos dentro de un procedimiento, sólo nos modifica la variable del mismo y no del resto de la unidad. También detecta cuando estamos renombrando la variable de una clase en concreto o de toda la unidad.

CAMBIANDO LOS PARÁMETROS DE FUNCIONES Y PROCEDIMIENTOS

¿Qué pasa cuando tenemos que añadir, modificar o eliminar un nuevo parámetro a una función o procedimiento de una clase? Hasta ahora teníamos que hacer lo siguiente:

1. Nos vamos a la definición de la clase (dentro de type) y añadimos el nuevo parámetro.

2. Nos vamos a la implementación del procedimiento y también añadimos el nuevo parámetro.

3. Buscamos por todas las unidades del proyecto a ver quien llama a este procedimiento y añadimos el nuevo parámetro (esto lo hacemos compilando y según se vaya quejando el compilador vamos reemplazando código).

Pues ahora sólo tenemos que hacerlo en un solo paso. Por ejemplo tenemos este procedimiento:

procedure TForm1.FacturarDocumento( i: Integer );
begin
MarcarDocumento(i);
CrearFactura;
ActualizarMovimientos;
end;

el cual es llamado por este otro:

procedure TForm1.Facturar;
var
j: integer;
begin
for j := 1 to iNumeroPedidos do
FacturarDocumento(j);

for j := 1 to iNumeroAlbaranes do
FacturarDocumento(j);
end;

Pues bien, supongamos que tengo que añadir otro parámetro:

procedure TForm1.FacturarDocumento( i: Integer; bEstado: Boolean );

Hay que hacerlo del siguiente modo:

1. Selecciono el nombre del procedimiento (FacturarDocumento) con el botón derecho del ratón y selecciono Refactoring -> Change params:


Aparecerá esta ventana:


2. Pulsamos el botón Add y aparecerá este cuadro de diálogo:


3. En el campo Parameter Name escribimos bEstado y en Data type el valor Boolean. Después pulsamos Ok.

3. Volverá a la ventana anterior con el nuevo parámetro:


4. Pulsamos Ok y aparecerá la sección de refactoring informándonos de los las líneas de código afectadas por el cambio:


5. Pulsamos el botón Refactor y trabajo terminado tanto en la declaración de la clase:

private
{ Private declarations }
procedure FacturarDocumento(i: Integer; bEstado: Boolean);
...

Como en la implementación:

procedure TForm1.FacturarDocumento( i: Integer; bEstado: Boolean );
begin
MarcarDocumento(i);
CrearFactura;
ActualizarMovimientos;
end;

Además nos deja preparadas las llamadas este procedimiento para añadir el nuevo parámetro:

procedure TForm1.Facturar;
var
j: integer;
begin
for j := 1 to iNumeroPedidos do
FacturarDocumento(, )(j);

for j := 1 to iNumeroAlbaranes do
FacturarDocumento(, )(j);
end;

Aunque hay que reconocer que esto último es algo rarillo. Pero bueno, al fin y al cabo tenemos que modificar las llamadas y darles el nuevo parámetro. El IDE ha realizado un trabajo limpio e impecable que nos ahorra muchas compilaciones y saltos en el código de allá para acá.

En el próximo artículo seguiremos viendo más novedades.

Pruebas realizadas con RAD Studio 2007.

11 julio 2008

Pasando de Delphi 7 a RAD Studio 2007 (1)


Llegó la hora de sacar la artillería pesada. Si bien las versiones de Delphi 5, 6 y 7 seguirán presentes entre nosotros durante mucho tiempo, hay que reconocer que las nuevas herramientas de CodeGear incorporan grandes características que acelerarán nuestro proceso de desarrollo. Y más ahora que con Embarcadero Technologies comienza una nueva etapa.

Desde que apareció Delphi 7 en el año 2002 han aparecido muchas versiones que si bien eran mejores que las anteriores, hay que reconocer que las primeras mezclas que hicieron con la plataforma .NET fueron bastante lentas e inestables. Sumado además la ausencia notable de su ayuda en línea y ejemplos de código, hizo que una gran avalancha de programadores se fugara a la plataforma .NET con Visual Studio o a Java con sus entornos de desarrollo libres y gratuitos (NetBeans o Eclipse).

También lo intentaron con las versiones Turbo, unas versiones ligeras de RAD Studio pero sin la posibilidad de instalar componentes de terceros (es como si te regalan un Ferrari con unas ruedas cuadradas que no puedes cambiar). Debido a esta limitación y la falta de marketing similar a la que hace Microsoft con Visual Studio en sus versiones Express, han pasado casi inadvertidas para los programadores de Delphi.

Si además añadimos la aparición y mejora de nuevos lenguajes dinámicos y libres como Python, Ruby (con su famoso framework Ruby on Rails) y el veterano PHP, esto provocó la caída de Borland cuesta abajo y sin frenos. No ha sido hasta que sacaron la versión Delphi para Win32 cuando los usuarios han vuelto la cabeza y han visto una herramienta seria para programar en Windows y sin tener que utilizar las pesadas máquinas virtuales.

Vamos a ver todas las novedades que diferencian ambas versiones y también veremos sus defectos. Lo mostraré desde el punto de vista de aquellos usuarios que quieren pasar de los Delphi 5, 6 y 7 (donde todavía abundan muchos proyectos hoy en día) a Delphi para Win32 o RAD Studio 2007.

ARRANCANDO RADSTUDIO

RAD Studio puede ejecutarse de tres maneras diferentes:


Delphi 2007: para crear aplicaciones Win32 o Intranet.

Delphi .NET: para programar específicamente para la plataforma .NET.

CodeGear RAD Studio: Permite crear cualquier tipo de aplicación (Win32 o .NET). La carga en este modo es la más lenta.

Nosotros vamos a ejecutar la versión Delphi 2007 para programar específicamente en Win32:


El primer cambio que se aprecia es la lentitud al cargar el IDE. A diferencia de Delphi 7 y versiones anteriores que estaban desarrollados puramente en Win32, RAD Studio se ha reescrito desde cero en la plataforma .NET, independientemente de si vamos a programar en .NET o no.

Esto provoca una lentitud en la carga desesperante. Hay que reconocer que el entorno es mucho más grande y más complejo de los antiguos Delphi, pero al tener que cargar la plataforma .NET y luego todas las librerías DLL asociadas al Delphi, te puedes tomar un café y fregar los platos (a menos que tengas un buen PC).

Lo bueno que tiene es que si cierras el IDE y vuelves a ejecutarlo la carga es mucho más rápida que la primera vez. Un cosa que me gusta es que cuando cierras el IDE se hace casi instantáneamente, no como los antiguos Delphi en los que tenias que esperar unos segundos hasta que se liberase la memoria.

Otra cosa buena que tiene es que si nos hemos equivocado programando y no hemos liberado objetos de memoria, esto no propaga una cadena de errores continuos al intentar cerrar RAD Studio 2007, al contrario de lo que sucedía en las versiones de Delphi 7 e inferiores.

LA NUEVA INTERFAZ DE TRABAJO

Este es el aspecto que tiene el IDE al arrancar:


Para crear un nuevo proyecto hay que seleccionar en el menú superior File -> New -> VCL Forms Application:


El entorno se transformará en este otro aspecto:


Esta forma de trabajar es similar a la de otros entornos de trabajo similares como Visual Studio.

Las pantalla se divide en seis partes principales:

BARRA DE HERRAMIENTAS SUPERIOR


Al igual que en los antiguos Delphi utilizaremos esta barra para realizar las acciones más importantes: abrir proyectos, compilar, modificar las opciones del proyecto, etc. La diferencia más notable respecto a las versiones de Delphi 7 e inferiores es la desaparición de la barra de componentes VCL.

ESTRUCTURE


Situada en la esquina superior izquierda de la ventana, esta sección muestra la estructura de componentes visuales del formulario que estamos editando o la estructura de las unidades si estamos editando el código fuente del formulario:


OBJECT INSPECTOR


Este inspector de objetos muestra las propiedades y eventos de los objetos visuales que hemos seleccionado. Una diferencia que puede apreciarse en este nuevo inspector de objetos es que las propiedades aparecen ordenadas por categoría, en vez de estar por orden alfabético.

Para ordenarlo por nombre alfabético tenemos que pulsar el inspector de objetos con el botón derecho del ratón y seleccionar Arrange -> By Name:


Entonces se quedará como siempre:


Nota: El inspector de objetos a veces tiene el fallo de que si estas editando un formulario y quieres entrar al inspector de objetos por ejemplo a la propiedad Name, el cursor del ratón no se sitúa ahí. Se queda como tonto. Tienes que pinchar otra propiedad y luego otra vez en Name para que haga caso. No es una cosa que moleste mucho, pero a veces cuando tienes prisa mosquea bastante. Lo he visto en todas las versiones de Delphi para Win32 y las de RAD Studio. No se si es un fallo o es que es así de torpe este control.

ZONA CENTRAL DE TRABAJO


Situado en el centro de la pantalla, contiene los formularios o el código fuente (podemos alternar con F12). Aquí encontramos un inconveniente respecto a los antiguos Delphi. A menos que tengamos un buen monitor 16:9 con buena resolución, la zona central de la pantalla se nos queda algo escasa, sobre todo al editar grandes formularios, donde tendríamos que utilizar la barra de desplazamiento inferior. Luego veremos como ganar más espacio en esta zona de trabajo.

PROJECT MANAGER


El administrador de proyectos contiene toda la estructura jerárquica de nuestro proyecto mostrando las unidades PAS debajo de nuestro ejecutable. Esta ventana es multiuso, ya que en su parte inferior tenemos tres pestañas:

Project Manager: es la ventana del proyecto que hemos mencionado.

Model View: se utiliza para crear nuestros organigramas visuales para crear nuestros mapas del proyecto.

Data Explorer: mediante el explorador de datos podemos conectar con distintas bases de datos y acceder a sus tablas. Con esta herramienta podemos visualizar las tablas de nuestras bases de datos sin tener que acceder a herramientas externas como IBConsole o IBExpert:


TOOL PALETTE


Esta es la gran diferencia de RAD Studio respecto a los Delphi 7 e inferiores. La pestaña de componentes VCL aparece ahora situada en la esquina inferior derecha de la ventana y aparece agrupada por temática. Si pulsamos el botón + de cada apartado veremos los componentes que contiene:


Pulsando con el botón derecho del ratón sobre la paleta de componentes podemos abrir todas las categorías a la vez con Expand All o cerrarlas todas con Colapse All:


Aunque pueda parecer incómoda la manera de buscar componentes aquí gozamos de la ventaja de poder buscar componentes por su nombre. Por ejemplo, si quisiera buscar el componente DBGrid para mostrar datos seguiría estos pasos:

1. Pincho el título Tool Pallete:


2. Escribimos grid y veremos como se van filtrando los componentes automáticamente:

3. Ahora sólo tenemos que seleccionar el componente DBGrid y pinchar en el formulario.

Una vez nos acostumbramos a buscar componentes de este modo se acelera mucho respecto a Delphi 7, sobre todo cuando tenemos muchas paletas de componentes.

COMO GANAR ESPACIO EN LA ZONA CENTRAL DE TRABAJO

Para poder ampliar nuestro espacio ya sea si estamos editando formulario o escribiendo código fuente, podemos esconder las secciones Proyect Manager y Tool Palette pulsando estos botones:


Así se trabaja más cómodo:


Estas dos secciones realmente no han desaparecido. Las seguimos teniendo a la derecha. Si queremos insertar un componente, sólo hay que situar el puntero del ratón sobre la pestaña lateral Tool Palette y aparecerá como una persiana:


Igualmente se pueden ocultar a la izquierda las secciones Structure y el Object Inspector, aunque no lo recomiendo ya que es una cosa que se suele utilizar mucho.

LOS FORMULARIOS DE OPCIONES

Otra de las ventajas que han mejorado en los nuevos Delphi 2007 es que han concentrado todas las opciones de compilación, depuración, etc. dentro de una sola ventana. Esta aparece seleccionando Tools Options:


Podemos modificar las opciones del entorno, del editor de código fuente y las opciones de depuración.

Por otro lado tenemos las opciones del proyecto actual si seleccionamos Project -> Options:


Se han sustituido las pestañas por un explorador de temas a la izquierda que facilita llegar antes a los apartados más específicos.

EL MODO CLÁSICO DE DELPHI

Para aquellos a los que no les guste tener todas las ventanas de Delphi agrupadas dentro de la ventana principal, pueden pasar a un modo de ventanas sueltas como Delphi 7 seleccionando la opción Classic Undocked en este combobox situado en la barra de herramientas superior:


El IDE quedará igual pero con cada sección en una ventana independiente:


Aunque una vez que te acostumbras al modo que trae por defecto no le das importancia.

En el próximo artículo veremos más características de RAD Studio 2007.

Pruebas realizadas en RAD Studio 2007.

04 julio 2008

Programar videojuegos con la librería SDL (y 11)

Hoy voy a terminar la sección dedicada a la programación con SDL en Delphi hablando de la creación de enemigos en un juego de plataformas.

CREANDO LOS ENEMIGOS

Para crear los sprites enemigos voy a utilizar este gráfico:


Como en el juego voy a insertar 6 enemigos, no es necesario cargar 6 sprites con los mismos gráficos. Al igual que hicimos en el juego de aviones, con un solo sprite enemigo podemos dibujar el resto.

Para ello voy a crear una nueva estructura de datos para guardar la posición de cada enemigo y su estado:

TEnemigo = record
x, y, iSubX, iSubY: Integer;
iPosicion: Integer;
bActivo, bIzquierda: Boolean;
end;

Este registro va a guardar las coordenadas de un enemigo, los subsprites que va a dibujar en ese momento, la posición al andar, si está activo y si anda hacia la izquierda o hacia la derecha.

Al igual que hicimos con los aviones vamos a añadir un array global en la unidad UPlataforma.pas para controlar a varios enemigos:

var
Fondo, Temporal, Piezas, Heroe, Enemigo: TSprite;
Enemigos: array[1..5] of TEnemigo;
...

También vamos a ampliar nuestro procedimiento cargar sprites para cargar los enemigos:

procedure CargarSprites;
begin
...
Enemigo := TSprite.Create;
Enemigo.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) + 'enemigo.png' );
Enemigo.bSubsprites := True;
Enemigo.iAnchoSub := 40;
Enemigo.iAltoSub := 40;

CargarPantalla( 'pantalla1.txt' );
DibujarPiezasEnTemporal;
end;

Y por supuesto no nos olvidamos de liberarlo de memoria:

procedure DestruirSprites;
begin
Enemigo.Free;
Heroe.Free;
Piezas.Free;
Temporal.Free;
Fondo.Free;
end;

SITUANDO LOS ENEMIGOS EN PANTALLA

Una vez ya tenemos nuestro sprite enemigo creado vamos a crear un procedimiento encargado de colocar los enemigos en pantalla:

procedure SituarEnemigos;
var
i: Integer;
begin
Enemigos[1].x := 40;
Enemigos[1].y := 320;
Enemigos[2].x := 400;
Enemigos[2].y := 320;
Enemigos[3].x := 440;
Enemigos[3].y := 200;
Enemigos[3].bIzquierda := True;
Enemigos[4].x := 160;
Enemigos[4].y := 200;
Enemigos[4].bIzquierda := True;
Enemigos[5].x := 200;
Enemigos[5].y := 80;
Enemigos[6].x := 440;
Enemigos[6].y := 80;

for i := 1 to 6 do
Enemigos[i].bActivo := True;
end;

A este procedimiento lo podemos llamar al final del procedimiento CargarSprites:

procedure CargarSprites;
begin
...
CargarPantalla( 'pantalla1.txt' );
DibujarPiezasEnTemporal;
SituarEnemigos;
end;

Lo más difícil es lo que viene ahora. Tenemos que mover todos los enemigos a la vez:

procedure MoverEnemigos;
var
i, xMapa, yMapa: Integer;
begin
for i := 1 to 6 do
begin
yMapa := ( Enemigos[i].y + 40 ) div 40 + 1;

if Enemigos[i].bActivo then
begin
if Enemigos[i].bIzquierda then
begin
Enemigos[i].iSubY := 1;
Dec( Enemigos[i].x );
xMapa := ( Enemigos[i].x - 1 ) div 40 + 1;
if ( Mapa[xMapa,yMapa] = '0' ) or ( Mapa[xMapa,yMapa] = '1' ) then
Enemigos[i].bIzquierda := False;
end
else
begin
Enemigos[i].iSubY := 0;
Inc( Enemigos[i].x );
xMapa := ( Enemigos[i].x + 40 ) div 40 + 1;
if ( Mapa[xMapa,yMapa] = '0' ) or ( Mapa[xMapa,yMapa] = '1' ) then
Enemigos[i].bIzquierda := True;
end;

// cambiamos de fotograma
Inc( Enemigos[i].iPosicion );

if Enemigos[i].iPosicion > 4 then
begin
if Enemigos[i].iSubX = 0 then
Enemigos[i].iSubX := 1
else
Enemigos[i].iSubX := 0;

Enemigos[i].iPosicion := 0;
end;
end;
end;
end;

Este procedimiento recorre todos los enemigos y los mueve a la derecha o hacia la izquierda hasta que choquen con un obstáculo. Cuando choca cambia de dirección hasta que vuelva a chocar. También nos encargamos de cambiar de fotograma para que se produzca la animación al andar.

Ahora sólo tenemos que modificar el procedimiento DibujarSprites para que dibuje a los enemigos y los mueva:

procedure DibujarSprites;
var
i: Integer;
begin
Temporal.Dibujar;
Heroe.Dibujar;

for i := 1 to 6 do
if Enemigos[i].bActivo then
begin
Enemigo.x := Enemigos[i].x;
Enemigo.y := Enemigos[i].y;
Enemigo.iSubX := Enemigos[i].iSubX;
Enemigo.iSubY := Enemigos[i].iSubY;
Enemigo.Dibujar;
end;

MoverEnemigos;
end;

Este sería el resultado:


COMPROBANDO LAS COLISIONES ENTRE SPRITES

Por último vamos a hacer que si el sprite del héroe choca con algún enemigo tenga que volver a empezar desde el principio (incluyendo volver a recoger las joyas). Eso lo vamos a hacer con el siguiente procedimiento:

procedure ComprobarEnemigos;
var
i: Integer;
begin
for i := 1 to 6 do
begin
if Enemigos[i].bActivo then
if ( Heroe.x + 20 >= Enemigos[i].x ) and ( Heroe.x + 20 <= Enemigos[i].x + 39 ) and
( Heroe.y + 20 >= Enemigos[i].y ) and ( Heroe.y + 20 <= Enemigos[i].y + 39 ) then
begin
// volvemos a colocar el equipo y los enemigos
Heroe.rx := 40;
Heroe.ry := 400;
SituarEnemigos;
CargarPantalla( 'pantalla1.txt' );

// volvemos a redibujar la pantalla de fondo
Fondo.DibujarEn( Temporal.Superficie );
DibujarPiezasEnTemporal;
end;
end;
end;

Para comprobar si el héroe choca con el enemigo lo que hago es comprobar si el centro de las coordenadas del héroe (heroe.x + 20, heroe.y + 20 ) están dentro de la cuadrícula de 40 x 40 que forma parte el enemigo. Cuando chocan entonces vuelvo a colocar al héroe, a los enemigos y las joyas, volviendo a dibujar la pantalla de fondo.

Este procedimiento hay que llamarlo desde DibujarSprites:

procedure DibujarSprites;
begin
...
MoverEnemigos;
ComprobarEnemigos;
end;


Otras cosas que se le podían haber añadido al juego es el control de vidas de modo que cada vez que nos maten decremente una vida. También haría falta meterle sonidos y una música de fondo, pero la falta de tiempo me impide cerrar el círculo.

Aquí os dejo el proyecto en RapidShare:

https://mega.nz/file/5FhU2SyD#ZavwnR9Amwcpub4wGaTI5zLsqEOPmUxuy201rtIUaOA

EL FIN DE UNA SERIE

Con esto doy más o menos por finalizado el curso de introducción a la programación de videojuegos con la librería SDL. Podía haber hecho muchos más ejemplos (juegos de estrategia, juegos de carreras, juegos de inteligencia tipo tetris, etc.), pero la falta de tiempo me impide llevarlo a cabo. Quizás en un futuro si encuentro un buen patrocinador que haga que no vuelva a trabajar podría abarcar todos estos temas (que bueno es soñar, jejeje).

Otro tema que también dejo pendiente para un futuro es la programación de videojuegos 3D con OpenGL, donde la librería SDL también da un buen soporte, pero todavía no lo tengo muy preparado y me gustaría presentarlo bien, ya que es un tema algo complicado pero apasionante.

Aprovechando también la librería OpenGL veremos también como realizar juegos 2D utilizando polígonos, lo que hace que los juegos sean espectaculares con sprites supergigantes y con los efectos que brindan las tarjetas 3D (iluminación, realzado de texturas, rotaciones, etc.).

Cuando tenga una librería bien montada crearé otra serie de artículos dedicados a la OpenGL en Delphi. En el próximo artículo voy a comenzar hablando de las novedades de RAD Studio 2007 respecto a Delphi 7. Ya va siendo hora de guardar nuestro querido Delphi 7 en el cajón y aprovechar las nuevas ventajas del nuevo IDE de CodeGear. Con Embarcadero Technologies comienza una nueva era para Delphi. Espero que sea para mejor.

Pruebas realizadas en Delphi 7.

27 junio 2008

Programar videojuegos con la librería SDL (10)

CREANDO AL HEROE

Una vez tenemos el escenario vamos a crear el personaje del héroe que será el que maneje el usuario:


Para manejar el héroe de nuestro juego tenemos que declarar unas variables globales dentro de nuestra unidad UPlataformas.pas:

var
Fondo, Temporal, Piezas, Heroe: TSprite;
Mapa: array[1..16, 1..12] of char;
iPosicion, iEstado: Integer;
bMoviendo: Boolean; // ¿se esta moviendo el héroe?

La variable iPosicion va a a encargarse de controlar la posición de las piernas del héroe. Conforme vaya andando se irá incrementando la posición para dar el efecto de la animación.

La variable iEstado la vamos a utilizar la saber que está haciendo el personaje. Los posibles estados los vamos a definir en estas constantes al principio de la unidad:

const
// Estados del heroe
ANDANDO = 0;
ESCALERAS = 1;
CAYENDO = 2;
SALTANDO = 3;

La variable booleana bMoviendo va a ser nuestra bandera para controlar si el héroe se mueve o no. Si no se mueve tenemos que volver a ponerle las piernas en el suelo.

Lo siguiente a realizar es ampliar los métodos de cargar y destruir los sprites:

procedure CargarSprites;
begin
Fondo := TSprite.Create;
Fondo.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) +
'fondo.jpg' );

Temporal := TSprite.Create;
Temporal.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) +
'fondo.jpg' );

Piezas := TSprite.Create;
Piezas.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) +
'piezas.png' );
Piezas.bSubsprites := True;
Piezas.iAnchoSub := 40;
Piezas.iAltoSub := 40;

Heroe := TSprite.Create;
Heroe.CargarSuperficie( ExtractFilePath( ParamStr( 0 ) ) +
'heroe.png' );
Heroe.bSubsprites := True;
Heroe.iAnchoSub := 40;
Heroe.iAltoSub := 40;
Heroe.rx := 40;
Heroe.ry := 400;

CargarPantalla( 'pantalla1.txt' );
DibujarPiezasEnTemporal;
end;

procedure DestruirSprites;
begin
Heroe.Free;
Piezas.Free;
Temporal.Free;
Fondo.Free;
end;

En este juego vamos a controlar al héroe utilizando coordenadas en formato real en vez de con enteros. La razón es porque necesito más precisión para las rutinas de salto y caída al vacío.

Ahora vamos a reprogramar el procedimiento ControlarEventos para mover el héroe por pantalla:

procedure ControlarEventos;
begin
bMoviendo := False;
ComprobarDerecha;
ComprobarIzquierda;
ComprobarArriba;
ComprobarAbajo;

// Si no se esta moviendo lo dejamos en la posición inicial
if not bMoviendo then
Heroe.iSubX := 0;

// Transformarmos las coordenadas reales en formato entero
Heroe.x := Round( Heroe.rx );
Heroe.y := Round( Heroe.ry );
end;

Cada una de las direcciones en las que se mueve el héroe las he separado en un procedimiento para dar más claridad al código fuente:

procedure ComprobarDerecha;
begin
if ( Teclado.bDerecha or Joystick.bDerecha ) and
( Heroe.rx <> ESCALERAS ) then
begin
Heroe.iSubY := 0; // Hacemos que mire a la derecha
Heroe.rx := Heroe.rx + 2;
CambiarPosicion;
bMoviendo := True;
end;
end;

Cuando el usuario pulsa la tecla del cursor hacia la derecha tenemos que comprobar que no se salga de la pantalla y que no esté en las escaleras.

Igual lo hacemos hacia la izquierda:

procedure ComprobarIzquierda;
begin
if ( Teclado.bIzquierda or Joystick.bIzquierda ) and
( Heroe.rx > 40 ) and ( iEstado <> ESCALERAS ) then
begin
Heroe.iSubY := 1; // Hacemos que mire a la izquierda
Heroe.rx := Heroe.rx - 2;
CambiarPosicion;
bMoviendo := True;
end;
end;

Lo más difícil viene ahora, comprobar cuando pulsamos hacia arriba si estamos o no debajo de las escaleras:

procedure ComprobarArriba;
var
xMapa, yMapa: Integer;
begin
if ( Teclado.bArriba or Joystick.bArriba ) then
begin
// Pasamos de las coordenadas de pantalla a las coordenadas del mapa
xMapa := ( Round( Heroe.rx ) + 20 ) div 40 + 1;
yMapa := ( Round( Heroe.ry ) + 39 ) div 40 + 1;

if iEstado = ESCALERAS then
begin
Heroe.ry := Heroe.ry - 2;
CambiarPosicion;

// Si ha terminado de subir las escaleras lo dejamos de nuevo andando
if Mapa[xMapa,yMapa] <> '3' then
begin
Heroe.ry := ( yMapa - 1 ) * 40; // Ajustamos el heroe al suelo
Heroe.iSubY := 0;
iEstado := ANDANDO;
end;
end
else
begin
// ¿hay unas escaleras para subir?
if Mapa[xMapa,yMapa] = '3' then
begin
Heroe.rx := ( xMapa - 1 ) * 40; // centramos el heroe en las escaleras
Heroe.iSubY := 2;
iEstado := ESCALERAS;
end;
end;

bMoviendo := True;
end;
end;

Para comprobar esto he tenido que convertir las coordenadas del los pies del héroe en coordenadas de mapa, a fin de averiguar si tenemos delante las escaleras.

Lo mismo hay que hacer para bajar las escaleras:

procedure ComprobarAbajo;
var
xMapa, yMapa: Integer;
begin
if ( Teclado.bAbajo or Joystick.bAbajo ) then
begin
// Pasamos de las coordenadas de pantalla a las coordenadas del mapa
xMapa := ( Round( Heroe.rx ) + 20 ) div 40 + 1;
yMapa := ( Round( Heroe.ry ) + 41 ) div 40 + 1;

if iEstado = ESCALERAS then
begin
Inc( Heroe.y, 2 );
CambiarPosicion;

// Si ha terminado de subir las escaleras lo dejamos de nuevo andando
if Mapa[xMapa,yMapa] <> '3' then
begin
Heroe.ry := ( yMapa - 2 ) * 40; // Ajustamos el heroe al suelo
Heroe.iSubY := 0;
iEstado := ANDANDO;
end;
end
else
begin
// ¿hay unas escaleras para bajar?
if Mapa[xMapa,yMapa] = '3' then
begin
// centramos el heroe en las escaleras
Heroe.rx := ( xMapa - 1 ) * 40;
Heroe.iSubY := 2;
iEstado := ESCALERAS;
end;
end;

bMoviendo := True;
end;
end;

En estos cuatro procedimientos para comprobar las direcciones del héroe he llamado a un nuevo procedimiento que comprueba si una parte del mapa es sólido o no:

function Choca( x, y: Integer ): Boolean;
var
xMapa, yMapa: Integer;
begin
// Pasamos de las coordenadas de pantalla a las coordenadas del mapa
xMapa := x div 40 + 1;
yMapa := y div 40 + 1;
Result := ( Mapa[xMapa,yMapa] = '1' ) or ( Mapa[xMapa,yMapa] = '2' );
end;

Lo que hacemos es comprobar si chocamos con el suelo de piedra (1) o el de madera (2).

Otro procedimiento que también llamo es el siguiente:

procedure CambiarPosicion;
begin
Inc( iPosicion );

if iPosicion > 4 then
begin
if Heroe.iSubX = 0 then
Heroe.iSubX := 1
else
Heroe.iSubX := 0;

iPosicion := 0;
end;
end;

Lo que hace es cambiar la posición del héroe al andar o al subir las escaleras. Cada vez que el personaje se mueve 4 pixels volvemos a cambiar de posición.

LA LEY DE LA GRAVEDAD

Para darle más emoción al juego vamos a hacer unos agujeros en el mapa para que el héroe tenga que saltar por encima de ellos, aunque lo primero que tenemos que hacer es que se caiga por ellos.

Como vimos en el artículos anterior, el mapa se guardar en un archivos de texto llamado pantalla1.txt. A ese archivo le vamos a hacer unos agujeros en el suelo para que el héroe tenga que saltar:

1111111111111111
1000000000000001
1000400000000001
1222222022322221
1000000000300001
1000000000300041
1232202222222221
1030000000000001
1030004000000001
1222222202232221
1000000000030041
1111111111111111

El procedimiento ComprobarSiCae va a encargarse de estar continuamente vigilando si debajo del héroe hay un suelo al que agarrarse:

procedure ComprobarSiCae;
var
xMapa, yMapa: Integer;
begin
if ( iEstado = ESCALERAS ) or ( iEstado = SALTANDO ) then
Exit;

// Pasamos de las coordenadas de pantalla a las coordenadas del mapa
xMapa := ( Round( Heroe.rx ) + 20 ) div 40 + 1;
yMapa := ( Round( Heroe.ry ) + 40 ) div 40 + 1;

// ¿No estaba cayendo anteriormente?
if iEstado <> CAYENDO then
// Inicializamos la aceleración
rAceleracion := 0.1;

// ¿No ha chocado con el suelo?
if ( Mapa[xMapa,yMapa] <> '1' ) and ( Mapa[xMapa,yMapa] <> '2' ) and
( Mapa[xMapa,yMapa] <> '3' ) then
begin
iEstado := CAYENDO;
Heroe.ry := Heroe.ry + rAceleracion;

if rAceleracion <= 4 then
rAceleracion := rAceleracion + 0.5;
end
else
begin
iEstado := ANDANDO;
// Ajustamos el heroe al suelo
Heroe.ry := ( yMapa - 2 ) * 40;
end;
end;

Este procedimiento hay que llamarlo dentro de ControlarEventos:

procedure ControlarEventos;
begin
bMoviendo := False;
ComprobarDerecha;
ComprobarIzquierda;
ComprobarArriba;
ComprobarAbajo;
ComprobarSiCae;
...
end;

SALTANDO OBSTÁCULOS

El último movimiento que le vamos a hacer al héroe es el salto. Realmente sólo hay que controlar la mitad de salto, una vez termina la subida empieza a caer. El salto lo vamos a realizar si el usuario pulsa la barra de espacio:

procedure ControlarEventos;
begin
bMoviendo := False;
ComprobarDerecha;
ComprobarIzquierda;
ComprobarArriba;
ComprobarAbajo;
ComprobarSiCae;
ComprobarSiSalta;

if ( Teclado.bEspacio or Joystick.Boton[0] ) and
( iEstado = ANDANDO ) then
begin
rAceleracion := 5;
iEstado := SALTANDO;
end;

// Si no se esta moviendo lo dejamos en la posición inicial
if not bMoviendo then
Heroe.iSubX := 0;

// Transformarmos las coordenadas reales en formato entero
Heroe.x := Round( Heroe.rx );
Heroe.y := Round( Heroe.ry );
end;

Hemos añadido la llamada al procedimiento ComprobarSiSalta para comprobar en todo momento la subida y la deceleración:

procedure ComprobarSiSalta;
begin
if iEstado = SALTANDO then
if rAceleracion > 0 then
begin
Heroe.ry := Heroe.ry - rAceleracion;
rAceleracion := rAceleracion - 0.4;
end
else
iEstado := CAYENDO;
end;

Para hacer la caída lo más suave posible primero creo una aceleración pequeña y luego incremento la aceleración hasta 4 pixels por movimiento. Cuando choque con el suelo lo volvemos a dejar andando.

Conforme sube hacia arriba decrementamos la aceleración hasta llegar a cero. Luego cambiamos el estado a CAYENDO y dejamos que nuestro código haga el resto.

EL DINERO ES LO PRIMERO

El objetivo del juego va a ser recoger las joyas sin que nos pillen los enemigos. Lo que hay que hacer es un procedimiento que compruebe si donde está el héroe hay una joya. Cuando la encuentre la eliminamos del mapa y volvemos a dibujar el mapa en la pantalla Temporal antes de llevarla a la pantalla de vídeo.

Vamos por pasos. Primero hacemos el procedimiento que recoge las joyas:

procedure ComprobarTesoro;
var
xMapa, yMapa: Integer;
begin
// Pasamos de las coordenadas de pantalla a las coordenadas del mapa
xMapa := ( Round( Heroe.rx ) + 20 ) div 40 + 1;
yMapa := ( Round( Heroe.ry ) + 20 ) div 40 + 1;

// ¿Ha encontrado una joya?
if Mapa[xMapa,yMapa] = '4' then
begin
// la quitamos del mapa
Mapa[xMapa,yMapa] := '0';

// copiamos la pantalla de fondo a la pantalla temporal
Fondo.DibujarEn( Temporal.Superficie );

// Dibujamos las piezas en la pantalla temporal
DibujarPiezasEnTemporal;
end;
end;

Lo que hace es mirar en el mapa si estamos encima de una joya. Si es así entonces la elimina del mapa y refresca la pantalla.

Este procedimiento hay que llamarlo desde ControlarEventos:

procedure ControlarEventos;
begin
bMoviendo := False;
ComprobarDerecha;
ComprobarIzquierda;
ComprobarArriba;
ComprobarAbajo;
ComprobarSiCae;
ComprobarSiSalta;
ComprobarTesoro;

if ( Teclado.bEspacio or Joystick.Boton[0] ) and
( iEstado = ANDANDO ) then
begin
rAceleracion := 5;
iEstado := SALTANDO;
end;

// Si no se esta moviendo lo dejamos en la posición inicial
if not bMoviendo then
Heroe.iSubX := 0;

// Transformarmos las coordenadas reales en formato entero
Heroe.x := Round( Heroe.rx );
Heroe.y := Round( Heroe.ry );
end;


Este es el resultado final del juego:


Aunque donde mejor se aprecia es ejecutando mi proyecto. Aquí lo tenéis comprimido con ZIP en los tres servidores de siempre:

https://mega.nz/file/5FhU2SyD#ZavwnR9Amwcpub4wGaTI5zLsqEOPmUxuy201rtIUaOA

En el próximo artículo meteremos los enemigos en pantalla y terminaré esta serie de artículos dedicada a la programación en SDL.

Pruebas realizadas en Delphi 7.

Publicidad