Печатать книгуПечатать книгу

§ 17. Анимация

Сайт: Профильное обучение
Курс: Информатика. 11 класс (Повышенный уровень)
Книга: § 17. Анимация
Напечатано:: Гость
Дата: Понедельник, 6 Май 2024, 06:53

17.1. Компонент Timer

Компонент таймер — Timer позволяет задавать в приложении интервалы времени. На панели компонентов System компонент таймер изображен в виде , имя объекта Timer. Компонент Timer, помещенный на форму, получает имя  TimerN, где N — номер 1, 2, 3… (пример 17.1).

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

Для таймера доступно единственное событие — OnTimer. В обработчике этого события записываются необходимые команды. Событие наступает по истечении реального времени эквивалентного значению свойства Interval для таймера. До тех пор, пока таймер не будет выключен, это событие будет наступать вновь и вновь.

Пример 17.3. Создать приложение, позволяющее вводить текущее время каждую секунду.

Этапы выполнения задания

  1. Поместить на форму компоненты Timer, Label, и два компонента Button.
  2. Изменить свойство Caption у компонента Button1 на «Старт».
  3. Изменить свойство Caption у компонента Button2 на «Стоп».
  4. Очистите свойство Caption у компонента Label1.
  5. Изменить свойство Enabled у компонента Timer1 на false.
  6. Написать обработчики событий OnClick для компонентов Button1 и Button2, которые будут запускать и останавливать таймер.
Написать обработчик событий OnTimer для компонент Timer, который будет выводить системное время. Для вывода системного времени можно использовать функцию TimeToStr(Time()), которая преобразует системное время Time() в строковое представление.

Пример 17.1. Компонент Timer на форме:

Пример 17.2. Свойства компонента Timer:

Свойство

Назначение

Interval

Задает период времени  срабатывания таймера в миллисекундах

Enabled

Значение true у этого свойства показывает, что таймер запущен

Пример 17.3. Форма на этапе проектирования:

Обработчики события OnClick для компонентов Button1 и Button2.

void __fastcall TForm1::
Button1Click(TObject *Sender)

{

  Timer1 -> Enabled = true;

}

//---------------------------

void __fastcall TForm1::
Button2Click(TObject *Sender)

{

  Timer1 -> Enabled = false;

} 

Обработчик события OnTimer.

void __fastcall TForm1::
Timer1Timer(TObject *Sender)

{

  Label1 ->Caption = TimeToStr(Time());

}

Работающее приложение:

Для вывода времени можно воспользоваться другой функцией[1]


[1] Со значениями параметров этой функции можно познакомиться в приложении

   FormatDateTime("hh:nn:ss",Time())   

Эта функция позволяет изменить формат вывода даты/времени. Например, для того чтобы вывести время так:

