Почитайте!

Нас читают

Статьи о программировании и не только

Mail.Ru

Rambler's Top100

Programming Blogs - BlogCatalog Blog Directory

Двойная буферизация или Анимация без мерцания

10.12.2007 от Иван Ширко

     Пример к статье (программа + исходники на Delphi)

     Все мы с упоением в детстве смотрели мультики, да и сейчас иногда, щёлкая каналы, с ностальгией в душе останавливаемся посмотреть на рисованных персонажей, живущих своею жизнью.
     Что же представляет из себя анимация? Да просто сменяющие одна другую картинки. И чем быстрее и плавнее меняется изображение, тем лучше выглядит анимация. Возможностей современного компьютера вполне достаточно, чтобы обеспечить нормальное отображение любой анимации. Но в играх анимация формируется динамически, так что любая задержка отображения картинок может вызвать мерцание. Чтобы этого избежать используют различные алгоритмы вывода изображения на экран. Давайте рассмотрим простой способ, с помощью которого можно делать динамические анимации, отлично отображающиеся даже на слабых компьютерах.

     Итак, разберём самый простой случай, когда нам нужно двигать простую фигуру (например, круг) по одноцветному фону. Реализовать это можно разными способами. Можно, например, действовать напрямую: нарисовать поверх фона круг и через некоторый промежуток времени заливать всю картинку цветом фона, после чего рисовать круг со смещёнными координатами. В итоге мы получим вполне движущуюся фигуру, но вот мерцание будет всё портить. Решение напрашивается само собой: нужно заливать не весь холст цветом фона, а только ту область, из которой нам нужно стереть изображение круга. В данном случае достаточно просто нарисовать цветом фона такой же круг, но у которого цвет равен цвету фона, что приведёт к стиранию начального круга.
А теперь представим, что у нас по одноцветному фону движется уже сложное изображение. Здесь уже не удастся пиксель в пиксель затереть это изображение, так что придётся обводить его некоторой простой областью (например, прямоугольником) и закрашивать её.
     Всё это, конечно, хорошо, но описанные случаи не представляют из себя особой практической ценности и используется разве что в учебных целях. Так что усложним ситуацию: пусть сложное изображение движется по сложному фону. Закрашивать тут не имеет смысла, так что нужно придумывать что-нибудь новенькое. Вот тут мы и подошли к методу двойной буферизации. Этот метод использовался и используется в играх, программах с графическим интерфейсом, да и просто в стандартных компонентах операционных систем. Как следует из названия, метод двойной буферизации предполагает наличие двух буферов. Один нужен для хранения перемещаемого изображения, а другой для хранения той области фона, в которую мы собираемся прорисовать это изображение. То есть последовательность действий примерно такая:

  1. сохраняем область фона в буфер;
  2. прорисовываем на фоне в этой области перемещаемое изображение;
  3. через некоторое время восстанавливаем участок фона, вычисляем новые координаты и переходим к пункту 1.

     Давайте рассмотрим реализацию этого алгоритма на Delphi. В данном примере по сложному фону будет летать по круговой траектории самолётик (рис.1).

Двойная буферизация

Вначале нужно объявить несколько констант и переменных:
const
  //координаты центра окружности
  xc = 200;
  yc = 200;
  //приращение угла
  dl = 0.05;
  //радиус окружности
  R=100;
var
  //для хранения изображений
  sam, buf: TBitmap;
  BufRct: TRect;
  l: real;
  x, y:integer;
  w, h:integer;

Действия при запуске программы:

  //создаём объекты для хранения изображений
  Buf:=TBitmap.Create;
  Sam:=TBitmap.Create;
  //делаем фон самолётика прозрачным
  Sam.Transparent:=true;
  //загружаем изображение самолётика
  Sam.LoadFromFile('sam.bmp');
  //устанавливаем размеры буфера такими же, как у самолётика
  w := Sam.Width;
  h := Sam.Height;
  Buf.Width:=w;
  Buf.Height:=h;
  //начальный угол
  l := 0;
  //начальные координаты
  x := round(xc+R*cos(l));
  y := round(yc+R*sin(l));
  //сохраняем участок фона
  BufRct := Bounds(x,y,w,h);
  Buf.Canvas.CopyRect(Buf.Canvas.ClipRect,Image1.Canvas,BufRct);
  //рисуем самолётик
  Image1.Canvas.Draw(x,y,sam);
  //запускаем таймер
  Timer1.Enabled := true;

Теперь через небольшой промежуток времени выполняем процедуру:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //восстанавливаем участок фона
  Image1.Canvas.Draw(x,y,buf);
  //увеличиваем угол
  l := l + dl;
  //если самолёт сделал круг, то отнимаем от угла 2 пи
  if (l>2*pi) then
    l := l - 2*pi;
  //высчитываем координаты по новому углу
  x := round(xc+R*cos(l));
  y := round(yc+R*sin(l));
  //сохраняем участок фона в буфер
  BufRct := Bounds(x,y,w,h);
  Buf.Canvas.CopyRect(Buf.Canvas.ClipRect,Image1.Canvas,BufRct);
  //рисуем самолётик
  Image1.Canvas.Draw(x,y,sam);
end;

     Разумеется, этот алгоритм можно перенести и на случай движения изображения по движущемуся фону, достаточно лишь математически учесть изменение положения фона. Метод двойной буферизации применяют также и для неподвижных объектов, например, для отображения какой-нибудь анимации (флаг развевается на ветру), т.е. после каждого кадра анимации мы восстанавливаем фон, чтобы исключить наложение кадров друг на друга.
     Как видим, метод двойной буферизации достаточно легко реализовать, но несмотря на это он показывает очень хорошие результаты. И правильное использование описанного алгоритма делает программу с простой анимацией более эффектной и профессиональной без особых усилий программиста.
Иван Ширко
ishyrko@gmail.com

Рубрики: Delphi | Комментарии (2) »

Комментарии (2)

  1. Антон пишет:

    Прекрасная статья! Респект автору и уважуха. Спасибо, очень помогло.

  2. samsim пишет:

    ООоооо.. эта тема мне по душе. Писал небольшую игру типа марио, но никак не мог избавиться от постоянного мерцания выводимого обьекта. Спасибо за такое решение !