Lazarus: escribir un componente para la animación de sprites

En lugar del prólogo

En la escuela de Odessa, los estudiantes de octavo grado en las clases de ciencias de la computación usan el entorno gratuito de desarrollo multiplataforma Lazarus ( sitio oficial ), que se parece mucho al querido de muchos Delphi, usando la versión de Object Pascal llamada Free Pascal y realmente simplificando enormemente el proceso de ingresar a la programación.

Pero los niños no están interesados ​​en escribir un programa para calcular la gravedad usando la fórmula F = mg, que aún no les queda claro. Casi todos los niños que intenté enseñar programación quieren escribir un juego desde la primera lección. Afortunadamente, Lazarus es excelente para escribir juegos simples.

Sin embargo, para crear sprites animados, necesitaba un componente que muestre un fragmento arbitrario de la imagen (que representa varias proyecciones diferentes del mismo personaje en diferentes fases de movimiento), pero no hay tal componente en la entrega estándar. Resultó ser muy fácil escribirlo usted mismo, y quiero hablar sobre esta tecnología en este artículo.

Para mostrar contenido gráfico divertido en lugar de un conjunto empresarial seco de componentes estándar en Lazarus (como en Delphi), hay 3 componentes en la pestaña Adicional:
- TImage (que muestra una imagen de un archivo arbitrario);
- TShape (visualización de una de varias primitivas gráficas predefinidas);
- TPaintBox (muestra un lienzo en el que puede dibujar mediante programación).

Lo más espectacular para un estudiante es cargar un pequeño sprite en TImage y escribir un programa para moverlo por la pantalla, de acuerdo con los eventos del mouse / teclado, automáticamente en un bucle o automáticamente por un evento desde un temporizador.

Tan pronto como esto comienza a funcionar, el estudiante tiene la siguiente pregunta legítima: ¿es posible hacer que el personaje se mueva? ¿Y es posible hacerlo para que no nos mire constantemente, sino que gire en la dirección que coincide con la dirección del movimiento?

En la Web puedes encontrar una gran cantidad de imágenes listas para usar en el desarrollo de juegos. Y muchos personajes están prediseñados en varias proyecciones y varios cuadros de animación (como, por ejemplo, aquí en este sitio ).

Aquí hay una imagen de ejemplo donde los sprites están dispuestos en forma de una tabla, en la que cada fila corresponde a una determinada proyección, y cada columna corresponde a una determinada fase de animación:


¿Por qué tantas fotos?
Para mostrar tal sprite, es suficiente colocar en la pantalla un componente simple que muestre no toda la imagen, sino solo un fragmento; y luego, cambiando el desplazamiento del fragmento seleccionado horizontal y verticalmente, puede hacer que el personaje gire en diferentes direcciones y realice movimientos cíclicos (por ejemplo, aleteo de alas o pasos con patas). Esta técnica se usa a menudo en el desarrollo web: incluso conjuntos simples de iconos para gráficos comerciales a menudo se colocan en un archivo y se muestran en diferentes lugares en páginas con diferentes desplazamientos, dando la impresión de diferentes imágenes.

Desafortunadamente, el componente TImage incluido en la entrega estándar de Lazarus (y Delphi) no permite mostrar un fragmento arbitrario de una imagen: al cambiar sus propiedades, podemos obligarlo a mostrar solo la imagen completa, la esquina superior izquierda o su parte central. Para mostrar un fragmento arbitrario de la imagen definida por el desplazamiento y las dimensiones a lo largo de ambos ejes, necesita algún otro componente. Pero, resultó que hacerlo usted mismo en Lázaro no es nada difícil.

Crea un nuevo componente

Como instrucción para crear componentes, utilicé la guía oficial .

Todo está escrito allí con suficiente detalle; la duplicación no tiene sentido. Solo me detendré en algunos puntos.

1. El Asistente de proyecto estándar no nos ofrece crear un paquete y, de alguna manera, tener acceso al editor, seleccione "Nuevo proyecto" (en la versión rusa - "Nuevo proyecto")


y luego "Aplicación" (en la versión rusa - "Aplicación"):


2. Actuando de acuerdo con las instrucciones, en el menú "Paquete" (en la versión rusa - "Paquete"), seleccione el elemento superior "Nuevo paquete ..." (en la versión rusa - "Nuevo paquete ..."), seleccione el nombre y la ruta del archivo para salvar Llamé a mi nuevo paquete "Juego" y lo coloqué en una carpeta separada con el mismo nombre:


Creé una carpeta separada de Lazarus / Cmp con la expectativa de que podría tener varios paquetes diferentes con componentes, y ya creé la carpeta "Juego" en esta carpeta.

Si todo se hace correctamente, debería aparecer una ventana de un nuevo paquete (hasta ahora vacío) en la pantalla.

3. Continúe nuevamente de acuerdo con las instrucciones, para crear un nuevo componente en la ventana del paquete, haga clic en el botón Agregar (en la versión rusa - "Agregar") y seleccione "Nuevo componente" en la lista desplegable (en la versión rusa - "Nuevo componente"):


Especificamos TCustomImage como la clase ancestro: esta clase se usa realmente para implementar el componente TImage, pero se diferencia de él en que no contiene propiedades publicadas y nos permite determinar el conjunto de propiedades que estarán disponibles en el diseñador para nuestro componente.

