Главная » Хабрахабр » Lazarus — простая анимация при помощи компонента TImageFragment

Lazarus — простая анимация при помощи компонента TImageFragment

Вместо предисловия

В моей недавней статье Lazarus — пишем компонент для анимации спрайтов я описал процесс создания простого компонента TImageFragment, позволяющего отображать заданный фрагмент изображения.

Продолжая выбранную тему, в этой статье я хочу показать, как легко можно сделать анимацию спрайтов в среде разработки Lazarus (официальный сайт) при помощи этого компонента.

При таком подходе отдельные кадры анимации в разных проекциях размещаются на одном изображении, а компонент для отображения спрайта показывает только один выбранный фрагмент этого изображения, используя свойства OffsetX и OffsetY (смещение левого верхнего угла фрагмента изображения по горизонтали и по вертикали).

Множество таких готовых изображений можно найти в Сети — например, вот на этом сайте.

Выбираем (и подготавливаем) изображение

Для своего примера я выбрал вот это изображение:

— очень уж выразительно эта птица Феникс машет крыльями.

Меняя только OffsetX, можно заставить птицу махать крыльями, а для смены проекции достаточно только менять OffsetY. Как можно заметить, каждая строка содержит по 4 кадра для каждой из 4-х проекций. Такое разделение кадров по строкам очень упрощает программирование анимации.

К сожалению, прямое использование этого изображение огорчило нас артефактами: некоторые кадры изображения размещены так, что их края попадают на соседние кадры, и во время анимации на краях спрайта мелькают желтые штрихи. Размер этого изображения — 384х384, а размер каждого кадра — 96х96.

Все, что нужно было сделать — удалить выступающие пиксели изображений в тех местах, где они попадают на соседний кадр. Для устранения этих дефектов я использовал бесплатный кросс-платформенный графический редактор GIMP (официальный сайт).

Исправленный файл выглядит вот так:

— невооруженным глазом отличия незаметны, но второй вариант работает без артефактов.

Создаем новый проект

1. Создаем новый проект типа «Приложение».

По умолчанию IDE создает проект с названием «project1», в котором сразу создается один программный модуль с названием «unit1», описывающий класс с названием "«TForm1» и объявляющий его экземпляр с названием «Form1».

Я считаю хорошим стилем переименовывать все такие объекты, присваивая им осмысленные имена, отражающие роль или назначение объекта. Вообще при создании новых объектов IDE присваивает им подобные имена, состоящие из названия типа объекта и порядкового номера.

Так, наш проект будет называться не «project1», а «Phoenix» — по названию выбранного спрайта.

Сохраняем наш новый проект. 2.

В процессе сохранения мы указываем каталог для сохранения (при необходимости — тут же его создаем), затем имя файла проекта и имя файла программного модуля. Желательно сохранять каждый проект в отдельный каталог с именем, совпадающим с названием проекта. Я создал папку «Phoenix» и сохранил туда файл проекта («Phoenix.lpi» вместо предложенного «project1.lpi») и файл программного модуля («UnitMain.pas» вместо предложенного «unit1.pas»).

Нюанс с регистром символов

С файлом проекта этого не происходит, имя файла сохраняет оригинальный регистр символов. Версия Lazarus для Windows приводит название файла программного модуля к нижнему регистру: «unitmain.pas», но программное название модуля сохраняет оригинальный регистр символов: «unit UnitMain;».

3. Переименовываем форму и меняем ее заголовок.

Изменяем свойство Name формы на «FormMain», при этом название класса изменится на «TFormMain». Только что созданная форма называется «Form1» (свойство Name), является экземпляром класса «TForm1» и содержит заголовок «Form1» (свойство Caption).

Свойство Caption меняем на «Phoenix», чтобы в заголовке окна отображалось название проекта.

В итоге у меня получился вот такой текст модуля «unitmain.pas»: 4.

