Эта статья посвящена оптимизации ActionScript3 на примере создания эффекта «полета сквозь звезды»(на подобии старого скринсейвера Windows). Мне этот эффект необходим был для игры. Эффект должен был служить фоном и поэтому требовалось чтобы он был как можно менее ресурсоемким.
Я не изобретаю никакие новые приемы оптимизации, я лишь приведу пример их применения на практике. Для начала я поискал в интернете готовые решения и наткнулся на решение, которое визуально меня устраивало: http://www.lemlinh.com/flash-source-starfield-generator/. Открыв код мы обнаружим, что эти Звездочки — это MovieClip’ы (крик ужаса за кадром). Каждый флеш-разработчик понимает, что кроме отображения «точки» на каждую «Звездочку-MovieClip» вешается куча свойств и методов класса MovieClip. И чем больше звезд – тем хуже. Я ничего не хочу сказать об авторе — просто для нам требуется другое.
Ясно, что в такой ситуации прийдется сделать все с самому и с нуля. Ничего гениального в нашей реализации нет:
- создать массив точек stars:Array
- в каждом кадре:
- …просчитать новое положение каждой точке по суперсложной математической формуле (умножение).
- …нарисовать каждую точку на экране.
Сразу оговорюсь, я использую собственный метод получения случайного значения в классе Tools.Utils.as:
public static function rand(lowValue:Number, hightValue:Number, round:Number = 1):Number{
return lowValue + Math.floor(Math.random() * (hightValue-lowValue)*round)/round;
}
Реализуем наш нехитрый алгоритм и получаем класс StarFieldBad.as. Код нам не важен. Но именно такой код я написал бы до того, как начал закомиться с методами оптимизации когда.
Звезд у нас будет меньше чем на небе, создадим их всего 2000 (реально их хватит 100-200, но нам для теста нужно побольше):
background = new StarFieldBad(_game.STAGE.stageWidth, _game.STAGE.stageHeight, 2000); addChild(background);
Выполнение инициализации не является критическим параметром, т.к. создание объекта делается не часто. Но все же среднее время создания: 7,42ms.
Критическим показателем в нашем случае является время выполнения кадра: 9,1095ms.
Вроде бы и подход не самый страшный, и звезды не создаются каждый раз, но на самом деле это все не верх совершенства. Итак, пойдем по порядку.
bitmapData.lock()
В коде не хватает использования метода lock() и unlock(). Добавляем их. (Подробнее «Работа с пикселами» на сайте Adobe.)
В итоге время выполнения стало: 8,9753ms. (Печально, маленький прирост)
Цикл FOR
...for (var i:int = 0; i < stars.length; i++) // Проходимся по всем звездам...
Цикл при каждом проходе обращается к объекту и его свойству. Заодно заменяем цикл for на цикл while:
var i:uint = starsCount;// Итератор звезд while ( --i > -1)// Проходимся по всем звездам ...
Время выполнения: 7,5379ms. (Удивительно большой прирост)
По возможности избегайте использования оператора квадратной скобки
Например у нас внутри циклов происходят подобные операции:
stars[i].x += (stars[i].x - fieldWidth/2) * 0.01; ...
Заменяем оператор квадратные скобки на ссылку в локальной переменной (имеет смысл, если мы обращаемся к переменной много раз):
var star:Point;// Ссылка на мат. модель звезды
while ( --i > -1)// Проходимся по всем звездам
{
star = stars[i];
star.x += (star.x - halfWidth) * 0.01;
...
Время выполнения: 4.4279ms. (Невероятно большой прирост. Видимо, ещё сказывается большой объем массива)
По возможности используйте класс Vector вместо класса Array
Заменяем Array на Vector.<Point>. «В проигрыватель Flash Player 10 добавлен класс Vector, который обеспечивает более быстрый доступ для чтения и записи, чем класс Array.» (источник)
stars = new Vector.<Point>(starsCount, true);
Время выполнения: 3.0963ms.
Избавляем Flash от ненужных повторяющихся вычислений
Можно заранее просчтитать значения таких выражений как «fieldWidth / 2» на «halfWidth» или «fieldHeight+offset» на «fieldWidthWithOffset» и сохранить их в переменные, тем самым избавив флеш от ~20000 лишних операций деления и сложения(это в нашем примере) за кадр:
public function StarField(width:int, height:int, count:uint)
{
fieldWidthWithOffset = fieldWidth = width;
fieldHeightWithOffset = fieldHeight = height;
fieldWidthWithOffset += offset;
fieldHeightWithOffset += offset;
halfWidth = fieldWidth / 2;
halfHeight = fieldHeight / 2;
...
Время выполнения: 2.9642ms. (ну, большого прироста никто и не обещал)
Другие приемы оптимизации
Далее я опущу описание других методов, которые мало сказались на производительности (но это не значит, что нужно ими пренебрегать!):
- Определение переменных вне цикла.
- Встраивание кода для уменьшения числа вызовов функций в коде.
В итоге время выполнения скрипта было уменьшено с 9,7753ms до 2.9405ms. А это более чем в 3 раза! И время инициализации сократилось в 2 раза — с 7,42ms до 3,26ms.
Файл Tools.Utils.as.
Исходный файл StarFieldBad.as.
Итоговый файл StarField.as
Эти и подобные методы оптимизации следует научится использовать сразу, чтобы в последующем не пришлось проводить оптимизацию кода. Всем творческих успехов!
Пару ссылок по теме:
Оптимизация производительности для платформы Flash Platform
http://www.rozengain.com/blog/2007/05/01/some-actionscript-30-optimizations/
И конечно, Google