Решение проблемы производительности во Flash

Изначально работа над игрой «Грибник» шла быстро и прогрессивно. Я очень радовался технологии Flash благородя её простоте использования, и был полон оптимизма. Но позже я с ужасом обнаружил, что Flash с трудом выдает должное количество кадров, когда объектов становится более сотни.

А в игре должна быть карта размером 20х20 клеточек, да и еще с несколькими слоями. То есть объектов на карте только в одном слое может быть легко до 300 шт. А некоторые объекты еще и с анимацией, что еще хуже для производительности, потому что анимация типа Motion и Shape Tween программная. А это значит, что каждый кадр не только просто рендерится, но и производится еще масса расчетов за пределами моего кода для расчета промежуточных кадров в MovieClip`ах. И даже хитрый рендер во Flash, который перерисовывает только обновленные участки экрана — не спасает ситуацию.

Вот тут — то мой оптимизм немного угас. Я был расстроен. Неужели такая замечательная технология, как Flash, годится только для баннеров и примитивных игр!?

Векторная графика сама по себе очень сложная штука. Любой, кто хоть как-то знаком с векторной графикой, должен отлично понимать, что все кривые просчитываются сложными математическими формулами. А когда дело еще касается анимации, то там необходимость не только в отображении массива точек и построении между ними кривых, но и в поиске промежуточных  положений кривых между ключевыми кадрами. И такие расчеты должны выполняться не менее 25 раз в секунду для игры. То есть, выходит, что затраты на расчеты максимальны, а результат весьма посредственный.

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

У Flash есть специальная опция «cache as bitmap», которую можно устанавливать для отдельных клипов — это обозначает, что клип где-то в памяти сохраняется в виде растровой картинки и более не пересчитывается как векторное изображение. Мои эксперименты показали, что это дает прирост производительности, но для полного счастья его явно не достаточно. Причина скорее всего в том, что время от времени эти «кэшированные» клипы вновь обновляются за счет чего могут возникать тормоза. Да и количество объектов по-прежнему остается прежним, что продолжает вызывать проблемы с производительностью. 

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

Оптимизация из вектора в растр

Сделать растровую картинку из массы клипов просто. Составляем все клипы в том порядке и очередности как надо, а потом словно фотоаппаратом делаем с них снимок в битмап, который далее используем как подложку в виде игрового уровня. Подробно о том, как это сделать, очень хорошо рассказано в примере-уроке у Хитри.

Это все конечно здорово и настроение мое уже улучшилось, ведь производительность такими не хитрыми способами удалось поднять почти в двое. Но проблема до конца не решена, так как львиную долю производительности по прежнему кушает «векторная анимация». Начинаю уже задумываться о том, а что если примерно так же, но в виде массива растровых картинок сохранять отдельные кадры из MovieClip`ов, а потом их, как в старые добрые времена, выводить в нужном месте с нужным кадром. И не успел я еще эту мысль до конца переварить, как обнаружил, что и этот «велосипед» уже изобретен до меня, ему лишь надо отрегулировать «педали» и «сидушку».

Лента растровых картинок

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

Сам класс с реализацией конвертирования векторной анимации в растр для AS3 можно скачать здесь... В архиве с исходниками есть пример как использовать.

Разобравшись с чужим классом, немного скорректировав его под себя и даже исправив авторские баги (а может быть это были фичи) — я получил .swf файл со всей игровой графикой размером в 460 кб. и при этом с хорошей производительностью. Узкое место этого подхода только в том, что после загрузки флешки уходит некоторое время, чтобы выполнить конвертирование всей векторной графики в растровую. Но это не страшно, в среднем на это требуется 5-10 сек. Если процесс конвертирования оформить как загрузку флешки, то пользователь вовсе ничего не заметит :)

И теперь я кажется понимаю почему так мало хороших Flash платформеров и любых других динамических игр с картами более чем в один экран. Не каждый разработчик подходит к процессу оптимизации внимательно и как правило пользуется самым простым решением — делит игру на экраны или старается использовать минимум объектов. Об этом, судя по всему, так же не догадываются и разработчики социальных игр, потому-что их игры в большинстве очень жутко тормозят даже на хороших компьютерах, из-за чего авторы возможно теряют аудиторию.

Ссылки по теме:

- Из вектора в растр
- Тотальная растеризация


Индикаторы: Action Script 3, Уроки
Постоянная ссылка

 

 

Полезная штука AS3 Bitmap Cached Animations :) Спасибо.
Хотелось бы заметить про cache as bitmap.
Такие объекты действительно считаются очень быстро, но есть много ограничений на использование. Не стОит его использовать если:
есть изменение содержимого мувика (внутренняя анимация), мувик будет перерисовываться.
мувик поворачивается или масштабируется, мувик будет перерисовываться.
если кэшированная картинка будет большой, а вектор, из которого она состоит, очень простой (на ее отображение уйдет куча оперативки, чтобы отстроить битмапу).

