Сегодня я познакомлю тебя с простейшим примером анимации с использованием DirectDraw. Для этого примера я нарисовал вертолёт в нескольких положениях. Точнее сказать, я его не рисовал, а стыбрил, потому что рисовать я не умею. А изюменкой этой статьи будет то, что я покажу тебе как самому сделать искусственое отсечение картинок, не попадающих в пределы экрана.
Рис 1. Фон
На рисунке 1 показана картинка, которая состоит из трёх вертолётов. Мы загрузим её и будем последовательно менять картинку. Это создаст впечатление летящего вертолёта с вращающимися лопостями.
Если последовательно выводить эти картинки вертолёта, то будет создаваться впечатление, что лопасти крутятся и вертолёт летит. Для большей реалистичности мы будем двигать картинку по экрану, но об этом немного позже.
Чтобы не сильно мучится, загрузи пример в конце статьи, а я здесь только объясню, что происходит. Сегодняшний пример основан на примере из предыдущей статьи, где мы учились выводить на экран прозрачные картинки.
В самом начале я объявил одну запись TVertol в разделе type :
type
TVertol=record
vLeft:Integer;
vTop:Integer;
vState:Integer;
vWaitTime:Integer;
end;
Она состоит из четырёх свойств:
vLeft - будет указывать на левую позицию вертолёта.
vTop - будет указывать на верхнюю позицию вертолёта.
vState - указывает на состояние вертолёта, а именно на ту картинку, которая должна быть выведена сейчас на экран.
vWaihtTime - эта задержка между появлениями вертолёта на экране.
В разделе private объекта TForm1 я объявил ещё две переменные и одну процедуру:
FVertolImage - это поверхность, которая будет хранить картинку с вертолётом.
Vertol1 - это переменная типа структуры TVertol. Она будет хранить в себе состояние нашего пилотируемого аппарата.
InitVertol - в этой процедуре будет происходить инициализация картинки вертолёта. А именно, здесь я буду загружать картинку и выставлять значения по умолчанию. Вот как она выглядит:
procedure TForm1.InitVertol;
begin
//Загружаю картинку
FVertolImage := DDLoadBitmap(FDirectDraw, PChar('vertol.bmp'), 0, 0);
//Выставляю цвет прозрачности.
DDSetColorKey (FVertolImage, RGB(255, 0, 255));
Vertol1.vLeft:=-200;//Левая позиция = -200
Vertol1.vTop:=30; //Высота = 30
Vertol1.vState:=0; //Состояние =0
Vertol1.vWaitTime:=100; //Задержка перед вылетом = 100.
end;
Эту процедуру я вызываю самой последней в обработчике события OnCreate.
После этого я создал обработчик события OnIdle. Это событие вызывается, когда приложение свободно и ему нечего делать. Для этого я выделил компонент TApplicationEvents и на закладке Events дважды щёлкнул по этой строке. В нём я написал следующее:
procedure TForm1.ApplicationEvents1Idle(Sender: TObject;
var Done: Boolean);
begin
if FActive <> True then exit;
FormPaint(nil);
Done := False;
end;
В первой строке происходит проверка, является ли приложение сейчас активным. Если нет, то выход. Если приложение актино, то произвести прорисовку FormPaint(nil) и переменной done присвойть значение false. Если ты не сделаешь последнее действие, то процедура, больше не будет вызываться.
Переменная FActive - это простая булева переменная (boolean). Я присваиваю ей true по событию OnActivate и OnRestore:
procedure TForm1.ApplicationEvents1Activate(Sender: TObject);
begin
FActive:=true;
end;
procedure TForm1.ApplicationEvents1Restore(Sender: TObject);
begin
WindowState := wsMaximized;
FActive := True;
end;
А по событию OnDeactivate присваиваю false:
procedure TForm1.ApplicationEvents1Deactivate(Sender: TObject);
begin
Application.Minimize;
FActive := false;
end;
Всё. Теперь изменения в процедуре рисования:
procedure TForm1.FormPaint(Sender: TObject);
var
hRet : HRESULT;
bltfx : TDDBLTFX;
x1,x2:Integer;
dstR, srcR:TRect;
begin
ZeroMemory(@bltfx, SizeOf(bltfx));
bltfx.dwSize := sizeof(bltfx);
bltfx.dwFillColor := 0;
hRet := FBackGround.Blt(nil, nil, nil, DDBLT_COLORFILL or DDBLT_WAIT,
@bltfx);
if hRet = DDERR_SURFACELOST then
begin
FPrimarySurface._Restore;
FImageSurface := DDLoadBitmap(FDirectDraw, '1.bmp', 0, 0);
FTransparentImage := DDLoadBitmap(FDirectDraw, 'bart.bmp', 0, 0);
DDSetColorKey (FTransparentImage, RGB(255, 0, 255));
FVertolImage := DDLoadBitmap(FDirectDraw, PChar('vertol.bmp'), 0, 0);
DDSetColorKey (FVertolImage, RGB(255, 0, 255));
end;
FBackGround.BltFast (175, 75, FImageSurface, nil, DDBLTFAST_WAIT);
//Если свойство vWaitTime меньше 1 то рисовать
//Иначе уменьшить это свойство на 1 (это будет ниже,
//после слова else.
if Vertol1.vWaitTime<1 then
begin
//Увеличить левую позицию вертолёта на 2.
Vertol1.vLeft:=Vertol1.vLeft+2;
//Увеличить значение состояния вертолёта
//Если состояние 0, то выведится первая картинка,
//если состояние 1, то выведится вторая картинка,
//если состояние 2, то выведится треться картинка вертоля
inc(Vertol1.vState);
//Если состояние больше 2, то присвоить 0.
//Это потому что состояний три (три картинки вертолёта).
if Vertol1.vState>2 then Vertol1.vState:=0;
//Если вертолёт не виден полностью, то обрезать его
if Vertol1.vLeft<0 then X1:=Abs(Vertol1.vLeft)
else X1:=0;
if Vertol1.vLeft+200>800 then X2:=800-Vertol1.vLeft
else X2:=200;
//Создаю область источника
srcR:=Rect(X1, Vertol1.vState*80, X2, Vertol1.vState*80+80);
//Если вертолёт не виден полностью, то обрезать его
if Vertol1.vLeft<0 then X1:=0
else X1:=Vertol1.vLeft;
if Vertol1.vLeft+200>799 then X2:=799
else X2:=Vertol1.vLeft+200;
//Создаю область приёмника
dstR:=Rect(X1 ,Vertol1.vTop,X2,Vertol1.vTop+80);
//Вывожу на экран вертолёт.
FBackGround.Blt(@dstR, FVertolImage, @srcR, DDBLT_KEYSRC, nil);
//Если вертоль уже улетел, то установить левую позицию в -200
//И установить задержку в 200.
if Vertol1.vLeft>800 then
begin
Vertol1.vWaitTime:=200;
Vertol1.vLeft:=-200;
end;
end
else
Vertol1.vWaitTime:=Vertol1.vWaitTime-1;
FBackGround.BltFast(mouseX-20, mouseY-47, FTransparentImage, nil,
DDBLTFAST_WAIT or DDBLTFAST_SRCCOLORKEY);
FPrimarySurface.Flip(nil, DDFLIP_WAIT);
end;
Обрати внимание, что вертолёт вылетает чётко из-за экрана. Напомню, недостаток прошлого примера - если картинка не помещается на экран, то она не выводится полностью. В этом примере я проверяю, если вертолёт не помещается полностью, то вывожу только то, что помещается. Для этого служит следующий код:
//Если вертолёт не виден полностью, то обрезать его
if Vertol1.vLeft<0 then X1:=Abs(Vertol1.vLeft)
else X1:=0;
if Vertol1.vLeft+200>800 then X2:=800-Vertol1.vLeft
else X2:=200;
//Создаю область источника
srcR:=Rect(X1, Vertol1.vState*80, X2, Vertol1.vState*80+80);
//Если вертолёт не виден полностью, то обрезать его
if Vertol1.vLeft<0 then X1:=0
else X1:=Vertol1.vLeft;
if Vertol1.vLeft+200>799 then X2:=799
else X2:=Vertol1.vLeft+200;
//Создаю область приёмника
dstR:=Rect(X1 ,Vertol1.vTop,X2,Vertol1.vTop+80);
Попробую объяснить, как происходит обрезание. Если левая позиция вертолёта -50, то он не помещается полность. Ширина картинки вертолёта 200 пикселов. Это значит, что первые 50 пикселов будут за пределами экрана. Для обрезания нужна следующая проверка:
if Vertol1.vLeft<0 then X1:=Abs(Vertol1.vLeft)
else X1:=0;
Если левая позиция меньше 0, то в переменную X1 записать абсолютное значение переменной Vertol1.vLeft (абсолютное значение - значит без знака). Результат - в X1 будет число 50.
Далее идёт следующая проверка:
if Vertol1.vLeft+200>800 then X2:=800-Vertol1.vLeft
else X2:=200;
Если левая позиция + ширина картинки (200) больше 800 (ширины экрана), то обрезать правую позицию картинки. У нас это значение меньше, значит выполнится X2:=200. Результат в X2 будет 200.
Рис 2. Вертоль
После этого я создаю структуру, которая показывает область копирования из источника: srcR:=Rect(X1, Vertol1.vState*80, X2, Vertol1.vState*80+80). Результат, в srcR будет (50, 0, 200, 80). Это при условии, что Vertol1.vState равно нулю. Если равно 1, то будет выведена вторая картинка, потому что SrcR будет равно (50, 80, 200, 160). Это значит, что нам надо скопировать картинку, начиная с 50 позиции слева, нулевой сверху и по 200 справа и 80-ю снизу. Посмотри на рисунок 2. На нём закрашена красным цветом та область, которая не будет копироваться.
После этого, я точно так же вычисляю область - куда нужно копировать.
//Следующая проверка присвоит X1 значение 0, потому что
//Vertol1.vLeft меньше 0 (мы же выбрали значение 50)
if Vertol1.vLeft<0 then X1:=0
else X1:=Vertol1.vLeft;
//Следующая проверка присвоит X2 значение -50+200=150.
if Vertol1.vLeft+200>799 then X2:=799
else X2:=Vertol1.vLeft+200;
//Следующая строка создаст запись dstR с параметрами (0,0,150,80).
dstR:=Rect(X1 ,Vertol1.vTop,X2,Vertol1.vTop+80);
Рис 3. Вертоль
Запись - (0,0,150,80) означает, что вертоль будет нарисован начиная с нулевой левой позиции. Посмотри на рисунок 3. Ты увидишь, как это будет выглядить.
Как видишь, на результате картинка выведена обрезанной в левую позицию экрана. Как только вертоль вылетит на экран полностью, он будет просто копироватся без всяких отсечений.