Программирование анимации в Delphi
В этом примере показано, как, объеденив классы Delphi 5
с функциями Win32 GDI, можно добиться анимации упрощенного
избражения эльфа.
Исходные тексты можно взять здесь.
Запустить пример.
unit MainFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs, Menus, Stdctrls;
{$R SPRITES.RES } {Привязка растровых изображений к
исполняемому файлу.}
type
TSprite = class
private
FWidth: integer;
FHeight: integer;
FLeft: integer;
FTop: integer;
FAndImage, FOrImage: TBitMap;
public
property Top: Integer read FTop write FTop;
property Left: Integer read FLeft write FLeft;
property Width: Integer read FWidth write FWidth;
property Height: Integer read FHeight write FHeight;
constructor Create;
destructor Destroy; override;
end;
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
BackGnd1, BackGnd2: TBitMap;
Sprite: TSprite;
GoLeft, GoRight,GoUp,GoDown: boolean;
procedure MyIdleEvent(Sender: TObject;
var Done: Boolean);
procedure DrawSprite;
end;
const
BackGround = 'BACK2.BMP';
var
MainForm: TMainForm;
implementation
{$R *.DFM}
constructor TSprite.Create;
begin
inherited Create;
{ Создание растров для хранения изображений эльфа,
которые будут использованы при выполнении операции
AND/OR (И/ИЛИ) для содания анимации }
FAndImage := TBitMap.Create;
FAndImage.LoadFromResourceName(hInstance, 'AND');
FOrImage := TBitMap.Create;
FOrImage.LoadFromResourceName(hInstance, 'OR');
Left := 0;
Top := 0;
Height := FAndImage.Height;
Width := FAndImage.Width;
end;
destructor TSprite.Destroy;
begin
FAndImage.Free;
FOrImage.Free;
inherited Destroy;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
// Создание исходного фонового изображения
BackGnd1 := TBitMap.Create;
with BackGnd1 do
begin
LoadFromResourceName(hInstance, 'BACK');
Parent := nil;
SetBounds(0, 0, Width, Height);
end;
// Создание копии фонового изображения
BackGnd2 := TBitMap.Create;
BackGnd2.Assign(BackGnd1);
// Создание изображения эльфа
Sprite := TSprite.Create;
// Инициализация переменных направления
GoRight := true;
GoDown := true;
GoLeft := false;
GoUp := false;
{ Установка события приложения OnIdle равным значению
MyIdleEvent, с которого начнется движение эльфа }
Application.OnIdle := MyIdleEvent;
// Установка высоты и ширины области клиента формы
ClientWidth := BackGnd1.Width;
ClientHeight := BackGnd1.Height;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
// Освобождение всех объектов, созданных в конструкторе
формы FormCreate()
BackGnd1.Free;
BackGnd2.Free;
Sprite.Free;
end;
procedure TMainForm.MyIdleEvent(Sender: TObject;
var Done: Boolean);
begin
DrawSprite;
{ Разрешение вызова события OnIdle даже при отсутствии
сообщений в очереди сообщений приложения }
Done := False;
end;
procedure TMainForm.DrawSprite;
var
OldBounds: TRect;
begin
// Сохранение границ эльфа в объекте OldBounds
with OldBounds do
begin
Left := Sprite.Left;
Top := Sprite.Top;
Right := Sprite.Width;
Bottom := Sprite.Height;
end;
{ Теперь изменяем границы эльфа, чтобы он двигался в одном
направлении, или изменяем направление при
соприкосновении с границами формы }
with Sprite do
begin
if GoLeft then
if Left > 0 then
Left := Left - 1
else begin
GoLeft := false;
GoRight := true;
end;
if GoDown then
if (Top + Height) < self.ClientHeight then
Top := Top + 1
else begin
GoDown := false;
GoUp := true;
end;
if GoUp then
if Top > 0 then
Top := Top - 1
else begin
GoUp := false;
GoDown := true;
end;
if GoRight then
if (Left + Width) < self.ClientWidth then
Left := Left + 1
else begin
GoRight := false;
GoLeft := true;
end;
end;
{ Стираем исходное изображение эльфа на фоне BackGnd2
путем копирования прямоугольника из фона BackGnd1 }
with OldBounds do
BitBlt(BackGnd2.Canvas.Handle, Left, Top, Right, Bottom,
BackGnd1.Canvas.Handle, Left, Top, SrcCopy);
{ Теперь рисуем эльфа на "внеэкранном" растре, тем самым
избавлясь от мерцания }
with Sprite do
begin
{ Создадим черное пятно с силуэтом эльфа с помощью
операции логического И, выполненной над растрами
FAndImage и BackGnd2 }
BitBlt(BackGnd2.Canvas.Handle, Left, Top, Width, Height,
FAndImage.Canvas.Handle, 0, 0, SrcAnd);
// Выполним заливку черного пятна исходными цветами эльфа
BitBlt(BackGnd2.Canvas.Handle, Left, Top, Width, Height,
FOrImage.Canvas.Handle, 0, 0, SrcPaint);
end;
{ Копируем эльфа в его новой позиции на канву формы.
При этом используется прямоугольник, который немного
больше, чем нужно для фигуры эльфа. Тем самым мы
добиваемся эффективного стирания эльфа путем его
перезаписи, после чего рисуем нового эльфа в новой
позиции с помощью доного вызова функции BitBlt }
with OldBounds do
BitBlt(Canvas.Handle, Left-2, Top-2, Right+2, Bottom+2,
BackGnd2.Canvas.Handle, Left-2, Top-2, SrcCopy);
end;
procedure TMainForm.FormPaint(Sender: TObject);
begin
// Рисуем фоновой изображение при закрашивании формы
BitBlt(Canvas.Handle, 0, 0, ClientWidth, ClientHeight,
BackGnd1.Canvas.Handle, 0, 0, SrcCopy);
end;
end.
Как это работает. Анимационный проект состоит из фонового
изображения и нарисованного на нем эльфа в виде летающего
блюдца, которое перемещается в пределах области клиента
фона. Фон представлен растровым изображением разбросанных
по небу звезд (рис 1).
Рис 1.
Эльф составлен из двух растров размером 64x32. О них речь
пойдет ниже, а пока рассмотрим, что происходит в программе.
В приведенном модуле определяется класс TSprite, который
содержит поля, предназначенные для хранения позиций эльфа на
изображении фона, и два объекта типа TBitmap для хранения
растровых изображений эльфа. Конструктор TSprite.Create
создает оба экземпляра класса TBitmap и загружает их
реальными растрами. Оба растровых изображения эльфа и
фоновый растр содержатся в файле ресурсов, который
привязывается к проекту путем включения в основной модуль
следующей инструкции: { $R SPRITES.RES }.
После загрузки растра устанавливаются границы изображения
эльфа. Деструктор TSprite.Destroy освобождает оба экземпляра
растра. Главная форма содержит два объекта типа TBitmap,
объект TSprite и индикаторы напрвлений, задающие линию
движения эльфа. Кроме того, в главной форме определены два
метода: MyIdleEvent(), служащий обработчиком событий
Application.OnIdle, и DrawSprite(), предназначенный для
рисования изображения эльфа.
Обработчик событий FormCreate() создает оба экземпляра
класса TBitmap и загружает каждый одним и тем же растровым
изображением (зачем - разберемся чуть ниже). Затем создается
экземпляр класса TSprite, устанавливаются значения
индикаторов направлений и обработчику событий
Application.OnIdle назначается метод MyIdleEvent(). Наконец,
обработчик событий формы FormCreate() изменяет размеры формы
в соответствии с размерами фонового изображения.
Метод FormPaint() выполняет рисование на канве фона
BackGnd1.
Метод FormDestroy() освобождает экземпляры классов
TBitmap и TSprite.
Метод MyIdleEvent() вызывает метод DrawSprite(),
который перемещает и рисует эльфа на существующем фоне.
Метод MyIdleEvent() вызывается, когда приложение
находится в состоянии ожидания, т.е. когда пользователь
не выполняет никаких действий, на которые приложению
следовало бы отреагировать.
Метод DrawSprite() изменяет расположение эльфа на
изображении фона. Для этого требуется выполнить немало
инструкций - ведь сначала нужно стереть старое изображение
эльфа, а затем нарисовать его на новом месте, сохраняя цвет
фона вокруг реального изображения эльфа. Кроме того, метод
DrawSprite() должен выполнить эти действия без мерцания.
Для достижения поставленных целей процесс рисования
выполняется на "внеэкранном" растре BackGnd2. Растры
BackGnd2 и BackGnd1 являются точными копиями фонового
изображения, однако BackGnd1 никогда не модифицируется
(поэтому его можно назвать чистой копией фона). По
завершении рисования модифицированная область растра
BackGnd2 копируется на канву формы. Это позволяет за одно
обращение к функции BitBlt() выполнить как стирание на канве
формы, так и рисование эльфа в новой позиции. Какие же
опреции выполняются с растром BackGnd2?
Во-первых, из BackGnd1 в BackGnd2 копируется
прямоугольный участок, превышающий по размерам область,
занимаемую самим эльфом. Тем самым гарантируется стирание
изображения эльфа с растра BackGnd2. После этого растр
FAndImage копируется в BackGnd2 на его новой позиции с
помощью поразрядной опреции AND (логическое И). Это
приводит к созданию черного пятна с силуэтом эльфа, но с
сохранением цветов в области растра BackGnd2, окружающей
черный силуэт. Растр FAndImage показан на (рис 2).
Рис 2.
На рис 2 эльф представлен черными пикселами, а
изображение вокруг эльфа состоит из белых пикселей. Черный
цвет имеет значение, равное 0, а белый - 1. В табл. 1 и
2 приведены результаты выполнения опреции AND с белым и
черным цветами.
Табл. 1 "Опреция AND с черным цветом"
Фон
|
Значение
|
Цвет
|
BackGnd2
|
1001 |
Некоторый цвет |
FAndImage |
0000 |
Черный |
Результат
|
0000
|
Черный
|
Табл. 2 "Операция AND
с белым цветом"
Фон
|
Значение
|
Цвет
|
BackGnd2
|
1001 |
Некоторый цвет |
FAndImage |
1111 |
Белый |
Результат
|
1001
|
Некоторый цвет
|
Эти таблицы показывают, как выполнение операции
логического И приводит к зачернению области, занимаемой
эльфом на растре BackGnd2. В табл. 1 столбец "Значение"
представляет цвет пискселя. Если пиксель на растре BackGnd2
содержит некоторый произвольный цвет, то объединение этого
цвета с черным при использовании оператора AND заставит этот
пиксель полностью почернеть. Аналогичная операция,
выполненная над тем же цветом и абсолютно белым "коллегой"
никак не отразится наисходном цвете, как видно в табл. 2.
А поскольку цвет фона, на котором находится эльф в растре
FAndImage, был белым, то пиксели на растре BackGnd2
копируются без изменения своих цветов. После копирования
растра FAndImage в объект BackGnd2 растр FOrImage должен
быть скопирован в то же самое место растра BackGnd2, чтобы
заполнить черное пятно, созданное объединением растра
FAndImage с реальными цветами эльфа. Растр FOrImage таже
имеет прямоугольник, окружающий реальное изображение эльфа.
И вновь мы сталкиваемся с задачей получения цветов эльфа для
растра BackGnd2 и одновременным сохранением цветов этого
растра в области, окружающей эльфа. Это достигается
объединением растров FOrImage и BackGnd2 с использованием
оператора OR (лигическое ИЛИ). Растр FOrImage показан на
(рис 3).
Рис 3.
Обратите внимание на то, что область, окружающая
изображение эльфа, окрашена в черный цвет. В табл. 3
показаны результаты выполнения операции ИЛИ с растрами
FOrImage и BackGnd2. Из табл. 3 следует, что если растр
BackGnd2 содержит произвольный цвет, то после операции
лигического сложения с черным цветом останется тот же цвет
растра BackGnd2.
Табл. 3 "Операция OR с черным цветом"
Фон
|
Значение
|
Цвет
|
BackGnd2
|
1001 |
Некоторый цвет |
FOrImage |
0000 |
Черный |
Результат
|
1001
|
Некоторый цвет
|
Напомним, что все рисование выполняется на "внеэкранном"
растре. По завершении рисования достаточно только одного
обращения к функции BitBlt(), чтобы стереть и скопировать
изображение эльфа. В описанном способе создания анимации
нет ничего необычного. Вы можете сами расширить
функциональные возможности класса, связанные с перемещением
и рисованием на родительской канве.
|