А с анимацией надо поэкспериментировать :)

Сашка
8 Декабря 2009
— 11:11
#

Кстати у хитри как раз по этому есть тема...
И дам ссылку на том же сайте что и ты давал только там можно посмотреть тест как работают разные техники... 
1. uncached
2. cached as frames
3. cached on spritesheet

Первые две понятны, а вот третий так и не понял. Но он показывает среднюю скорость, потому наверное не стоит на нем заморачиваться.

А вообще наверное можно поделить оптимизацию на 3 категории:
1. Оптимизация анимации (о чем ты нам поведал сейчас)
2. Алгоритмическая оптимизация кода (чтобы код работал по принципу кратчайшего пути, без лишних условий, ветвлений и циклов)
3. Техническая оптимизация кода
(это когда вместо:

var someArray:Array = new Array();
for(var i:int = 0; i < someArray.length; i++)
...
мы пишем:
var someArray:Array = new Array();
var len:int = someArray.length;
for(var i:int = 0; i < len; i++)
...

или вместо:
if()
{}
else
{}

пишем:
someConditions ? result : result

или
i = i + 1; меняем на i++;

или i = i + someVal; меняем на i += someVal;

и т.д. Думаю меня поняли :)))
)

VirtualMaestro
8 Декабря 2009
— 11:24
#

@Сашка, про cache as bitmap — все верно, казуальная опция для домохозяек :) Кэшируя графику и анимацию в битмапы вручную тоже следует контролировать объем используемой оперативной памяти, так как без нее в этих случаях никак. И если объемы анимаций и графики велики, то возможно стоит выполнять кэширование только той графики, которая необходима для текущего уровня, это с экономит память и время ожидания игрока при запуске игры ;)

Ant.Karlov
8 Декабря 2009
— 14:06
#

@VirtualMaestro, только в этом примере Хитри как раз предлагает конвертировать графику в ручную от чего сразу возникают две проблемы: время на конвертирование при любых исправлениях и большой вес флешки.

Признаюсь с cached on spritesheet я тоже не до конца разобрался. Но мне показалось, что этот метод разбивает каждый кадр на тайлы. Зачем это нужно я не понял. Поскольку производительность от данного приема действительно сомнительная я лично не стал заморачиваться :)

По поводу технической оптимизации кода с удовольствием дам пару интересных ссылок о том как оптимально следует писать код:
Повышение производительности AS3 приложений
Efficient Programming Practices
Только конечно следует учитывать, что если весь код написать с учетом этих приемов, то не факт, что на деле удастся получить хотя бы на 1-2 фпс больше. Так как все зависит от сложности приложения и в простых играх/приложениях такие хитрости могут вообще не дать эффекта. Просто следует эти приемы запомнить и использовать как стандартный подход в своей работе ;)

Ant.Karlov
8 Декабря 2009
— 14:33
#

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

VirtualMaestro
8 Декабря 2009
— 20:57
#

Если процесс конвертирования оформить как загрузку флешки, то пользователь вовсе ничего не заметит :)
Практически во всех играх у Nitrome так и сделано :)

Алексей
9 Декабря 2009
— 08:58
#

@Алексей, у Nitrome же в играх вся графика пиксельная. Мне кажется, что им там нечего конвертировать, поэтому там скорее всего реальная загрузка флешки :)

Ant.Karlov
9 Декабря 2009
— 19:07
#

Можно сделать поэтапный рендеринг графики, лишь в поле видимости. Тогда большинство проблем с производительностью решаются. Мануал еще от 04 года, там есть демо флешка 2200 объектов и 110-120 фпс http://www.strille.net/tutorials/part1_scrolling.php

kakudaf
10 Декабря 2009
— 20:19
#

Спасибо за линк, очень интересно. Приблизительно так себе и представлял, но интересно как это другие реализовывают. Тут поле для фантазии большое, можно по разному эту технику реализовывать. Но главное сама идея - Рендеринг в поле видимости.

VirtualMaestro
12 Декабря 2009
— 15:49
#

Очень интересный и пожалуй самый правильный подход, известный мне лично еще задолго до Flash. Первое, что я сделал столкнувшись с тормозами во Flash — это попробовал реализовать подобную систему, но к своему удивлению обнаружил, что она совершенно не дала никакого прироста производительности. Судя по приведенному примеру мой просчет был в том, что я не удалял объекты из сцены (removeChild();) когда они выходили за приделы экрана, а просто скрывал их (visible = false;). Потом я понял почему при таком подходе небыло прироста производительности. Flash обладает хитрым рендером и все что не попадает в область видимости экрана — не рендерится. Более того даже то что находится в области видимости не все каждый кадр перерисовывается. Перерисовываются только те области экрана которые изменяются. Как это все работает можно посмотреть в отладочном плеере выбрав в контекстном меню к клипу "Показать области перерисовки" (Show redraw regions). А вот при полном удалении объектов из сцены судя по примеру эффект есть, я вот до этого не догадался :)