нужно использовать функцию со следующими параметрами:

    FormatDateTime("dddd, dd mmmm yyyy, 
    hh:mm:ss:zz", Now());   

Время срабатывания таймера изменить на 100.

17.2. Простейшая анимация

Из курса информатики 8 класса вам известно, что выделяют два способа создания компьютерной анимации: покадровая и расчетная (пример 17.4).

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

Эффект движения объекта появлялся в результате быстрой смены кадров на экране.

Используя компонент таймер, можно создать простейшую анимацию в среде C++Builder. Для этого нужно:

  1. Нарисовать объект.
  2. Запустить таймер.
  3. Пока таймер работает:
    3.1. стереть объект;
    3.2. рассчитать новое положение объект;
    3.3. нарисовать объект.

Пример 17.5. Создать секундомер с движущейся секундной стрелкой.

Этапы выполнения задания

  1. Поместить на форму компоненты Timer, два компонента Image, и компонент Button.
  2. Изменить свойство Caption у компонента Button1 на «Пуск».
  3. Изменить свойство Enabled у компонента Timer1 на false.
  4. Задать размеры Image1 и Image2 такие как у рисунка с циферблатом. Компоненты разместить так, чтобы Image2 располагался точно над Image1.
  5. Изменить свойство Transparent у компонента Image2 на true.
  6. Написать обработчик событий OnCreate для формы, в котором:
    6.1. Загрузить изображение с циферблатом в Image1.
    6.2. На канве Image2 нарисовать стрелку, указывающую на 12.
  7. Написать обработчик события OnClick для компонента Button1, который будет запускать таймер.
  8. Написать обработчик событий OnTimer для компонента Timer, который будет стирать стрелку и прорисовывать ее в новом месте каждую секунду. Каждую секунду стрелка поворачивается на угол равный  

Пример 17.4. Способы создания компьютерной анимации:

 

Пример 17.5. Форма на этапе проектирования:

Переменные x, y, x_0, y_0 типа int и переменные ang и pi типа double описаны как глобальные.

Обработчик события OnClick для компонента Button1.

void __fastcall TForm1::
Button1Click(TObject *Sender)

{

  Timer1 ->Enabled = true;

}

 Обработчик события OnCreate для формы.

void __fastcall TForm1::FormCreate
               (TObject *Sender)

{

  Image1 -> Picture -> LoadFromFile
        
("stopwatch.bmp");

  x_0 = 123; y_0 = 181; ang = 0;

  //прямоугольник белого цвета, для того

  //чтобы прозрачным стал белый цвет

  Image2 -> Canvas -> Pen -> Color = clWhite;

  Image2 -> Canvas -> Rectangle

    (00, Image1 -> Width, Image1 -> Height);

  //стрелка длиной 75 пикселей 

  //в начальном положении

  x = x_0; y = y_0 - 75;

  Image2 -> Canvas -> Pen-> Color = clBlack;

  Image2 -> Canvas -> Pen -> Width = 2;

  Image2 -> Canvas -> MoveTo(x_0, y_0);

  Image2 -> Canvas -> LineTo(x, y);

  pi = acos(-1.);

}

 Обработчик события OnTimer для таймера.

void __fastcall TForm1::Timer1Timer
                (TObject *Sender)

{

  //стирание стрелки белым цветом

  Image2 -> Canvas -> Pen -> Color = clWhite;

  Image2 -> Canvas -> Pen -> Width = 3;

  Image2 -> Canvas -> MoveTo(x_0, y_0);

  Image2 -> Canvas -> LineTo(x, y);

  //вычисление координат конца стрелки

  ang = ang + pi / 30;

  x = x_0 + ceil(75 * sin(ang));

  y = y_0 - ceil(75 * cos(ang));

  //рисование стрелки в новом положении

  Image2 -> Canvas -> Pen -> Color = clBlack;

  Image2 -> Canvas -> Pen -> Width = 2;

  Image2 -> Canvas -> MoveTo(x_0, y_0);

  Image2 -> Canvas -> LineTo(x, y);

}

 Работающее приложение:

17.3. Анимация движения

Для создания анимации движения достаточно загрузить изображение в Image и для каждого срабатывания таймера менять координаты верхнего левого угла компонента. Для того чтобы объект двигался горизонтально, достаточно менять только значение координаты х. Для организации движения по более сложным траекториям необходимо вычислять значения координат по формулам. Зависимости могут быть функциональными (значение y зависит от значения х) или параметрическим (значения как x, так и y зависят от значений параметра t).

Пример 17.6. Создать проект, в котором самолет будет пролетать над городом. При удалении самолет должен уменьшаться.

Этапы выполнения задания

  1. Поместить на форму два компонента Image, компоненты Timer и Button.
  2. Изменить свойства Caption у компонента Button1 на «Полетели!».
  3. Установить у компонента Image2 значение true для свойств Transparent и AutoSize.
  4. Установить значение false у свойства таймера Enabled.
  5. Установить в инспекторе объектов время срабатывания таймера равным 10.
  6. Написать обработчик события OnCreate для формы, в котором:
    6.1. Загрузить из файла изображение города в компонент Image1.
    6.2. Загрузить из файла изображение самолета в Image2.
    6.3. Описать начальное положение самолета, указав координаты верхнего левого угла Image2 за пределами формы.
  7. Написать обработчик события OnClick для компонента Button1, в котором запустить таймер.
  8. Написать обработчик события OnTimer, в котором необходимо изменить значение свойств компонента Image2.
    8.1. Значение свойств Left и Top. Значение Left — это величина координаты по x, которое меняется на 1 при каждом срабатывании таймера. Значение Top — это величина координаты по y, которое вычисляется по формуле = –0.3+ 200. Если самолет вылетел за границу, то вернуть его в начальное положение.

    8.2. Значение свойств Width и Height изменяется на 3 и 1 соответственно (ширина рисунка с самолетом примерно в 3 раза больше, чем высота). Изменения происходят для каждого x, которое кратно 30.

Пример 17.6. Форма на этапе проектирования:

Переменные x_plane, y_plane, w, h типа int описаны как глобальные.

Обработчик события OnClick для компонента Button1.

void __fastcall TForm1::
Button1Click(TObject *Sender)

{

  Timer1 ->Enabled = true;

}

Обработчик события OnCreate для формы.

void __fastcall TForm1::
FormCreate(TObject *Sender)

{

  Image1 -> Picture ->

    LoadFromFile("city.bmp");

  Image2 -> Picture ->

    LoadFromFile("plane.bmp");

  w = Image2 -> Width;

  h = Image2 -> Height;

  x_plane = -w;

  Image2 -> Left = x_plane;

  Image2 -> Top = y_plane;

  //изменение свойств для того,

  //чтобы можно было менять размер

  Image2 -> AutoSize = false;

  Image2 -> Stretch = true;

}

Обработчик события OnTimer для таймера.

void __fastcall TForm1::
Timer1Timer(TObject *Sender)

{

  //изменение координты x

  x_plane ++;

  if (x_plane > Image1 -> Width)

    x_plane = -w;

  //изменение координты y

  y_plane = -0.3 * x_plane + 200;

  //изменение размера

  if (x_plane % 30 == 0){

    Image2 -> Width -= 3;

    Image2 -> Height -= 1;

  }

  //вывод рисунка в новой позиции

  Image2 -> Left = x_plane;

  Image2 -> Top = y_plane;

}

Работающее приложение (в разные моменты времени):

Если при работе приложения возникает мерцание, то необходимо установить значение true для свойств формы AlphaBlend и DoubleBuffered.

17.4. Фрагменты изображений

Мерцание, которое можно было наблюдать в примере 17.6 возникает вследствие того, что изображение приходится перерисовывать на форме при каждом срабатывании таймера. Чем больше будут размеры движущегося объекта, тем сильнее будет мерцание. Использование свойств формы AlphaBlend и DoubleBuffered не всегда дает результат.

Для организации анимации движения можно использовать битовые образы, которые были рассмотрены в §14. Для вывода битового образа на Image можно использовать разные методы (пример 17.7). Использование метода Draw было рассмотрено в §14. Для применения других методов необходимо выделять на рисунке прямоугольную область.

Переменные Pr1 и Pr2 из таблицы примера 17.7 описаны типом TRect. Тип TRect — это структура с 4 полями (Left, Top, Right, Bottom), которая используется для хранения верхней левой и нижней правой вершин прямоугольника. Задавать значения можно непосредственным присваиванием значений или с помощью функций Rect или Bounds (пример 17.8).

Пример 17.9. Изменить проект из примера 14.5 так, чтобы животных можно было рисовать в выбранном масштабе.

Этапы выполнения задания

  1. Поместить на форму два компонента Image, компоненты LabeledEdit, Button и диалог OpenPictureDialog.
  2. Изменить свойства Caption у компонента Button1 на «Выбрать».
  3. Установить у компонента Image2 значение true для свойства AutoSize (рисунок будет загружаться в реальном размере).
  4. Изменить у компонента LabeledEdit1 свойство Caption на «Коэффициент масштабирования», свойство Text — на 1,3.
  5. Описать как глобальные переменные два битовых образа: Ris1 — для хранения исходного изображения, Ris2 — для хранения изображения с учетом масштаба;
  6. Написать обработчик события OnCreate для формы, в котором загрузить из файла изображение леса в компонент Image1.
  7. Написать обработчик события OnClick для компонента Button1.
    7.1. Загрузить в битовый образ Ris1 изображение из выбранного файла.
    7.2. Вывести его в Image2 методом Assign.
  8. Написать обработчик события OnMouseDown для компонента Image1, в котором
    8.1. Задать прямоугольник, длины сторон которого получаются как длины сторон исходного изображения, умноженные на коэффициент масштабирования.

    8.2. Задать размеры Ris2 в соответствии с масштабом.
    8.3. Скопировать изображение с Ris1 на Ris2, используя метод StretchDraw.
    8.4. Методом Draw вывести изображение на Image1.

При использовании метода CopyRect можно копировать прямоугольную область, находящуюся на одной канве, в прямоугольную область, находящуюся на другой канве (пример 17.10).

Если размеры прямоугольников совпадают, то получаем точную копию. Если размер источника меньше или больше, то копия масштабируется так, чтобы вписаться в прямоугольник (пример 17.11). Прозрачность фона при этом не поддерживается.

При использовании метода CopyRect изображения можно выводить, отразив их слева-направо или сверху-вниз. Для этого нужно заменить значения полей в структуре, описывающей прямоугольник:

  //сверху вниз
  Pr2 = Pr1;
  Pr2.bottom = Pr1.top;[1]
  Pr2.top = Pr1.bottom;
  //слева направо
  Pr3 = Pr1;
  Pr3.left = Pr1.right;
  Pr3.right = Pr1.left;

При необходимости можно изменить все четыре значения — в этом случае получим зеркальное отображение рисунка.

В примере 17.12. изображения копировались с битового образа Pict1 в битовый образ Pict2 для того, чтобы можно было затем вывести изображение с прозрачным фоном.



[1] Иногда необходимо отнять 1, поскольку точка, описывающая нижний правый угол, не принадлежи области копирования. Вследствие чего, при добавлении прозрачности, может оставаться белая полоса толщиной в 1 пиксель. 

Пример 17.7. Методы вывода битового образа (Ris1):

Требуемый результат

Метод

Копирование графики

Draw(x, y, Ris1)

Копирование с масштабирова-нием

StretchDraw(Pr1, Ris1)

Копирование прямоугольного участка канвы

CopyRect(Pr1, 
Ris -> Canvas, Pr2)

Полное копирование всех свойств (в том числе и размеров)

Picture->Assign(Ris1)

Пример 17.8. Определение прямоугольной области:

TRect Pr1, Pr2, Pr3;

//Определение прямоугольной 
//области указанием значений //полей

  Pr1.left = x1;

  Pr1.top = y1;

  Pr1.right = x2;

  Pr1.bottom = y2;

//Определение прямоугольной 
//области по координатам

  Pr2 = Rect(x1, y1, x2, y2);

//Определение прямоугольной 
//области путем задания 

//верхнего левого угла  и

//длины, ширины прямоугольника

  Pr3 = Bounds(x1, y1, w, h);

Пример 17.9. Форма на этапе проектирования:

Обработчик события OnCreate для формы.

void __fastcall TForm1::
FormCreate(TObject *Sender)

{

  Image1 ->Picture ->
     LoadFromFile
("лес.bmp");

  OpenPictureDialog1 -> InitialDir =

      ExtractFilePath(ParamStr(0));

}

 Обработчик события OnClick для компонента Button1.

void __fastcall TForm1::
Button1Click(TObject *Sender)

{

  if (OpenPictureDialog1 ->Execute()){

  Ris1 -> LoadFromFile
  
(OpenPictureDialog1 ->FileName);

  Image2 -> Picture -> Assign(Ris1);

  w = Ris1 -> Width;

  h = Ris1 -> Height;

  }

}

Обработчик события OnMouseDown для компонента Image1.

void __fastcall TForm1::
Image1MouseDown(TObject *Sender, 
TMouseButton Button, 
TShiftState Shift, 
int X, int Y)
{
  TRect Pr1;
  double k = StrToFloat
    (LabeledEdit1 -> Text);
  Pr1 = Bounds(00,
    int(k * w), int(k * h));
  Ris2 -> Width = int(k * w);
  Ris2 -> Height = int(k * h);
  Ris2 -> Canvas ->StretchDraw(Pr1, Ris1);
  Ris2 -> Transparent = true;
  Image1 -> Canvas -> Draw(X, Y, Ris2);
}

Работающее приложение:

Пример 17.10. Формат команды CopyRect:

Пример 17.11. Использование метода CopyRect:

Пример 17.12. Копирование с отражением:

Обработчик события для кнопки:

void __fastcall TForm1::
Button1Click(TObject *Sender)

{

  TRect Pr1, Pr2, Pr3, Pr4;

  Pr1 = Bounds(00, w, h);

  Image1 -> Canvas -> Draw(250200, Pict1);

  //сверху вниз

  Pr2 = Pr1;

  Pr2.bottom = Pr1.top - 1;

  Pr2.top = Pr1.bottom;

  Pict2 -> Canvas -> CopyRect(Pr2,

      Pict1 -> Canvas, Pr1);

  Image1 -> Canvas -> Draw(50300, Pict2);

  //слева направо

  Pr3 = Pr1;

  Pr3.left = Pr1.right;

  Pr3.right = Pr1.left;

  Pict2 -> Canvas -> CopyRect(Pr3, 

      Pict1 -> Canvas, Pr1);

  Image1 -> Canvas -> Draw(100200, Pict2);

  //сверху-внизслева-направо

  Pr4 = Pr1;

  Pr4.left = Pr1.right;

  Pr4.right = Pr1.left;

  Pr4.top = Pr1.bottom;

  Pr4.bottom = Pr1.top;

  Pict2 -> Canvas -> CopyRect(Pr4,

     Pict1 -> Canvas, Pr1);

  Image1 -> Canvas -> Draw(350300, Pict2);

}

17.5. Анимация движения с использованием битовых образов

Использование битовых образов позволяет обходиться только одним компонентом Image на форме. При срабатывании таймера объект, хранящийся в виде битового образа, будет прорисовываться поверх фона.

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

Алгоритм для реализации такой анимации будет следующим:

  1. Сохранить часть фона.
  2. Нарисовать объект.
  3. Запустить таймер.
  4. Пока таймер работает:
    4.1. восстановить фон;
    4.2. рассчитать новое положение объекта;
    4.3. сохранить часть фона;
    4.4. нарисовать объект.

Пример 17.13. Создать проект, в котором луна будет летать вокруг земли.

Этапы выполнения задания

  1. Поместить на форму компонент Image, компоненты Timer и Button.
  2. Изменить свойства Caption у компонента Button1 на «Старт».
  3. Установить значение false у свойства таймера Enabled.
  4. Установить в инспекторе объектов время срабатывания таймера равным 100.
  5. Описать три переменных для хранения битовых образов: fon — для хранения фонового изображения, kadr — для хранения изображения луны, fon_kadr — для хранения части фона.
  6. Написать обработчик события OnCreate для формы, в котором:
    6.1. Загрузить из файла изображение земли в битовый образ fon и компонент Image1.
    6.2. Загрузить из файла изображение луны в битовый образ kadr. Описать начальное положение луны.
    6.3. Определить прямоугольные области для текущего положения на фоне и для кадра.
  7. Написать обработчик события OnClick для компонента Button1, в котором необходимо запустить таймер.
  8. Написать обработчик события OnTimer, в котором следует реализовать анимацию движения луны. Луна двигается по эллиптической орбите.

Пример 17.14*. Создать проект в котором бабочка будет летать вокруг цветка по траектории в виде астроиды и менять свое направление полета.

Этапы выполнения задания

  1. Анимация запускается сразу при загрузке формы (без кнопки запуск).
  2. Если не учитывать необходимость смены направления бабочки при полете, то задание аналогично примеру 17.13 (достаточно изменить формулы определяющие точки окружности на формулы определяющие точки астроиды — пример 15.11). Поэтому рассмотрим только смену направления полета.
  3. Направление будем менять, когда значение переменной t изменится на значение   т. е. в значениях: . Переменная  будет определять, где в текущий момент времени находится бабочка. Значение переменной ρ определим следующим образом:
    =
  4. С помощью логической переменной f будем определять нужен ли поворот изображения в текущей точке.
  5. Если поворот нужен, то для четных значений p бабочка отражается слева-направо, а для нечетных — сверху-вниз.

Пример 17.13. Форма на этапе проектирования:

Глобальные переменные:

Graphics::TBitmap  

  *kadr = new Graphics::TBitmap(),

  *fon = new Graphics::TBitmap(),

  *fon_kadr = new Graphics::TBitmap();

int w, h, x_luna, y_luna, 

  x_c = 180, y_c =130,

  R_x = 155, R_y =130;

TRect pr_kadr, pr_fon;

double t = 0

Обработчик события OnClick для компонента Button1.

void __fastcall TForm1::
Button1Click(TObject *Sender)

{

  Timer1 ->Enabled = true;

}

Обработчик события OnCreate для формы.

void __fastcall TForm1::
FormCreate(TObject *Sender)

{

  //загрузка изображений

  fon -> LoadFromFile("земля.bmp");

  Image1 -> Picture -> Assign(fon);

  kadr -> LoadFromFile("луна.bmp");

  w = kadr -> Width;

  h = kadr -> Height;

  kadr -> Transparent = true;

  //определение битового образа

  //для хранения части фона

  fon_kadr -> Width = w;

  fon_kadr -> Height = h;

  pr_kadr = Bounds(00, w, h);

  //начальное положение луны

  x_luna = 180;

  y_luna = 2;

  //сохранение части фона под луной

  pr_fon = Bounds(x_luna, y_luna, w, h);

  fon_kadr -> Canvas -> CopyRect

    (pr_kadr, fon -> Canvas, pr_fon);

  //рисование луны

  Image1 -> Canvas -> Draw

    (x_luna, y_luna, kadr);

  pi = acos(-1);

}

Обработчик события OnTimer для таймера.

void __fastcall TForm1::
Timer1Timer(TObject *Sender)

{

  //восстановление фона

  Image1 -> Canvas -> Draw

    (x_luna, y_luna, fon_kadr);

  //расчет координат эллипса

  t += 0.1;

  if (t > 2 * pi)

    t = 0;

  x_luna = x_c + ceil(R_x * sin(t));

  y_luna = y_c - ceil(R_y * cos(t));

  //сохранение части фона

  pr_fon = Bounds(x_luna, y_luna, w, h);

  fon_kadr -> Canvas -> CopyRect

     (pr_kadr, fon -> Canvas, pr_fon);

  //рисование луны

  Image1 -> Canvas -> Draw

       (x_luna, y_luna, kadr);

}

 Работающее приложение:

Пример 17.14. Обработчик события OnTimer для таймера.

void __fastcall TForm1::Timer1Timer
(TObject *Sender)

{

  //восстановление фона

  Image1 -> Canvas -> Draw

    (x_fly, y_fly, fon_kadr);

  += 0.1;

  if (t > 2 * pi)

    t = 0;

  //определение четверти, в которой

  //находится бабочка и

  //смена ее при необходимоси

  if (t < pi / 2){

    if ( p != 0){

      p = 0;

      f = true;

    }

  }

  else

    if (t < pi) {

      if (p != 1 ){

        p = 1;

        f = true;

      }

    }

    else

      if (t < 3 * pi /2){

        if (p != 2){

           p = 2;

           f = true;

        }

      }

      else

        if (p != 3){

          p = 3;

          f = true;

        }

  //отражение бабочки

  if (f){

    f = false;

    pr_fly = pr_kadr;

    switch (p){

      case 0: case 2:{

        pr_fly.left = pr_kadr.right;

        pr_fly.right = pr_kadr.left;

        break;

      }

      case 1: case 3:{

        pr_fly.top = pr_kadr.bottom;

        pr_fly.bottom = pr_kadr.top;

        break;

      }

    }

    kadr -> Canvas -> CopyRect

      (pr_fly, kadr -> Canvas, pr_kadr);

  }

  //расчет координат астроиды

  x_fly = x_c + R_x * cos(t) * cos(t) * cos(t);

  y_fly = y_c - R_y * sin(t) * sin(t) * sin(t);

  //сохранение части фона

  pr_fon = Bounds(x_fly, y_fly, w, h);

  fon_kadr -> Canvas -> CopyRect

     (pr_kadr, fon -> Canvas, pr_fon);

  //рисование бабочки

  Image1 -> Canvas -> Draw

     (x_fly, y_fly, kadr);

}

Работающее приложение.

17.6. Спрайтовая анимация

Используя битовые образы, можно создавать спрайтовую анимацию.

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

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

Спрайты использовались при создании анимации в примерах 17.13 (луна) и 17.14 (бабочка). В этих примерах спрайт содержал фиксированное изображение. Современным подходом к применению спрайтов в анимации является использование раскадровок (spritesheet — атлас спрайтов) — графических файлов, содержащих несколько фаз движения какого-либо объекта (пример 17.15). Быстрая смена отдельных кадров позволяет создать иллюзию движения. Такую анимацию называют спрайтовой.

Удобно использовать раскадровки, в которых все кадры имеют одинаковую ширину (высоту).

Алгоритм создания анимации с использованием атласа спрайтов будет аналогичен алгоритму, рассмотренному в примере 17.5:

  1. Сохранить часть фона.
  2. Нарисовать первый кадр спрайта.
  3. Запустить таймер.
  4. Пока таймер работает:
    4.1. восстановить фон;
    4.2. рассчитать новое положение объекта;
    4.3. сохранить часть фона;
    4.4. перейти к следующему кадру;
    4.5. нарисовать кадр.

Пример 17.16. Создать проект, в котором бабочка будет летать вокруг цветка. Для полета бабочки использовать спрайт с раскадровкой.

Этапы выполнения задания

  1. Анимация запускается сразу при загрузке формы (без кнопки запуск).
  2. Если не учитывать необходимость смены кадров из атласа спрайтов, то задание аналогично примеру 17.13. Рассмотрим смену кадров.
  3. При каждом срабатывании таймера будем выделять новый кадр из раскадровки. Если горизонтальная координата текущего кадра стала больше, чем ширина атласа спрайтов, то выделение начинается сначала.
  4. Атлас спрайтов для бабочки состоит из 18 кадров, поэтому при каждом срабатывании таймера значение переменной t будем изменять на   .

Слово «спрайт» было придумано в 1970-е годы сотрудником компании Texas Instruments: их новая микросхема TMS9918 могла аппаратно отображать небольшие движущиеся картинки поверх неподвижного фона.

Пример 17.15. Примеры раскадровок[1]:


[1] Изображения бесплатных раскадовок взяты с сайта https://ru.freepik.com/

Пример 17.16. Раскадровка бабочки (первые четыре кадра):

Описание переменных:

Graphics::TBitmap

  *kadr = new Graphics::TBitmap(),

  *spritesheet = new Graphics::TBitmap(),

  *fon = new Graphics::TBitmap(),

  *fon_kadr = new Graphics::TBitmap();

int w, h, x_fly, y_fly, x_k, y_k;

int x_c = 150, y_c =120, R = 120;

//количество кадров

int k = 18;

bool f = false;

TRect pr_kadr, pr_fon, pr_fly;

double t = 0, pi;

Обработчик события FormCreate для формы.

void __fastcall TForm1::

FormCreate(TObject *Sender)

{

  //загрузка изображений

  fon -> LoadFromFile("podsolnuh.bmp");

  Image1 -> Picture -> Assign(fon);

  spritesheet -> LoadFromFile("fly.bmp");

  w = spritesheet -> Width / 18;

  h = spritesheet -> Height;

  //определение битового образа

  //для хранения кадра бабочки

  kadr -> Width = w;

  kadr -> Height = h;

  kadr -> Transparent = true;

  pr_kadr = Bounds(0, 0, w, h);

  pr_fly  = pr_kadr;

  //начальный кадр

  x_k = 0;

  y_k = 0;

  kadr -> Canvas -> CopyRect(pr_kadr,

      spritesheet -> Canvas, pr_fly);

  //определение битового образа

  //для хранения части фона

  fon_kadr -> Width = w;

  fon_kadr -> Height = h;

  //начальное положение бабочки

  x_fly = x_c;

  y_fly = y_c - R;

  //сохранение части фона под бабочкой

  pr_fon = Bounds(x_fly, y_fly, w, h);

  fon_kadr -> Canvas -> CopyRect

    (pr_kadr, fon -> Canvas, pr_fon);

  //рисование бабочки

  Image1 -> Canvas -> Draw

     (x_fly, y_fly, kadr);

  pi = acos(-1.);

}

Обработчик события OnTimer для таймера:

void __fastcall TForm1::
Timer1Timer(TObject *Sender)

{

  //восстановление фона

  Image1 -> Canvas -> Draw

      (x_fly, y_fly, fon_kadr);

  //новый кадр бабочки

  x_k += w;

  if (x_k > w * k)

    x_k = 0;

  pr_fly = Bounds(x_k, y_k, w, h);

  kadr -> Canvas -> CopyRect (pr_kadr,

     spritesheet -> Canvas, pr_fly);

  //новое положение бабочки

  += 0.349;

  if (> 2 * pi)

    t = 0;

  x_fly = x_c + ceil(* sin(t));

  y_fly = y_c - ceil(* cos(t));

  //сохранение части фона

  pr_fon = Bounds(x_fly, y_fly, w, h);

  fon_kadr -> Canvas -> CopyRect

    (pr_kadr, fon -> Canvas, pr_fon);

  //рисование бабочки

  Image1 -> Canvas -> Draw

       (x_fly, y_fly, kadr);

}

Вопросы к параграфу

1. Какой компонент используется для отсчета времени?

2. Как запрограммировать простейшую анимацию?

3. Какая функция позволяет скопировать фрагмент изображения с одной канвы на другую?

4. Как осуществляется отражение изображений?

5. Что такое спрайт?

6. Как организована спрайтовая анимация?


Упражнения

1. Добавьте в проект из примера 17.5 минутную стрелку (для этого удобно использовать еще один компонент timer, стрелку рисовать на отдельном компоненте Image). Добавьте кнопку «Стоп» для остановки таймера.

2. Создайте анимацию движения шарика (мяча) в прямоугольной «коробке». Удары о стенку абсолютно упругие. Шарик можно прорисовывать при каждом срабатывании таймера или загрузить его в отдельный компонент Image. *Добавьте текстовые поля для ввода начального положения шарика (мяча) и угла (угол между левой стенкой коробки и линеей траетории движения).

        

3. Создать проект, в котором над изображением будут производиться операции отражения и масштабирования.

4. Создайте проект, в котором необходимо анимировать падение яблока с башни. Движение осуществляется по параболе. Яблоко увеличивается при приближении к земле.

5. Создайте проект с использованием спрайтовой анимации. Фон и персонажей выберите самостоятельно.