В статьях «Работа с несколькими спрайтами» и «Экранный буфер» (см. «Мир ПК», №2/02, с. 107 и №3/02, с. 100) было рассказано, как выводить на экран одновременно несколько спрайтов и добиться при этом быстрой и корректной (без помех на экране) работы. Сведений об этом в общем-то вполне достаточно, чтобы написать простейшую игру. Однако если спрайты не будут изменяться во времени, то она получится непрофессиональной. Так, у фигурок при перемещении должны двигаться хотя бы ноги, у деревьев — шевелиться листва и т. п. Собственно, в данном случае под анимацией и понимается «оживление».

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

  • левая нога стоит на земле, правая поднята и согнута в колене;
  • левая нога осталась сзади, правая ушла вперед;
  • правая нога стоит на земле, левая поднята и согнута в колене;
  • правая нога осталась сзади, левая ушла вперед.

Отдельные фазы анимации можно поместить в разные файлы, хотя это не совсем правильно — файлов получится слишком много. Для каждого персонажа или объекта лучше собрать изображения в один файл. Если расположить их вертикально друг под другом, как кадры на кинопленке, то будет удобнее считывать файлы. Когда же кадры размещены горизонтально слева направо или в виде матрицы MЁN, при чтении файла придется перекомпоновывать строчки, чтобы они правильно легли в массивы.

Итак, с помощью редактора Paint создадим рисунок размером 20x80 точек, при этом на каждую из четырех фаз анимации приходится по 20 строк растра. Для работы потребуются как минимум два файла. Назовем их sprt03.bmp и sprt04.bmp.

Теперь нужно не просто добавить в модуль sprites новые процедуры, а изменить уже существующие. Чтобы не запутаться, скопируйте все тексты программ в новый каталог и туда же «положите» новые файлы с рисунками. Если перемещением спрайта (т. е. вычислением его координат) управляет основная программа, то, по-видимому, «шевелиться» спрайт должен уже самостоятельно. Для этого в новом модуле sprites необходимо использовать функции измерения времени — добавим в директиву uses ссылку на модуль timer18. Далее опишем в модуле новую константу AnimTime, указывающую, сколько 1/18 долей секунды будет длиться каждая фаза.

AnimTime = 3; {время показа одного
 кадра анимации}

Полный цикл анимации из четырех кадров будет занимать две трети секунды (12/18 = 2/3).

Подкорректируем также описания спрайтовых типов таким образом, чтобы они хранили информацию не об одной фазе анимации, а о четырех (см. листинг). Здесь следует учесть, что если раньше на изображение одного спрайта требовалось одно поле в 400 байт, то теперь нужно хранить уже четыре поля — по одному для каждой фазы анимации, т. е. на спрайт приходится 1,6 Кбайт. С учетом размера доступной памяти можно заключить, что таких спрайтов должно быть не больше 200—250.

NumSprites = 200; 
            {число спрайтов}

Чтобы предоставить спрайту необходимый объем памяти, а также запомнить нужную фазу анимации, следует внести изменения в процедуры создания, отображения и уничтожения спрайта. Номер последней фазы целесообразно вычислять одновременно с определением положения спрайта, и потому внесем нужные изменения. Фрагменты модуля sprites с выполненными коррективами показаны в листинге.

Итак, осталось лишь отредактировать названия используемых файлов в основной программе, и можно ее запускать.

NameSprt : array[1..2{NumSprites}]of string =
(?sprt03.bmp?,?sprt04.bmp?);
{имена файлов спрайтов}

Если назвать новые файлы так же, как и старые, то в основной программе ничего изменять не придется, кроме максимального количества спрайтов, но тут уж ничего не поделаешь. Это как раз тот идеал, к которому и нужно стремиться: изменения в одном модуле не должны требовать переработки в других или основной программе. Когда приходится часто вносить такие изменения, то, видимо, либо декомпозиция программы выполнена неправильно, либо плохо продумана интерфейсная часть модулей.

Вы в полной мере насладитесь открывшимися возможностями, если посмотрите, как преобразится изображение при изменении значения AnimTime.

Листинг
unit sprites;
interface
uses  bmpread,timer18;
const
Xsize = 20;                {размеры спрайта,
 точек}
Ysize = 20;
TransparentColor = $FF;         {"прозрачный"
 цвет}
AnimTime = 3;  {время показа одного кадра
 анимации}
type
SpriteArrayType =
array[0..Ysize-1,0..Xsize-1]of byte;
{массив равный по размеру спрайту}
SpriteAnimArrayType = array[0..3]of
 SpriteArrayType;
SpriteType = record
x,y  : word;         {текущие координаты
 спрайта}
dx,dy : integer;   {приращения координат
 спрайта}
Img  : ^SpriteAnimArrayType;
{для массива с изображением спрайта}
OldTime : longint;         {время изменения
 фазы}
Phase : word;                     {фаза
 анимации}
end;
ScreenType = array[0..199,0..319]of byte;
{для экрана}
{...}
procedure PutSprite(Sprite:SpriteType);
{вывод спрайта на экран}
var
i,j    : word;      {переменные цикла}
begin
for j := 0 to Ysize-1 do
for i := 0 to Xsize-1 do
with Sprite do
if Img^[phase,j,i] <> TransparentColor then
{ставим только точки,}
{цвет которых отличается от "прозрачного"}
Scr^[j+y,i+x] := Img^[phase,j,i];
end;
procedure CreateSprite(s:string;
 x,y,dx,dy:integer;
var Sprite:SpriteType);  {"создание" спрайта}
var
f : file;             {файл с изображением
 спрайта}
begin
getmem(Sprite.Img,sizeof(SpriteAnimArray
Type));
{выделяем память для спрайта}
Readbmp(@(Sprite.Img^),Xsize,Ysize*4,@p,s);
Sprite.x := x;
Sprite.y := y;        { задаем начальные
 значения }
Sprite.dx := dx;      {  координат
 и приращений   }
Sprite.dy := dy;
Sprite.OldTime := Clock;            {текущее
 время}
Sprite.Phase := random(4);{случайное
 значение фазы}
end;
procedure DestroySprite(Sprite:SpriteType);
{"уничтожение" спрайта}
begin
{ возвращаем память }
freemem(Sprite.Img,sizeof(SpriteAnimArray
Type));
end;
procedure CalcSpritePosition(var Sprite:
SpriteType);
{вычисление новых координат спрайта}
var
NewTime : longint;
begin                       { спрайта и
 их приращений}
{по достижении границы экрана делаем так,}
{ чтобы спрайт "отразился" от нее}
with Sprite do begin
if (x + Xsize + dx) >= 319 then
dx := -dx;       {вычисляем новые приращения,}
if (x + dx) <= 0 then
dx := -dx;          {реализующие "отражение"}
if (y + Ysize + dy) >= 199 then
dy := -dy;                {спрайта от стенок}
if (y + dy) <= 0 then
dy := -dy;
x := x+dx;           {   вычисляем новые  }
y := y+dy;           { координаты спрайта }
NewTime := Clock;
if NewTime >= (OldTime+AnimTime) then begin
{время менять фазу}
Phase := (Phase + (NewTime - OldTime)
div AnimTime) and 3;
OldTime := OldTime + ((NewTime - OldTime)
div AnimTime)*AnimTime;
end;
end;
end;
{...}

Полные тексты программ находятся по адресу: www.pcworld.ru