ActionScript3: Кэширование анимации и ее проигрывание (blitting).

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


package cache2bmp
{
import ru.dijump.wonf.cache2bmp.*;

import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.geom.Matrix;
import flash.geom.Rectangle;

public class CacheAsBitmap
{
    private var _sourceMovieClip:MovieClip;

    public function CacheAsBitmap():void
    {
        this._sourceMovieClip = new MovieClip();
    }

    public function cache(source:MovieClip):Vector.<CachedFrameModel>
    {
        //конечный материал будет состоять из массива простых объектов - кадров с 
        //необходимой информацией об выдираемом кадре
        //и его "оттиском" в виде BitmapData
        var clip:Vector.<CachedFrameModel> = new Vector.<CachedFrameModel>();
        this.sourceMovieClip = source;
        var totalFrames:int = sourceMovieClip.totalFrames;
        //перебираем все кадры и кэшим их в наш массив
        for (var i:int = 1; i <= totalFrames; i++)
        {
            sourceMovieClip.gotoAndStop(i);

            clip.push(cacheFrame(sourceMovieClip));
        }
        return clip;
    }

    private function cacheFrame(sourceMovieClip:MovieClip):CachedFrameModel
    {
        //сохраняем всю информацию о кадре
        var cachedFrame:CachedFrameModel = new CachedFrameModel();
        cachedFrame.width = sourceMovieClip.width;
        cachedFrame.height = sourceMovieClip.height;
        cachedFrame.x = sourceMovieClip.x;
        cachedFrame.y = sourceMovieClip.y;
        cachedFrame.numFrame = sourceMovieClip.currentFrame;
        //получаем прямоугольник, описанный вокруг изображения, с его размерами и 
        координатами в координатном пространстве
        //нашего мувклипа:
        var bounds:Rectangle = sourceMovieClip.getBounds(sourceMovieClip);
        //матрица геометрических трансформаций - отдельная тема для разговора, о ней 
        //можно почитать в эдабовских справочниках.
        //в данном случае она нам нужна, чтобы переместить отпечаток изображения в 
        //систему координат с началом в верхнем левом углу полученного
        // прямоугольника:
        var matrix:Matrix = new Matrix();
            matrix.translate(-bounds.x, -bounds.y);
        //Оччень тонкий и мутный момент. Если мы хотим чтобы пустые пространства были 
        //прозрачными, заливка битмапдаты
        //  ДОЛЖНА быть черной. Простая установка свойства transparent=true без этого 
        //не сработает.
        var bitmapData:BitmapData = new BitmapData(bounds.width,  bounds.height,true,0x000000);
        bitmapData.draw(sourceMovieClip, matrix);
        cachedFrame.bitmapData = bitmapData;
        return cachedFrame;
    }

    public function get sourceMovieClip():MovieClip
    {
        return _sourceMovieClip;
    }

    public function set sourceMovieClip(value:MovieClip):void
    {
        _sourceMovieClip = value;
    }
}
}



Вот как выглядит объект кадра:

package cache2bmp
{
import flash.display.BitmapData;

public class CachedFrameModel
{
    private var _bitmapData:BitmapData;
    private var _x:int;
    private var _y:int;
    private var _width:int;
    private var _height:int;
    private var _numFrame:int;
    private var _action:Function;
    private var _layer:String;
…

Гэттеры и сеттеры описывать не буду, ленивым можно сделать свойства публичными

Далее на очереди эмулятор мувклипа – проигрыватель нашей скэшированной анимации. Сами эдабовцы уверяют, что наибыстрейший способ вывода на экран – это свойство copyPixels() битмапдаты, и у нас нет оснований им не верить Вот что у нас должно получится (класс реализует необходимые для клипа органы управления, присущие MovieClip, которые я комментировать не буду – они элементарны.


package cache2bmp
{
import ru.dijump.wonf.cache2bmp.*;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;

public class CachedMovieClip extends Bitmap
{
    private var _clip:Vector.<CachedFrameModel>;
    private var _currentFrame:int;
    private var _totalFrame:int;
    private var _destinationPoint:Point;
    private var _areaRectangle:Rectangle;
    private var _clearRectangle:Rectangle;

    public function CachedMovieClip(width:int = 150, height:int = 150)
    {
        bitmapData = new BitmapData(width, height, true,0x000000);
        this.width = width;
        this.height = height;
        this._clip = new Vector.<CachedFrameModel>();
        this._destinationPoint = new Point();
        this._areaRectangle = new Rectangle();
        this._clearRectangle = new Rectangle(0, 0, width, height);
        this.currentFrame=1;
    }

    public function gotoAndStop(frame:int = 1):void
    {
        if (frame < 1)
        {
            frame = 1;
        }
        if (frame > totalFrame)
        {
            frame = totalFrame;
        }
        render(frame);
        this.currentFrame = frame;
        this.removeEventListener(Event.ENTER_FRAME, playEnterFrameHandler);
    }

    private function render(frame:int):void
    {
        // считываем данные с нашего объекта-снимка
        destinationPoint.x = clip[frame - 1].x;
        destinationPoint.y = clip[frame - 1].y;
        areaRectangle.width = clip[frame - 1].width;
        areaRectangle.height = clip[frame - 1].height;
        //Очень тонкий момент! Чтобы плеер не рендерил лишнюю муть, навроде очистки битмапдаты, лочим ее:
        bitmapData.lock();
        //Очищаем битмапдату от предыдущего изображения черной заливкой (это единственно верный путь):
        this.bitmapData.fillRect(_clearRectangle,0x000000);
        //рисуем новую картинку
        this.bitmapData.copyPixels(clip[frame - 1].bitmapData, areaRectangle, destinationPoint);
        //и позволяем вывести теперь ее на экран
        bitmapData.unlock();
    }

    public function gotoAndPlay(frame:int = 1):void
    {
        if (frame < 1)
        {
            frame = 1;
        }
        if (frame > totalFrame)
        {
            frame = totalFrame;
        }
        render(frame);
        this.currentFrame = frame;
        this.addEventListener(Event.ENTER_FRAME, playEnterFrameHandler);
    }

    public function play():void
    {
        this.addEventListener(Event.ENTER_FRAME, playEnterFrameHandler);
    }

    public function stop():void
    {
        this.removeEventListener(Event.ENTER_FRAME, playEnterFrameHandler);
    }


    public function get clip():Vector.<CachedFrameModel>
    {
        return _clip;
    }

    public function set clip(value:Vector.<CachedFrameModel>):void
    {
        _clip = value;
        this.totalFrame = value.length;
        this.currentFrame = 1;
        gotoAndStop(1);
    }

    public function get currentFrame():int
    {
        return _currentFrame;
    }

    public function set currentFrame(value:int):void
    {
        _currentFrame = value;
    }

    public function get totalFrame():int
    {
        return _totalFrame;
    }

    public function set totalFrame(value:int):void
    {
        _totalFrame = value;
    }

    public function get destinationPoint():Point
    {
        return _destinationPoint;
    }

    public function set destinationPoint(value:Point):void
    {
        _destinationPoint = value;
    }

    public function get areaRectangle():Rectangle
    {
        return _areaRectangle;
    }

    public function set areaRectangle(value:Rectangle):void
    {
        _areaRectangle = value;
    }

    private function playEnterFrameHandler(event:Event):void
    {
        if (currentFrame != totalFrame)
        {
            render(currentFrame);
            currentFrame++;
        }
        else
        {
            render(1);
            currentFrame = 1;
        }
    }
}
}


Вот, собственно, и все!