Pull to refresh

Пример кэширования программных анимаций во Flash

Reading time6 min
Views2.8K
При разработке приложений с использованием технологии Flash, в большей степени это касается игр с большим кол-вом графики и анимаций, в итоге можно прийти к тому, что FPS остановится где-то на уровне 2-3-х. Это означает, что настало время заняться оптимизацией. При этом оптимизировать нужно в первую очередь то, что действительно в итоге повлияет на производительность системы. Ускорение работы за счет оптимизации на уровне логики специфично и зависит от конкретного приложения. А вот ускорение анимаций можно с успехом применять во многих проектах, о нем и пойдет речь.

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

Если же если эффект реализован с использованием ActionScript (к примеру система частиц), то в кэшируемом MovieClip-е может содержаться всего один фрейм. В таком случае данный метод требует доработки.

Решение


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

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

Код основного класса для кэширования:

public class MyCache extends Sprite
{
  private var _clip: Sprite = null;
  
  public function MyCache(clip: Sprite, framesCount: int)
  {
    _clip = clip;
    _framesCount = framesCount;
    _bitmap = new Bitmap(null, PixelSnapping.AUTO, true);
    addChild(_bitmap);
    mouseEnabled = false;
  }

  private var _bitmap: Bitmap = null;
  
  private var _frames: Array = new Array();
  private var _framesCount: int = 0;
  private var _currentFrame: int = 0;
  private var _onEnd: Function = null;
  private var _loop: Boolean = true;
  
  public function play(loop: Boolean = true, onEnd: Function = null): void
  {
    _currentFrame = 0;
    _loop = loop;
    _onEnd = onEnd;
    addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
  
  public function stop(): void
  {
    removeEventListener(Event.ENTER_FRAME, onEnterFrame);
    if (_onEnd != null)
      _onEnd(this);
  }
  
  private function createCache(clip: Sprite): Object
  {
    var rect: Rectangle = clip.getRect(clip);
    var bmpData: BitmapData = new BitmapData(rect.width, rect.height, true, 0x00000000);
    var m: Matrix = new Matrix();
    m.translate(-rect.x, -rect.y);
    m.scale(clip.scaleX, clip.scaleY);
    bmpData.draw(clip, m);
    
    return { "frame": bmpData, "x": rect.x, "y": rect.y };
  }
  
  private function cachNextFrame(): void
  {
    if (_frames.length < _framesCount)
    {
      _clip.dispatchEvent(new Event(Event.ENTER_FRAME));
      _frames.push(createCache(_clip));
    }
  }
  
  private function onEnterFrame(e: Event): void
  {
    cachNextFrame();
    
    if (_currentFrame < _frames.length)
    {
      var bmpData: Object = _frames[_currentFrame];
      _bitmap.bitmapData = bmpData.frame;
      _bitmap.x = bmpData.x;
      _bitmap.y = bmpData.y;
    }
    else
    {
      if (_loop)
        _currentFrame = 0;
      else
        stop();
    }
    _currentFrame++;
  }
}

* This source code was highlighted with Source Code Highlighter.

Чтобы правильно позиционировать битмап, для каждого кадра кроме самой картинки дополнительно хранится ее смещение.

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

Базовый класс пула

public class MyCachePool
{
  protected function createEffect(): MyCache
  {
    return null;
  }
  
  private var _pool: Array = new Array();
  
  public function show(owner: Sprite, px: int, py: int, loop: Boolean = true): void
  {
    var effect: MyCache = getEffect();
      
    owner.addChild(effect);
    effect.x = px;
    effect.y = py;
    effect.play(loop, onEnd);
  }
    
  private function getEffect(): MyCache
  {
    if (_pool.length > 0)
    {
      var idx: int = Math.round((Math.random() * (_pool.length - 1)));
      var rez: MyCache = _pool[idx];
      _pool.splice(idx, 1);
      return rez;
    }
    
    return createEffect();
  }
    
  private function onEnd(effect: MyCache): void
  {
    _pool.push(effect);
  }
}

* This source code was highlighted with Source Code Highlighter.


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

Производный класс пула для конкретного эффекта.

public class BoomCachPool extends MyCachePool
{
  override protected function createEffect():MyCache
  {
    return new MyCache(new gBoom(), 20);
  }
}


* This source code was highlighted with Source Code Highlighter.


Класс gBoom представляет собой MovieClip, экспортированный в проект из swc файла. Кол-во кадров для кэширования подбиралось эксперементально.

Ниже представлены результаты тестов.



Рисунок 1 — Частицы без кэширования


Рисунок 2 — Частицы с кэшированием

У данного метода также имеются определенные недостатки. На первом этапе FPS такой, как и без использования кэширования (в теории даже меньше, т.к. дополнительно производится растеризация в BitmapData). Кроме того, в отличие от использования оригинального клипа, кол-во различных эффектов здесь все-таки конечное, т.к. последующие эффекты уже берутся из пула. Однако это уже необходимое зло для увеличения производительности (всегда приходится чем-то жертвовать). Чтобы это компенсировать, эффекты из пула берутся не последовательно, а случайным образом. На практике этого зачастую достаточно, чтобы обеспечить приемлемое для пользователя разнообразие.

Заключение


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

Исходный код проекта можно скачать здесь.

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

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

Tags:
Hubs:
Total votes 29: ↑24 and ↓5+19
Comments13

Articles