Lazarus - animação simples usando o componente TImageFragment

Em vez do prefácio

No meu recente artigo do Lazarus - escrevendo um componente para animação de sprite, descrevi o processo de criação de um componente TImageFragment simples que permite exibir um determinado fragmento de uma imagem.

Continuando o tópico selecionado, neste artigo, quero mostrar como é fácil fazer animações de sprites no ambiente de desenvolvimento do Lazarus ( site oficial ) usando esse componente.

Com essa abordagem, os quadros de animação individuais em diferentes projeções são colocados na mesma imagem, e o componente para exibir o sprite mostra apenas um fragmento selecionado dessa imagem usando as propriedades OffsetX e OffsetY (deslocamento do canto superior esquerdo do fragmento de imagem horizontal e verticalmente).

Muitas dessas imagens prontas podem ser encontradas na Web - por exemplo, aqui neste site .

Selecione (e prepare) a imagem

Para o meu exemplo, escolhi esta imagem:

- muito expressivamente, este pássaro Phoenix bate as asas.

Como você pode ver, cada linha contém 4 quadros para cada uma das 4 projeções. Ao alterar apenas o OffsetX , você pode fazer o pássaro bater as asas e, para alterar a projeção, basta alterar o OffsetY . Essa separação de quadros por linhas simplifica bastante a programação da animação.

O tamanho desta imagem é 384x384 e o tamanho de cada quadro é 96x96. Infelizmente, o uso direto dessa imagem nos incomoda com artefatos: alguns quadros da imagem são colocados para que suas bordas caiam em quadros adjacentes e, durante a animação, traços amarelos piscam nas bordas do sprite.

Para corrigir esses defeitos, usei o editor gráfico gratuito de plataforma cruzada GIMP ( site oficial ). Tudo o que precisava ser feito era remover os pixels salientes das imagens nos locais onde elas caíam no quadro adjacente.

O arquivo corrigido fica assim:



- A olho nu, as diferenças são invisíveis, mas a segunda opção funciona sem artefatos.

Crie um novo projeto

1. Crie um novo projeto do tipo "Aplicativo".

Por padrão, o IDE cria um projeto chamado "projeto1", que cria imediatamente um módulo de programa chamado "unidade1", que descreve uma classe chamada "" TForm1 "e declara uma instância com o nome" Form1 ".

Em geral, ao criar novos objetos, o IDE atribui nomes semelhantes, consistindo no nome do tipo de objeto e número de série. Considero um bom estilo renomear todos esses objetos, dando-lhes nomes significativos que refletem o papel ou a finalidade do objeto.

Portanto, nosso projeto não será chamado de "projeto1", mas "Phoenix" - de acordo com o nome do sprite selecionado.

2. Salve nosso novo projeto.

É aconselhável salvar cada projeto em um diretório separado com um nome que corresponda ao nome do projeto. Durante o processo de salvamento, especificamos o diretório para salvamento (se necessário, criamos ali), depois o nome do arquivo do projeto e o nome do arquivo do módulo do programa. Criei a pasta "Phoenix" e salvei o arquivo do projeto ("Phoenix.lpi" em vez do "project1.lpi" proposto) e o arquivo do módulo do programa ("UnitMain.pas" em vez do "unit1.pas" proposto).

Nuance de maiúsculas e minúsculas
A versão do Lazarus para Windows leva o nome do arquivo do módulo do programa para letras minúsculas: “unitmain.pas”, mas o nome do programa do módulo mantém a caixa original dos caracteres: “unit UnitMain;”. Isso não acontece com o arquivo do projeto; o nome do arquivo preserva o caso original dos caracteres.

3. Renomeie o formulário e altere seu título.

O formulário recém-criado, chamado “Form1” (propriedade Nome ), é uma instância da classe “TForm1” e contém o título “Form1” (propriedade Caption ). Altere a propriedade Name do formulário para "FormMain" e o nome da classe será alterado para "TFormMain".

Altere a propriedade Caption para "Phoenix" para que o título do projeto seja exibido no título da janela.

4. Como resultado, recebi o seguinte texto do módulo unitmain.pas:

unit UnitMain; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs; type TFormMain = class(TForm) private public end; var FormMain: TFormMain; implementation {$R *.lfm} end. 


5. Compile, execute o projeto (chave <F9>):



Coloque o sprite no formulário

Supondo que você já tenha o componente TImageFragment instalado , descrito no meu artigo anterior do Lazarus - escrevemos um componente para animação de sprite , selecione a guia "Jogo" na paleta de componentes e adicione o componente "TImageFragment" ao formulário.

Usando a propriedade Picture , carregue uma imagem (uma versão fixa do pássaro Phoenix) no componente. Além disso, também alteramos as seguintes propriedades do novo objeto:

  • defina as propriedades Altura e Largura como 96
  • defina as propriedades Left e Top como 0 (conveniente para combinar com minhas capturas de tela)
  • A propriedade Name é alterada do inconveniente "ImageFragment1" para um simples e compreensível "Sprite"

