24 octubre 2007

Como dibujar sprites transparentes

Un Sprite es una figura gráfica móvil utilizada en los videojuegos de dos dimensiones. Por ejemplo, un videojuego de naves espaciales consta de los sprites de la nave, los meteoritos, los enemigos, etc., es decir, todo lo que sea móvil en pantalla y que no tenga que ver con los paisajes de fondo.

Anteriormente vimos como copiar imágenes de una superficie a otra utilizando los métodos Draw o CopyRect que se encuentran en la clase TCanvas. También se vió como modificar el modo de copiar mediante la propiedad CopyMode la cual permitia los valores cmSrcCopy, smMergePaint, etc.

El problema radica en que por mucho que nos empeñemos en dibujar una imagen transparente ningún tipo de copia funciona: o la hace muy transparente o se estropea el fondo.

DIBUJAR SPRITES MEDIANTE MASCARAS

Para dibujar sprites transparentes hay que tener dos imágenes: la original cuyo color de fondo le debemos dar alguno como común (como el negro) y la imagen de la máscara que es igual que la original pero como si fuera un negativo.

Supongamos que quiero dibujar este sprite (archivo BMP):


Es conveniente utilizar de color transparente un color de uso como común. En este caso he elegido el color rosa cuyos componentes RGB son (255,0,255). La máscara de esta imagen sería la siguiente:


Esta máscara no es necesario crearla en ningún programa de dibujo ya que la vamos a crear nosotros internamente. Vamos a encapsular la creación del sprite en la siguiente clase:

type
TSprite = class
public
x, y: Integer;
ColorTransparente: TColor;
Imagen, Mascara: TImage;
constructor Create;
destructor Destroy; override;
procedure Cargar( sImagen: string );
procedure Dibujar( Canvas: TCanvas );
end;

Esta clase consta de las coordenadas del sprite, el color que definimos como transparente y las imágenes a dibujar incluyendo su máscara. En el constructor de la clase creo dos objetos de la clase TImage en memoria:

constructor TSprite.Create;
begin
inherited;
Imagen := TImage.Create( nil );
Imagen.AutoSize := True;
Mascara := TImage.Create( nil );
ColorTransparente := RGB( 255, 0, 255 );
end;

También he definido el color rosa como color transparente. Como puede apreciarse he utilizado el procedimiento RGB que convierte los tres componentes del color al formato de TColor que los guarda al revés BGR. Así podemos darle a Delphi cualquier color utilizando estos tres componentes copiados de cualquier programa de dibujo.

En el destructor de la clase nos aseguramos de que se liberen de memoria ambos objetos:

destructor TSprite.Destroy;
begin
Mascara.Free;
Imagen.Free;
inherited;
end;

Ahora implementamos la función que carga de un archivo la imagen BMP y a continuación crea su máscara:

procedure TSprite.Cargar( sImagen: string );
var
i, j: Integer;
begin
Imagen.Picture.LoadFromFile( sImagen );
Mascara.Width := Imagen.Width;
Mascara.Height := Imagen.Height;

for j := 0 to Imagen.Height - 1 do
for i := 0 to Imagen.Width - 1 do
if Imagen.Canvas.Pixels[i,j] = ColorTransparente then
begin
Imagen.Canvas.Pixels[i,j] := 0;
Mascara.Canvas.Pixels[i,j] := RGB( 255, 255, 255 );
end
else
Mascara.Canvas.Pixels[i,j] := RGB( 0, 0, 0 );
end;

Aquí nos encargamos de dejar la máscara en negativo a partir de la imagen original.

Para dibujar sprites recomiendo utilizar archivos BMP en lugar de JPG debido a que este último tipo de imágenes pierden calidad y podrían afectar al resultado de la máscara, dando la sensación de que el sprite tiene manchas. Hay otras librerías para Delphi que permiten cargar imágenes PNG que son ideales para la creación de videojuegos, pero esto lo veremos en otra ocasión.

Una vez que ya tenemos nuestro sprite y nuestra máscara asociada al mismo podemos crear el procedimiento encargado de dibujarlo:

procedure TSprite.Dibujar( Canvas: TCanvas );
begin
Canvas.CopyMode := cmSrcAnd;
Canvas.Draw( x, y, Mascara.Picture.Graphic )
Canvas.CopyMode := cmSrcPaint;
Canvas.Draw( x, y, Imagen.Picture.Graphic );}
end;

El único parámetro que tiene es el Canvas sobre el que se va a dibujar el sprite. Primero utilizamos la máscara para limpiar el terreno y después dibujamos el sprite sin el fondo. Vamos a ver como utilizar nuestra clase para dibujar un sprite en el formulario:

procedure TFormulario.DibujarCoche;
var
Sprite: TSprite;
begin
Sprite := TSprite.Create;
Sprite.x := 100;
Sprite.y := 100;
Sprite.Cargar( ExtractFilePath( Application.ExeName ) + 'sprite.bmp' );
Sprite.Dibujar( Canvas );
Sprite.Free;
end;

Este sería el resultado en el formulario:


Si el sprite lo vamos a dibujar muchas veces no es necesario crearlo y destruirlo cada vez. Deberíamos crearlo en el evento OnCreate del formulario y en su evento OnDestroy liberarlo (Sprite.Free).

En el próximo artículo veremos como mover el sprite en pantalla utilizando una técnica de doble buffer para evitar parpadeos en el movimiento.

Pruebas realizadas en Delphi 7.

Publicidad