суббота, 14 августа 2010 г.

Очистка блитированного холста путем удаления только изменившихся частей (использование карты повреждений)

Сегодня поговорим о методах обновления экрана и сравним эти методы.
Это перевод статьи Tutorial: Clearing a blit canvas by erasing only the portions that have changed (using damage maps or a dirty rect).


Очень длинный заголовок, но создать более короткий и более информативный заголовок оказалось сложно.

В настоящий момент я работаю над ремейком игры «Star Castle», которую назвал «Solar Fortress». Я остановился на том моменте, где мне понадобилось выяснить, как рендерить мои частицы и эффекты на экран. Большинство текущих игровых объектов представляют собой объекты Bitmap с соответствующим объектом BitmapData. Для спецэффектов и частиц я планировал их блитировать на слой, который будет располагаться выше главного холста. Я уже собирался писать код для моей стандартной операции блитирования, когда на глаза попалась старая книга по программированию игр Andre Lamothe.

В этой классической книге по DOS играм, Андре на примере нескольких классических игр раскрывает секреты блитирования, спрайтов, сжатия изображений (RLE), прозрачности и много другого. Ожидая пока Стив закончит селекторное совещание, я сидел за его столом и изучал это «сокровище». В главе о спрайтах и блитировании Андре описал метод обновления блита главного холста, который я никогда ранее не пробовал. Обычно, я обновляю весь блит видимой части холста, начиная с фона и заканчивая персонажами. Метод, который предлагает Андре очень отличается от этого.

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

Я решил сравнить этот метод в AS3 с моим обычным блитированием всего холста целиком, и с очисткой фона с помощью заливки его однотонным цветом методом fillRect() класса BitmapData.

Ниже swf с результатами этого теста. Здесь у нас блитирование в главный холст 6000 двигающихся квадратиков размером 10х10. Эта операция запускается для каждого метода и длится в течение 100 кадров. Перед и после каждого кадра вызывается метод getTimer() для определения времени, которое занимает данная операция в текущем кадре и затем это значение складывается с общим значением. После 100 кадров общее значение делиться на 100, чтобы получить среднее значение.

Этот тест написан на скорую руку. В результате в браузерах и плеерах я увидел, что новый метод (обновление только изменившихся частей) быстрее двух остальных методов. Однако результаты очень близки. Это немного меня удивило, т.к. эти 6000 объектов дают 5999 операций блитирования (пусть и небольших) и должны были занимать больше времени, чем одно полное стирание экрана. Итак, методы располагаются в следующем порядке (от быстрого к медленному):

1. Обновление только тех частей, которые изменились
2. Полная заливка фона одним цветом с помощью метода fillRect() – понятно, что этот метод неприменим, если фон в игре представлен не однотонным цветом, а чем-то другим.
3. Использование метода copyPixels() для перерисовки всего содержимого экрана.

Поясним каждый из этих методов.

Метод 1: copyPixels()



Здесь, в каждом игровом цикле, я сначала блитирую в холст изображение фона

canvasBD.copyPixels(backgroundBD, backgroundBD.rect,backgroundPoint);


и затем каждое из изображений этих 6000 объектов. Итого мы проводим 6001 операций блитирования в течении одного игрового цикла.

blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);


Метод 2: fillRect()



Этот метод применим, если у нас фон залит одним цветом. Если у нас фон детализированный этот метод неприменим. В моей версии этого метода в начале игрового цикла я сначала заполняю холст черным цветом

canvasBD.fillRect(canvasBD.rect, 0xFF000000);


а затем отрисовываю все объекты

blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);


Метод 3: Обновление только изменившейся области



В этом методе я не стираю или заливаю фон перед выполнением блитирования объектов. Здесь мы выполняем 12000 операций блитирования. Для каждого объекта я сначала блитирую фон под ним в ту позицию, которую он сейчас занимает,

blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(backgroundBD, objectBD.rect, blitPoint);


а затем блитирую сам объект в новую позицию

blitPoint.x = tempObject.x;
blitPoint.y = tempObject.y;
canvasBD.copyPixels(objectBD, objectBD.rect, blitPoint);


Результаты:



В итоге у меня получилось, что новый метод быстрее других в среднем на 2-3 миллисекунды. А метод с fillRect() быстрее метода с copyPixels(). Я выкладываю класс теста, чтобы вы могли посмотреть результаты с разным количеством объектов. При количестве объектов от 1 до 50 новый метод имеет еще большее преимущество. С увеличением количества объектов это преимущество становиться меньше и это следовало ожидать.

Итак сам swf

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/08/14/NewProject.swf, 400, 400[/SWF]

Код класса можно посмотреть в исходниках.

Послесловие.



Есть еще один способ оптимизации, которую можно использовать, прежде чем обновлять содержимое экрана. Это проверка на то изменился объект или нет. Если нет, то повторно блитировать его не нужно, до тех пор пока его координаты или состояние, которое представлено другим изображением, не изменяться.

На этом у меня все. Исходники.

4 комментария:

  1. Да, на большом количестве объектов отличие очень мало.

    Спасибо за интересный блог, если будет интерес обратите внимание на подборку статей http://cheezeworld.com/game-structure/ и http://cheezeworld.com/category/game-library/

    Я думаю их перевод будет многим полезен.
    К сожалению автор перестал его вести.

    ОтветитьУдалить
  2. Да, да. Вы дали ссылку на очень интересный ресурс, я с ним сталкивался, когда переводил про steering behaviors, но потом благополучно забыл про него. Начнем, пожалуй, со статьи про машину состояний :)

    ОтветитьУдалить
  3. Посмотрел на исходники. Возможно, если прооптимизирвать updateAndDrawCopypixelsObjects было бы пошустрее. Избавиться от for, поставить LinkedList

    ОтветитьУдалить
  4. Знаю, что уже никто не ответит и это более никому не интересно, странно, что в исходниках не используется метод updateAndDrawBliteraseObjects, а режим тестирования Bliterase по сути тот же самый bitmapFill

    ОтветитьУдалить