Se tudo for feito corretamente, o componente mostrará o primeiro quadro da imagem:


O texto do módulo UnitMain sofrerá pequenas alterações:
- o módulo ImageFragment é adicionado à seção usos

 uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ImageFragment; 

- um novo objeto aparecerá na declaração de classe

  TFormMain = class(TForm) Sprite: TImageFragment; private public end; 


Adicionar animação - flaps de asas

1. Adicione um novo componente da classe TTimer ao formulário .

Este componente está localizado na guia "Sistema" da paleta de componentes. Você pode colocá-lo em qualquer local conveniente no formulário, pois ele não é exibido em um aplicativo em execução.

2. Renomeie o objeto adicionado.

O novo objeto recebe automaticamente o nome "Timer1", mas o renomeamos para "TimerLive". Muitas vezes, é conveniente atribuir nomes aos objetos, consistindo em duas partes: a primeira reflete a classe do objeto e a segunda reflete seu objetivo.

3. Altere a propriedade Interval de 1000 para 100.

Permita que os quadros desta animação se substituam a cada 100 milissegundos, ou seja, 10 vezes por segundo. No futuro, essa propriedade poderá ser alterada para diminuir a velocidade ou acelerar a envergadura - a critério do programador.

4. Adicione um manipulador de eventos OnTimer.

A maneira mais fácil de fazer isso é clicar duas vezes no ícone de um novo objeto TimerLive . Como resultado dessa ação, o próprio IDE adicionará um novo procedimento à declaração de classe de formulário, um link para esse procedimento nas propriedades do objeto e o corpo do novo procedimento será adicionado à seção de implementação (e o cursor será colocado dentro deste novo procedimento, entre as palavras-chave de início e fim ).

5. Adicione uma linha de código ao novo procedimento.

  Sprite.OffsetX := (Sprite.OffsetX + 96) mod 384; 

Como resultado dessas ações, a declaração de classe deve ser algo como isto:

  TFormMain = class(TForm) Sprite: TImageFragment; TimerLive: TTimer; procedure TimerLiveTimer(Sender: TObject); private public end; 

E o novo procedimento - o manipulador de eventos OnTimer deve ser algo como isto:

 procedure TFormMain.TimerLiveTimer(Sender: TObject); begin Sprite.OffsetX := (Sprite.OffsetX + 96) mod 384; end; 

Após compilar e executar o aplicativo, você pode assistir o pássaro Phoenix batendo suas asas.

Isso acontece porque o manipulador de eventos do timer a cada 100 milissegundos muda ciclicamente o deslocamento do fragmento exibido e o quadro selecionado é deslocado horizontalmente, exibindo sequencialmente 4 quadros da linha superior da imagem carregada. A operação mod - obtendo o restante da divisão - impede que o deslocamento ultrapasse o tamanho da imagem e, como resultado, o quarto quadro é novamente seguido pelo primeiro.

Adicione um movimento de sprite pela janela

1. Adicione o módulo Math à seção usos

 uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, ImageFragment, Math; 

2. Adicione uma nova variável e constante à declaração de classe.

Para salvar o vetor de mover o sprite pela janela, adicione uma variável do tipo TPoint

  private FVector: TPoint; 

No mesmo local, declaramos uma constante para definir o módulo de velocidade

  const Speed = 10; 

3. Adicione outro componente da classe TTimer ao formulário .

Lembro que: este componente está localizado na guia "Sistema" da paleta de componentes.

O novo objeto novamente recebe automaticamente o nome "Timer1", e o renomeamos - desta vez para "TimerMove". O objetivo do segundo temporizador é controlar o movimento do sprite. Eu não vinculei os dois processos (animação e movimento) ao mesmo cronômetro, para que cada um dos cronômetros pudesse ser definido separadamente - por exemplo, para diminuir a frequência dos movimentos das asas sem desacelerar o movimento e assim por diante.

4. Altere a propriedade Interval de 1000 para 100.

Deixe esse timer também disparar a cada 100 milissegundos, ou seja, 10 vezes por segundo. No futuro, essa propriedade também poderá ser alterada para diminuir ou acelerar a frequência de renderização do fato do sprite em movimento.

5. Adicione um manipulador de eventos OnTimer .

Para variar, desta vez, proponho fazer isso clicando duas vezes em frente ao evento OnTimer na guia "Events" do novo objeto TimerMove . Na última vez, como resultado dessa ação, o próprio IDE adicionará um novo procedimento à declaração da classe de formulário, um link para este procedimento nas propriedades do objeto e o corpo do novo procedimento será adicionado à seção de implementação (e o cursor será colocado dentro deste novo procedimento, entre a chave palavras começam e terminam ).

6. Adicione duas linhas de código ao novo procedimento.

  Sprite.Left := Max(0, Min(Width - Sprite.Width, Sprite.Left + FVector.x)); Sprite.Top := Max(0, Min(Height - Sprite.Height, Sprite.Top + FVector.y)); 

