Flashアニメーションのキャッシュの例

Flashテクノロジーを使用してアプリケーションを開発する場合、これは非常に多くのグラフィックスとアニメーションを含むゲームに適用されるため、FPSは2〜3のレベルで停止するという結論に達することができます。 これは、最適化を行うときが来たことを意味します。 この場合、まずシステムパフォーマンスに実際に影響するものをまず最適化する必要があります。 ロジックレベルでの最適化による作業の加速は特定のものであり、特定のアプリケーションに依存します。 しかし、アニメーションの高速化は多くのプロジェクトでうまく適用できます。これについては説明します。



アニメーションクリップをキャッシュする方法については、 この記事で既に述べました。

最初に、アニメーションをフレームごとにBitmapDataタイプのオブジェクトにラスタライズしてから、それらを使用してレンダリングすることが本質であることを簡単に思い出します。 同時に、プロセッサ時間はラスターグラフィックスのレンダリングにのみ費やされるため、実際にはかなりのパフォーマンスを達成できます。 ただし、この方法は常に機能するとは限りません。



ただし、ActionScript(パーティクルシステムなど)を使用してエフェクトを実装する場合、キャッシュされたMovieClipに含めることができるフレームは1つだけです。 この場合、この方法は、改善する必要があります。



解決策



ポイントは、以前ではなく、エフェクトの最初の再生中にフレームをキャッシュすることです(「遅延読み込み」)。 その後の起動では、レンダリング用のデータは既に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 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 .








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 .








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クラスは、swcファイルからプロジェクトにエクスポートされたMovieClipです。 キャッシングのフレーム数は実験的に選択されました。



以下はテスト結果です。







図1-キャッシュなしのパーティクル





図2-キャッシングのあるパーティクル



この方法には、いくつかの欠点もあります。 最初の段階では、FPSはキャッシュを使用しない場合と同じです(理論的には、BitmapDataのラスタライズが追加で実行されるため、さらに少なくなります)。 さらに、元のクリップを使用するのとは異なり、ここでのさまざまなエフェクトの数はまだ有限です。 その後の効果はすでにプールから取得されています。 ただし、これはすでに生産性を高めるために必要な悪です(常に何かを犠牲にしなければなりません)。 これを補うために、プールからの効果は連続的にではなく、ランダムに取得されます。 実際には、これはユーザーフレンドリーな多様性を提供するのに十分です。



おわりに



この記事のソースコードを完全なソリューションと見なすべきではありません。 明確にするために、例外的な状況のチェックが不足しており、全体として完全に使用するには、洗練が必要です。 さらに、パーティクルシステムを使用する場合は、例を適用する必要があります。 たとえば、 Partigenを使用する場合 (コードはもともとその最適化のために特別に開発された)、エミッターを手動で開始および停止する必要があります(微妙な違いもいくつかありますが、すでにこの記事の範囲外です)。 ただし、一般的にテストにより、効果のあるキャッシングソフトウェアはパフォーマンスを大幅に向上させることができます。多くの場合、これが許容可能なユーザー速度をアプリケーションに提供する唯一の方法です。



プロジェクトのソースコードはこちらからダウンロードできます



PS:上記は主にパーティクルシステムに基づくエフェクトについてです。 フレームアニメーションの場合、新しいオブジェクトを作成するときにキャッシュを再実行する必要はありません。 1フレーム内では、画像は静止しています。 フレームを公共の場所(静的配列など)に一度保存してから、クリップのすべてのコピーを描画してそこからフレームを取得する方がより正確です。 したがって、パフォーマンスとメモリ消費の点で最も有益です。



トピックに関するリンク:






All Articles