¿Qué son las propiedades publicadas?
Para aquellos que no saben esto, aclararé que publicado es una sección de la clase (como público) que describe nuevas propiedades o simplemente indica propiedades heredadas que deberían estar disponibles en el editor de propiedades visuales en la etapa de desarrollo del programa. Las clases intermedias no declaran nada en esta sección, dejando la oportunidad para que el programador saque lo que le parezca. Por lo tanto, la clase TImage no agrega ninguna funcionalidad, sino que solo coloca una serie de propiedades heredadas del padre TCustomImage en la sección publicada. Necesitamos ocultar algunas de estas propiedades, por lo que también heredaremos nuestro nuevo componente de TCustomImage y mostraremos solo lo que no contradiga la lógica de nuestro componente publicado.

Icono (icono) para el componente
Sería un buen estilo dibujar un ícono personal para cada nuevo componente, pero dado que nuestra tarea es mostrar cuán simple es, dejaremos este campo vacío, lo que llevará al ícono estándar utilizado en Lazarus / Delphi para todos los componentes caseros en la barra de herramientas .
Por cierto, la instrucción mencionada anteriormente contiene una sección separada sobre la creación de iconos para componentes, esto es para aquellos que no están satisfechos con el icono "predeterminado".

Después de completar todos los campos, haga clic en el botón "Crear nuevo componente" (en la versión rusa - "Crear un nuevo componente").

Agregue código al nuevo componente.

Inmediatamente después de crear un nuevo componente, su código fuente es algo como esto:

unit ImageFragment; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs; type TImageFragment = class(TCustomImage) private protected public published end; procedure Register; implementation procedure Register; begin RegisterComponents('Game', [TImageFragment]); end; end. 

Como se esperaba, la declaración de clase está completamente vacía y no hay implementación en absoluto. Todo lo que es es la función de registro de componentes en la pestaña "Juego".

Necesitamos agregar varias propiedades publicadas heredadas, crear dos propias y redefinir una función virtual. ¡Empecemos!

0. En la sección de importación, necesitamos dos módulos adicionales: ExtCtrls y LCLProc - agréguelos a la sección de usos:

 uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls, LCLProc; 

1. Agregue una lista de propiedades publicadas que sea completamente similar al componente TImage, a excepción de algunas propiedades que le permiten cambiar la escala y la posición de la imagen:

  published property AntialiasingMode; property Align; property Anchors; //property AutoSize; property BorderSpacing; //property Center; //property KeepOriginXWhenClipped; //property KeepOriginYWhenClipped; property Constraints; property DragCursor; property DragMode; property Enabled; property OnChangeBounds; property OnClick; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnMouseDown; property OnMouseEnter; property OnMouseLeave; property OnMouseMove; property OnMouseUp; property OnMouseWheel; property OnMouseWheelDown; property OnMouseWheelUp; property OnPaint; property OnPictureChanged; property OnPaintBackground; property OnResize; property OnStartDrag; property ParentShowHint; property Picture; property PopupMenu; //property Proportional; property ShowHint; //property Stretch; //property StretchOutEnabled; //property StretchInEnabled; property Transparent; property Visible; end; 

En aras de la persuasión, no eliminé, pero comenté aquellas propiedades que están en el componente TImage, pero interferirán en nuestro nuevo componente TImageFragment.

2. Agregue dos nuevas propiedades a la declaración de clase para establecer las compensaciones de imagen horizontal y vertical:

  private FOffsetX: Integer; FOffsetY: Integer; procedure SetOffsetX(AValue: Integer); procedure SetOffsetY(AValue: Integer); published property OffsetX: Integer read FOffsetX write SetOffsetX default 0; property OffsetY: Integer read FOffsetY write SetOffsetY default 0; 

y no olvide agregar dos procedimientos declarados a la implementación de la clase:
 implementation procedure TImageFragment.SetOffsetX(AValue: Integer); begin if FOffsetX = AValue then exit; FOffsetX := AValue; PictureChanged(Self); end; procedure TImageFragment.SetOffsetY(AValue: Integer); begin if FOffsetY = AValue then exit; FOffsetY := AValue; PictureChanged(Self); end; 

3. Redefinimos la función virtual DestRect:

  public function DestRect: TRect; override; 

y agregue su implementación a la implementación de la clase:

 function TImageFragment.DestRect: TRect; begin Result := inherited DestRect(); if (FOffsetX <> 0) or (FOffsetY <> 0) then LCLProc.OffsetRect(Result, -FOffsetX, -FOffsetY); end; 

Compila el paquete y reconstruye Lázaro

1. En la ventana del paquete, haga clic en el botón "Compilar" (en la versión rusa - "Compilar"). Si todo se hace correctamente, aparecerá un mensaje verde sobre la compilación exitosa en la ventana del mensaje; de ​​lo contrario, el mensaje será amarillo o rojo.

2. En la misma ventana, haga clic en el botón "Usar" (en la versión rusa - "Usar") y seleccione el segundo elemento "Instalar" en el menú desplegable (en la versión rusa - "Instalar"). El programa ofrecerá reconstruir y reiniciar el IDE. Estamos de acuerdo:



3. Después del reinicio, aparecerá una nueva pestaña "Juego" en la barra de herramientas, y en ella, un icono para nuestro nuevo componente.

En lugar de un epílogo

En el siguiente artículo de Lazarus, una animación simple que usa el componente TImageFragment, hablé sobre cómo usar dicho componente, en 5 minutos crea una ventana en la que el personaje animado se moverá en diferentes direcciones y rotará en la dirección del movimiento.

Si el tema es interesante para los lectores, puedo complementar esta serie con un artículo sobre cómo, habiendo pasado un poco más de tiempo, puede hacer, por ejemplo, un campo de fútbol con un par de jugadores de fútbol controlados por teclado.

Y si hay suficiente tiempo y ganas, ¡intentaré escribir diferentes algoritmos de control de personajes (por ejemplo, jugadores de fútbol) y organizar competiciones entre ellos!

Source: https://habr.com/ru/post/438828/


All Articles