Хабрахабр

Lazarus — пишем компонент для анимации спрайтов

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

В одесской школе ученики 8-го класса на уроках информатики используют бесплатную кроссплатформенную среду разработки Lazarus (официальный сайт), внешне и внутренне очень напоминающую любимый многими Delphi, использующую версию Object Pascal под названием Free Pascal и в действительности сильно упрощающую процесс вхождения в программирование.

Практически все дети, которых я пытался учить программированию, с первого занятия хотят написать игру. Но детям неинтересно писать программу для вычисления силы тяжести по непонятной им пока формуле F=mg. К счастью, Lazarus прекрасно подходит и для написания несложных игр.

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

Для отображения веселого графического контента вместо сухого делового набора стандартных компонентов в Lazarus (как и в Delphi) есть 3 компонента на вкладке Additional:

  • TImage (отображение картинки из произвольного файла);
  • TShape (отображение одного из нескольких заранее заданных графических примитивов);
  • TPaintBox (отображение холста, на котором можно рисовать программно).

Самое эффектное для школьника — загрузить небольшой спрайт в TImage и написать программу для перемещения его по экрану — по событиям мыши/клавиатуры, автоматически в цикле или автоматически по событию от таймера.

И можно ли сделать сделать так, чтобы он смотрел не постоянно на нас, а поворачивался в сторону, совпадающую с направлением движения? Как только это начинает работать, у школьника возникает следующий законный вопрос: а нельзя ли сделать так, чтобы персонаж двигался?

И многие персонажи заранее разработаны в несколько проекций и несколько кадров анимации (как, например, вот на этом сайте). В Сети можно найти большое количество готовых изображений для использования при разработке игр.

Вот пример изображения, где спрайты расположены в виде таблицы, у которой каждая строка соответствует определенной проекции, а каждый столбец — определенной фазе анимации:

Зачем так много картинок?

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

К сожалению, компонент TImage, входящий в стандартную поставку Lazarus (и Delphi), не позволяет показывать произвольный фрагмент изображения: изменяя его свойства, мы можем заставить его показывать только изображение целиком, левый верхний угол или центральную его часть. Для отображения произвольного фрагмента изображения, заданного смещением и размерами по обеим осям, нужен какой-то другой компонент. Но, как выяснилось, сделать его самостоятельно в Lazarus — совсем несложно!

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

В качестве инструкции по созданию компонентов я воспользовался официальным руководством.

Я только остановлюсь на некоторых моментах. Там все написано достаточно подробно, дублировать не имеет смысла.

Стандартный Project Wizard не предлагает нам создать package, и чтобы как-то получить доступ к редактору, выбираем «New Project» (в русской версии — «Новый проект») 1.

и затем «Application» (в русской версии — «Приложение»):

2. Действуя далее по инструкции, в меню «Package» (в русской версии — «Пакет») выбираем верхний пункт «New package...» (в русской версии — «Новый пакет...»), выбираем имя файла и путь для сохранения. Я назвал свой новый пакет «Game» и разместил его в отдельной папке с тем же названием:

Я создал отдельную папку Lazarus/Cmp в расчете на то, что у меня может появиться несколько разных пакетов с компонентами, и уже в этой папке создал папку «Game».

Если все сделано правильно, на экране должно появиться окно нового (пока пустого) пакета.

Действуя дальше опять же по инструкции, для создания нового компонента в окне пакета нажимаем кнопку «Add» (в русской версии — «Добавить») и в выпадающем списке выбираем «New Component» (в русской версии — «Новый компонент»): 3.

В качестве класса-предка указываем TCustomImage — этот класс фактически используется для реализации компонента TImage, но отличается от него тем, что не содержит published properties и позволяет нам самим определить набор свойств, который будет доступен в дизайнере для нашего компонента.

Что такое published properties?

Промежуточные классы не объявляют ничего в этой секции, оставляя возможность программисту самому вынести туда то, что он сочтет нужным. Для тех, кто этого не знает, уточню, что published — это раздел класса (наподобие public), в котором описываются новые или просто указываются унаследованные свойства, которые должны быть доступны в визуальном редакторе свойств на этапе разработки программы. Часть из этих свойств нам нужно спрятать, поэтому мы также унаследуем наш новый компонент от TCustomImage и выведем в published только то, что не противоречит логике нашего компонента. Так, класс TImage не добавляет никакой функциональности, а только помещает в раздел published ряд свойств, унаследованных от родителя TCustomImage.

Пиктограмма (иконка) для компонента

Хорошим стилем было бы рисовать персональную иконку для каждого нового компонента, но так как наша задача — показать, как это все просто, мы оставим это поле пустым, что приведет к отображению на панели инструментов стандартной иконки, используемой в Lazarus/Delphi для всех самодельных компонентов.
Кстати, упомянутая выше инструкция содержит отдельный раздел, посвященный созданию иконок для компонентов — это для тех, кого не устраивает «дефолтная» иконка.

Заполнив все поля, нажимаем кнопку «Create New Component» (в русской версии — «Создать новый компонент»).

Добавляем код в новый компонент

Сразу после создания нового компонента его исходный код получается примерно таким:

unit ImageFragment; {$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.

Как и следовало ожидать, объявление класса абсолютно пусто, а имплементация вообще отсутствует. Все, что есть — функция регистрации компонента на вкладке «Game».

Приступим! Нам нужно добавить несколько унаследованных published properties, создать два своих и переопределить одну виртуальную функцию.

В секции импорта нам понадобятся два дополнительных модуля: ExtCtrls и LCLProc — добавляем их в раздел uses: 0.

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

1. Добавляем список published properties, полностью аналогичный компоненту TImage, за исключением нескольких properties, позволяющих изменить масштаб и позицию изображения:

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;

Для пущей убедительности я не удалил, а закомментировал те properties, которые есть в компоненте TImage, но будут мешать в нашем новом компоненте TImageFragment.

Добавляем в объявление класса два новых properties для задания смещения изображения по горизонтали и по вертикали: 2.

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;

и не забываем добавить в имплементацию класса две объявленных процедуры:

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. Переопределяем виртуальную функцию DestRect:

public function DestRect: TRect; override;

и добавляем ее реализацию в имплементацию класса:

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

Компилируем пакет и пересобираем Lazarus

1. В окне пакета нажимаем кнопку «Compile» (в русской версии — «Компилировать»). Если все сделано правильно, в окне сообщений появится зеленая надпись об успешной компиляции, если нет — надпись будет желтой или красной.

В том же окне нажимаем на кнопку «Use» (в русской версии — «Использовать») и в выпадающем меню выбираем второй пункт «Install» (в русской версии — «Установить»). 2. Программа предложит пересобрать и перезапустить IDE — соглашаемся:

После перезапуска на панели инструментов появится новая вкладка «Game», а на ней — иконка для нашего нового компонента. 3.

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

Если тема окажется интересной читателям, я могу дополнить статью разделом о том, как использовать такой компонент — за 5 минут создать окно, в котором анимированный персонаж будет двигаться в разные стороны и поворачиваться в сторону направления движения. А потратив чуть больше времени, можно сделать, к примеру, футбольное поле с парой футболистов, управляемых с клавиатуры. И уже для самых отчаянных — можно писать разные алгоритмы управления футболистами (боты) и устраивать между ними соревнования!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть