
Кеширование MovieClip
Когда я заметил, что мои игры сильно тормозят с релизной графикой я решил её как-то оптимизировать. Стандартный cacheAsBitmap не помог. Порывшись на форумах и гуглах я выяснил, что лучше самому кешировать клипы в Bitmap. Даже нашёл исходники кеширования от TouchMyPixel, разработчиков игры Scary Girl. Но он был слишком не универсальным, заставлял художника сделить за размером кадров. Поэтому взяв его за основу я написал свой класс, который превращает любой MovieClip в набор Bitmap'ов с учётом всех смещений. В дальнейшем этот класс можно будет использовать для создания атласов анимации, которые можно будет использовать при портировании Flash игр на другие платформы.
Обратите внимание, что используя статичный метод getClip у нас создаётся библиотека клипов, откуда затем используются фреймы и оффсеты, чтобы не плодить их копии.
Update. Исправил ошибки, добавил лейблы.
package elmortem.animations {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
public dynamic class CacheMovieClip extends Sprite {
private var clip:MovieClip;
private var bmp:Bitmap;
public var frames:Vector.<BitmapData>;
public var offsets:Vector.<Point>;
public var labels:Vector.<String>;
public var currentFrame:int;
public function CacheMovieClip() {
clip = null;
addChild(bmp = new Bitmap());
frames = new Vector.<BitmapData>();
offsets = new Vector.<Point>();
labels = new Vector.<String>();
currentFrame = 1;
}
public function free():void {
if (parent != null) parent.removeChild(this);
clip = null;
removeChild(bmp);
bmp = null;
frames = null;
offsets = null;
labels = null;
}
public function buildFromLibrary(Name:String):void {
var cls:Class = getDefinitionByName(Name);
if(cls != null) buildFromClip(new cls());
}
public function buildFromClip(Clip:MovieClip):void {
if (Clip == null) throw("Clip not found.");
if (clip != null) {
clip = null;
frames = new Vector.<BitmapData>();
offsets = new Vector.<Point>();
}
clip = Clip;
var r:Rectangle;
var bd:BitmapData;
var m:Matrix = new Matrix();
var i:int;
var len:int = clip.totalFrames;
for (i = 1; i <= len; ++i) {
clip.gotoAndStop(i);
r = clip.getBounds(clip);
bd = new BitmapData(Math.max(1, r.width), Math.max(1, r.height), true, 0x00000000);
m.identity();
m.translate(-r.x, -r.y);
m.scale(clip.scaleX, clip.scaleY);
bd.draw(clip, m);
frames.push(bd);
offsets.push(new Point(r.x * clip.scaleX, r.y * clip.scaleY));
labels.push(clip.currentLabel);
}
gotoAndStop(1);
}
public function get totalFrames():int {
return frames.length;
}
public function get currentLabel():int {
if(totalFrames == 0) return "";
return labels[currentFrame - 1];
}
public function gotoAndStop(frame:int):void {
if (totalFrames == 0) return;
currentFrame = Math.max(1, Math.min(totalFrames, frame));
bmp.bitmapData = frames[currentFrame - 1];
bmp.x = offsets[currentFrame - 1].x;
bmp.y = offsets[currentFrame - 1].y;
}
/* STATIC */
static private var clips:Object = { };
static public function getClip(Name:String):CacheMovieClip {
if(clips[Name] == null) {
var m:CacheMovieClip = new CacheMovieClip();
m.buildFromLibrary(Name);
clips[Name] = m;
}
var clip:CacheMovieClip = new CacheMovieClip();
clip.frames = clips[Name].frames;
clip.offsets = clips[Name].offsets;
clip.labels = clips[Name].labels;
clip.gotoAndStop(1);
return clip;
}
}
}
Обратите внимание, что используя статичный метод getClip у нас создаётся библиотека клипов, откуда затем используются фреймы и оффсеты, чтобы не плодить их копии.
Update. Исправил ошибки, добавил лейблы.
- +28
- elmortem
Комментарии (48)
2. Прямоугольник, который отдается в этих методах имеет нецелые вообще говоря параметры. То-есть чтобы опять таки не получить обрезки по краям — применять Math.ceil для ширины/высоты и Math.floor для x и y.
3. Кстати матрицу пересоздавать каждый луп цикла не стоит, не мучайте гарбадж коллектор, да и вообще подобный подход сумарно на всем коде съэкономит перфоманс.
а если в мувиках, в кадрах уже лежат картинки (такая обертка битмапа в мувик) производительность по сравнению с кешированием меняется?
А камраду elmortem плюс конечно.
1. Создаешь прямоугольник и конвертируешь его в MovieClip, прямоугольник обязательно должен быть установлен в координаты 0,0 т.е. в левый верхний угол.
2. Добавляешь данный клип на отдельный слой своего клипа, где у тебя анимация, подгоняешь размер прямоугольника, чтобы вся анимация оставалась внутри него. Присваиваешь имя прямоугольнику e_bounds.
И ваяля, ничего за края не выходит и вся анимация отображается как надо.
Пришлось изучить весь код тачмайпиксель, чтобы увидеть этот нюанс
В этом подходе очень экономится память.
Поэтому у себя сделал примерно так как автор топика. Точнее, у меня отдельно класс «куча bmp кадров» и отдельно «bmp мувиклип», который по интерфейсу как Мувиклип, сам играет себя, в конце может дернуть экшн итд.
Что если в скешированом мувике есть лейблы и управляющий код в кадрах? Как тогда? :)
тоже использую переработанный класс от ТрогатьМойПиксель :)
сам не догадался до обрезания картинок…
из добавленного — сохраняю названия лэйблов, на случай если необходимо знать в каком месте анимация…
еще не понял зачем дублировать этот исходник, если можно пользоваться классом надстройкой который и будет заниматься анимацией и отрисовкой тупо используя линк на единственный экземпляр ресурса с битмап датами/фреймами и прочей тонной данных. имхо надо бы разделить понятия. дискуссия на эту тему приветствуется.
еще расскажи пожалуйста как ты реализовал переход по лейблу на нужную анимацию? типа gotoAndPlay(label:String) как у тебя работает? Был бы очень признателен за инфу. Сам выбрал довольно брутальный метод — лейблы это тупо uint и переход осуществляется по uint на номер лейбла а не по имени. Это не наглядно, а когда не наглядно голова болит.
Еще такой вопрос всплыл. Работая с физикой мы имеем дело с поворотами и пр. Приходится использовать матрицы и draw. Хотя copyPixel круче в разы. Насколько быстрее флеш рисует битмап дату (методом draw) чем не сложный вектор? И стоит ли заморачивать память кешированием всего и вся? Кто-нибудь делал сравнения? elmortem я как понял у тебя сейчас конкретная игра с этой технологией — поделись инфой сколько памяти выделяется под кеш?
За пост спасибо. Когда писал свой компилятор особо открытых кодов не было, приходилось сильно ломать голову)) Ну и да, согласен, можно сделать автоматический экспортер графики (резалку спрайтщитов)(куда нибудь в png) но надо подумать о сохранении данных об анимации/кадрах.
Про встроенные клипы. Ими можно пожертвовать, если цена — значительное ускорение отрисовки. Или просто не кешировать такие объекты, если их не много (а обычно это так и есть).
Про дублирование. Оно сделано как раз для того, чтобы не плодить одинаковые данные. Если у нас есть 10 одинаковых объектов, то они будут использовать один набор кадров. Но при этом они независимо анимируются.
Про лейблы. Переход по именам лейблов не делел и вообще не представляю, нафига это нужно. При работе с анимацией я всё равно использую номера кадров. Но сделать это достаточно просто. Либо записать в Object номер кадра по ключу имени кадра, либо перебрать массив labels — индексы массивов совпадают.
Про повороты. В битмап флеш рисует очень быстро, но. В нашем случае это нужно только один раз. Потом Bitmap добавляется в Sprite и крутим мы уже его, штатными средствами.
Про игру. Да, писал всё это под конкретную задачу — ускорить игру. Конкретных цифр не знаю. Но на глаз игра ускорилась раза в 3. Всё и вся, конечно, кешировать не нужно. Например я персонажей не кеширую, они сложно-составные, но их не много. Интерфейс тоже не кеширую. Только блоки, декорации, объекты.
Пожалуйста. Атлас делается в нашем случае просто. Графику — в PNG, данные в XML.
Про дублирование я плохо выразился — у меня двиг рисует всё в одну битмап дату — экран. Олдскульный такой подход. Сценграф флеша не используется при этом, никакие там new Bitmap() и т.п. не нужны. Поэтому для меня логично что ресурс как таковой не дублируется никаким способом. В твоем случае это нужно чтобы был Bitmap. Так как подходы разные то и методы разные. Мне например удобнее иметь совершенно отдельный класс который работает с единственным экземпляром ресурса переходя по кадрам и отрисовывая его на тот самый экран. Ну собственно в этом контексте и спрашивал что будет быстрее draw несложного вектора или draw того же вектора но кешированного в битмап дату?
Про лейблы всё очень просто — если ты используешь номера кадров это не наглядно. Ты потом сам читаешь код и должен вспоминать куда нафиг уходит этот gotoAndStop(182)? Можно сделать константу const ANIM_RUN:uint=182; и получим gotoAndStop(ANIM_RUN) что уже нагляднее, но удобнее было бы использовать лейблы чтобы не создавать лишнего кода. Попробую использовать Object для этой цели, вроде будет шустрее чем прокручивать список лейблов сравнивая строки.
Про экономию за счет записи множества экземпляров битмап_даты наверное соглашусь, но как-то после 2д опыта на опенГЛ такая мысль в голову просто не пришла)) Но я уже не раз сталкивался с тем что флеш вообще все законы рушит, так что наверное надо попробовать твой способ. Повторюсь спасибо за пост. Вообще приятно читать твои посты по реализации тех или иных методов — жду новых! Как вариант кстати не хватает цельного поста об организации своего редактора. Я б может и написал но у меня орфография кода хромает))
Прямоугольники кадров есть в самих кадрах, т.к. у нас каждый кадр рисуется в отдельную битмапу. Которая по размерам как раз равно кадру.
На счёт лейблов — пофиг. Там совсем мелочи, к тому же не дублируются.
Но если bitmap data просто создается на все время жизни флэшки (как кэш, например), то и удалять их явно не нужно.