O uso das funções Max () e Min () impede que o sprite saia do formulário (a janela principal do aplicativo).
É para usar essas funções que conectamos o módulo Math à seção de usos .

7. Adicione um manipulador de eventos OnKeyPress .

Selecione o formulário (clique no retângulo cinza do layout da janela, fora de todos os componentes adicionados) e, na guia Eventos, encontramos o evento OnKeyPress . Ao clicar duas vezes no valor vazio do manipulador de eventos, criamos e atribuímos um novo procedimento - o manipulador de eventos.

8. Adicione algumas linhas de código ao novo procedimento.

  if Key = 'a' then FVector := TPoint.Create(-Speed, 0) else if Key = 'd' then FVector := TPoint.Create(Speed, 0) else if Key = 'w' then FVector := TPoint.Create(0, -Speed) else if Key = 's' then FVector := TPoint.Create(0, Speed) else if Key = ' ' then FVector := TPoint.Create(0, 0); 

Como resultado dessas ações, a declaração de classe deve ser algo como isto:

  TFormMain = class(TForm) Sprite: TImageFragment; TimerMove: TTimer; TimerLive: TTimer; procedure FormKeyPress(Sender: TObject; var Key: char); procedure TimerLiveTimer(Sender: TObject); procedure TimerMoveTimer(Sender: TObject); private FVector: TPoint; const Speed = 10; public end; 

E os novos procedimentos - os manipuladores de eventos OnTimer e OnKeyPress devem ter a seguinte aparência:

 procedure TFormMain.TimerMoveTimer(Sender: TObject); begin Sprite.Left := Max(0, Min(Width - Sprite.Width, Sprite.Left + FVector.x)); Sprite.Top := Max(0, Min(Height - Sprite.Height, Sprite.Top + FVector.y)); end; procedure TFormMain.FormKeyPress(Sender: TObject; var Key: char); begin if Key = 'a' then FVector := TPoint.Create(-Speed, 0) else if Key = 'd' then FVector := TPoint.Create(Speed, 0) else if Key = 'w' then FVector := TPoint.Create(0, -Speed) else if Key = 's' then FVector := TPoint.Create(0, Speed) else if Key = ' ' then FVector := TPoint.Create(0, 0); end; 

Após compilar e executar o aplicativo, você pode mover o pássaro Phoenix pela tela usando as teclas "a", "w", "s", "d" e pará-lo com a barra de espaço.

Usamos diferentes projeções do sprite

Adicione o seguinte código ao final do procedimento TFormMain.FormKeyPress

  if FVector.x < 0 then Sprite.OffsetY := 96 else if FVector.x > 0 then Sprite.OffsetY := 192 else if FVector.y < 0 then Sprite.OffsetY := 288 else Sprite.OffsetY := 0; 

Alterar a propriedade OffsetY , dependendo do vetor de deslocamento, faz com que a imagem gire na direção do movimento.

Todo o texto do módulo UnitMain
 unit UnitMain; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, ImageFragment, Math; type { TFormMain } TFormMain = class(TForm) Sprite: TImageFragment; TimerMove: TTimer; TimerLive: TTimer; procedure FormKeyPress(Sender: TObject; var Key: char); procedure TimerLiveTimer(Sender: TObject); procedure TimerMoveTimer(Sender: TObject); private FVector: TPoint; const Speed = 10; public end; var FormMain: TFormMain; implementation {$R *.lfm} { TFormMain } procedure TFormMain.TimerLiveTimer(Sender: TObject); begin Sprite.OffsetX := (Sprite.OffsetX + 96) mod 384; end; procedure TFormMain.TimerMoveTimer(Sender: TObject); begin Sprite.Left := Max(0, Min(Width - Sprite.Width, Sprite.Left + FVector.x)); Sprite.Top := Max(0, Min(Height - Sprite.Height, Sprite.Top + FVector.y)); end; procedure TFormMain.FormKeyPress(Sender: TObject; var Key: char); begin if Key = 'a' then FVector := TPoint.Create(-Speed, 0) else if Key = 'd' then FVector := TPoint.Create(Speed, 0) else if Key = 'w' then FVector := TPoint.Create(0, -Speed) else if Key = 's' then FVector := TPoint.Create(0, Speed) else if Key = ' ' then FVector := TPoint.Create(0, 0); if FVector.x < 0 then Sprite.OffsetY := 96 else if FVector.x > 0 then Sprite.OffsetY := 192 else if FVector.y < 0 then Sprite.OffsetY := 288 else Sprite.OffsetY := 0; end; end. 

Em vez de um posfácio

Este exemplo simples não reivindica classificações altas de velocidade ou usabilidade. Se alguém, como no artigo anterior , quiser dizer nos comentários que a animação precisa ser feita de forma errada - bem-vindo, escreva seu artigo. E o tópico deste artigo é como fazer animação em várias linhas de código, sem usar nenhuma biblioteca especial, praticamente "no joelho". Esse método foi testado na prática e realmente funciona. Portanto, antes de criticar e “menos”, leia novamente o que é este artigo e por quê.

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


All Articles