unit UnitMain; {$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. Компилируем, запускаем проект (клавиша ):

Помещаем спрайт на форму

Предполагая, что у вас уже установлен компонент TImageFragment, описанный в моей предыдущей статье Lazarus — пишем компонент для анимации спрайтов, выбираем на палитре компонентов вкладку «Game» и добавляем на форму компонент «TImageFragment».

Кроме этого, меняем также следующие свойства нового объекта: Используя свойство Picture, загружаем в компонент изображение (исправленный вариант птицы Феникс).

  • свойства Height и Width устанавливаем в значение 96
  • свойства Left и Top ставим в 0 (удобно для совпадения с моими скриншотами)
  • свойство Name меняем с неудобного «ImageFragment1» на простое и понятное «Sprite»

Если все сделано правильно, компонент будет показывать первый кадр изображения:

В тексте модуля UnitMain при этом произойдут небольшие изменения:
— в раздел uses добавится модуль ImageFragment

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

— в объявлении класса появится новый объект

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

Добавляем анимацию — махи крыльями

1. Добавляем на форму новый компонент класса TTimer.

Разместить его можно в любом удобном месте формы, так как в работающем приложении он не отображается. Этот компонент находится на вкладке «System» палитры компонентов.

Переименовываем добавленный объект. 2.

Часто удобно давать объектам такие имена, состоящие из двух частей: первая отражает класс объекта, а вторая — его назначение. Новый объект автоматически получает имя «Timer1», но мы переименовываем его в «TimerLive».

Меняем свойство Interval с 1000 на 100. 3.

В дальнейшем это свойство можно менять для замедления или ускорения маха крыльев — на усмотрение программиста. Пусть кадры этой анимации сменяют друг друга каждые 100 миллисекунд, то есть 10 раз в секунду.

Добавляем обработчик события OnTimer. 4.

В результате этого действия IDE сама добавит в объявление класса формы новую процедуру, в свойства объекта — ссылку на эту процедуру, а в раздел implementation будет добавлено тело новой процедуры (и курсор будет помещен внутри этой новой процедуры, между ключевыми словами begin и end). Проще всего это сделать двойным щелчком мыши на значке нового объекта TimerLive.

Добавляем в новую процедуру одну строку кода. 5.

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

В результате этих действий объявление класса должно выглядеть примерно так:

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

А новая процедура — обработчик события OnTimer должна выглядеть примерно так:

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

После компиляции и запуска приложения можно наблюдать, как птица Феникс машет крыльями.

Операция mod — получение остатка от деления — предотвращает выход смещения за пределы размера изображения, и в результате за 4-м кадром снова следует 1-й. Происходит это потому, что обработчик события таймера каждые 100 миллисекунд циклически меняет смещение отображаемого фрагмента, и выбранный кадр смещается по горизонтали, последовательно отображая 4 кадра верхней строки загруженного изображения.

Добавляем перемещение спрайта по окну

1. Добавляем в раздел uses модуль Math

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

2. Добавляем в объявление класса новую переменную и константу.

Для сохранения вектора перемещения спрайта по окну добавим переменную типа TPoint

private FVector: TPoint;

3. Там же объявляем константу для задания модуля скорости перемещения

const Speed = 10;

4. Добавляем на форму еще один компонент класса TTimer.

Напоминаю: этот компонент находится на вкладке «System» палитры компонентов.

Переименовываем добавленный объект. 5.

Назначение второго таймера — управлять движением спрайта. Новый объект опять автоматически получает имя «Timer1», и мы переименовываем его — на этот раз в «TimerMove». Я не стал оба процесса (анимацию и перемещение) подвязывать на один и тот же таймер для того, чтобы каждый из таймеров можно было настраивать отдельно — например, замедлить частоту махов крыльями без замедления перемещения, и так далее.

Меняем свойство Interval с 1000 на 100. 6.

В дальнейшем это свойство также можно менять для замедления или ускорения частоты отрисовки факта перемещения спрайта. Пусть этот таймер тоже срабатывает каждые 100 миллисекунд, то есть 10 раз в секунду.

Добавляем обработчик события OnTimer. 7.

Как и в прошлый раз, в результате этого действия IDE сама добавит в объявление класса формы новую процедуру, в свойства объекта — ссылку на эту процедуру, а в раздел implementation будет добавлено тело новой процедуры (и курсор будет помещен внутри этой новой процедуры, между ключевыми словами begin и end). Для разнообразия на этот раз предлагаю сделать это двойным щелчком мыши напротив события OnTimer на вкладке «События» нового объекта TimerMove.

Добавляем в новую процедуру две строки кода. 8.

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

Использование функций Max() и Min() предотвращает выход спрайта за пределы формы (главного окна приложения).
Именно для использования этих функций мы и подключили в раздел uses модуль Math.

Добавляем обработчик события OnKeyPress. 9.

Двойным щелчком мыши по пустому значению обработчика события создаем и присваиваем новую процедуру — обработчик события. Выделяем форму (щелкаем мышью по серому прямоугольнику макета окна за пределами всех добавленных компонентов) и на вкладке «События» находим событие OnKeyPress.

Добавляем в новую процедуру несколько строк кода. 10.

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);

В результате этих действий объявление класса должно выглядеть примерно так:

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;

А новые процедуры — обработчики событий OnTimer и OnKeyPress должны выглядеть примерно так:

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;

После компиляции и запуска приложения можно перемещать птицу Феникс по экрану при помощи клавиш «a», «w», «s», «d» и останавливать ее клавишей «пробел».

Используем разные проекции спрайта

1. Добавляем в конец процедуры 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;

Изменение свойства OffsetY в зависимости от вектора перемещения приводит к тому, что изображение как бы поворачивается в сторону направления движения.

Весь текст модуля 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.

Вместо послесловия

Этот простой пример не претендует на высокие рейтинги по быстродействию или юзабилити. Если кто-то, как в прошлой статье, хочет рассказать в комментариях, что анимацию нужно делать не так — добро пожаловать, напишите свою статью. А тема этой статьи — как сделать анимацию в несколько строк кода, не используя никаких специальных библиотек, практически «на коленке». Этот метод проверен на практике, он реально работает, так что прежде чем критиковать и «минусовать», пожалуйста, еще раз перечитайте, о чем и для чего эта статья.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Интересные факты об истории Китайской лунной программы и космической миссии «Чанъэ-4»

Многое скрыто за заборами полигонов и стенами лабораторий Китайской академии космических технологий при реализации лунных научно-исследовательских космических программ, но часть информации потом все равно любезно предоставляется в открытый доступ.В продолжении этой публикации. Ранее опубликованные материалы о «Чанъэ-4»: Краткая Китайская история ...

Путеводитель по программе JPoint 2019

Благо, всего месяц остался до JPoint 2019 — международной Java-конференции, которая пройдёт в начале апреля в Москве. Последний месяц зимы подходит к концу, и просыпается здоровое желание сходить на какую-нибудь большую Java-конференцию. Программа почти стабилизировалась, и настало время раскрыть все ...