Жаль что исходник данного примера у меня лично не открылся :( Хотел по экспериментировать с производительностью. Будет время, попробую сделать свой подобный вариант.

Ant.Karlov
12 Декабря 2009
— 16:05
#

@VirtualMaestro, стоит обратить внимание на то, что flash не рендерит вообще то, что не попадает в область видимости. В этом примере, судя по всему эффект оптимизации за счет того, что объекты попадающие за приделы экрана вовсе удаляются из сцены :)

Ant.Karlov
12 Декабря 2009
— 16:10
#

Мы конкретно не интересовались, что делает флеш с графикой, банально предположили, что рендерит за пределами видимости =) . В одной из игр рпг/стратегии(пока в заморозке) используется довольно большие карты которые рисуются из текстур, когда пробовали создать карту 10000х10000 пикселей (это 1к объектов лишь земли + дома, деревья и тд накладывается сверху) производительность абсолютно никакая. Применив данный способ - проблема относительно решилась. Относительно, так как все равно существует вероятность, что в поле видимости попадет слишком много объектов :p

kakudaf
12 Декабря 2009
— 20:20
#

@kakudaf, у меня не получается никак открыть исходник по указанной ссылке чтобы поиграться с данным примером. На досуге попробую собрать свой аналогичный пример. И если вдруг выясню чего интересного, обязательно напишу отдельный пост в продолжение темы оптимизации :) Спасибо за ссылку!

Ant.Karlov
15 Декабря 2009
— 22:48
#

я слышал, что неплохой прирост производительности дает преобразование линий в заливку.. кто-нибудь может что-либо про это написать?

Hagemay
24 Марта 2010
— 11:44
#

@Hagemay, лично я сомневаюсь в этом. Преобразованная линия в заливку по прежнему останется векторной, а значит особо на производительности это не скажется. К тому же мне кажется, что для отображения заливки нужно чуть больше параметров чем для обычной линии.

Для повышения производительности как минимум следует устанавливать опцию cache as bitmap для векторных клипов.

Ant.Karlov
24 Марта 2010
— 14:29
#

http://www.bytearray.org/?p=290

kote
13 Мая 2010
— 23:36
#

Why cacheAsBitmap is bad, http://www.bytearray.org/?p=290

kote
13 Мая 2010
— 23:37
#

@kote, спасибо за ссылку. Я примерно это и имел в виду. Не пользуйтесь cacheAsBitmap!

Ant.Karlov
14 Мая 2010
— 11:47
#

В смысле "галочку" не ставить? (в английском не силен) А если использовать вышеуказанный класс - все ок будет? ограничений никаких не будет?
Ну и, собственно, вопросик есть...
Ant.Karlov, а ты не мог бы опубликовать какие конкретно баги (ну или фичи) ты нашел и как их исправлял?

Hagemay
6 Июля 2010
— 13:30
#

@Hagemay, cacheAsBitmap немного не так работает как мы возможно себе это представляем и в итоге производительность от его использования возрастает не на много. Данный же класс делает все "вручную" и дает лучшие результаты чем просто cacheAsBitmap.

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

Основные баги которые поджидали меня в этом классе — это неправильная работа реверса анимации и что-то еще было со сглаживанием битмапов (smoothing), толи он не задавался, толи не выключался, я уже не помню :)

Ant.Karlov
6 Июля 2010
— 13:52
#

@Ant.Karlov, спасибо большое - теперь понял :)
Жду упомянутой тобой статьи :)

Hagemay
6 Июля 2010
— 20:20
#

Разрешите конкретный вопрос?
Есть movieclip назовем его M_1, в нем 6 кадров. В каждом кадре movieclip с анимацией. В первом movieclip M_1-1, во стором M_1-2 ... M_1-6 последний шестой кадр.
M_1 добавляется на сцену 6 раз. Выбираются кадры, на все шесть M_1 вешаются слушатель щелчка. Если на какой либо M_1 щелкнули, он TweenLite анимацией переносится в некоторую точку.
Вопрос - как все это дело оптимизировать?
Кэшировать M_1-1...M_1-2 анимацию и кэшировать M_1 при добавлении на сцену?
Объекты добавляются один раз.

vacsa
24 Ноября 2010
— 01:36
#

Хоть кешед хоть не кешед, на моем атлоне все по 4 фпс :(

pavezlo
11 Апреля 2011
— 22:46
#

А что если я экспортирую jpg рисунки во флеш библиотеку и добавляю их в кадры мувиклипа, тогда мне же не нужно делать растеризацию и cacheAsBitmap?

Парень справа
1 Февраля 2012
— 15:36
#

Парень справа, думаю, нет - это же уже по определению битмап.

по теме - хорошая статья! и в комментариях много информации полезной, спасибо.

DeLameter
9 Февраля 2012
— 13:32
#

как решить проблему с тормозами в уже готовых запускаемых flash-приложениях?

АsЪ ЕсьмЪ
30 Сентября 2012
— 14:48
#