Flashで怒涛のごときイベント処理を捌きまくる3技速いFlash/ActionScriptチューニング入門(3)(2/3 ページ)

» 2010年07月22日 00時00分 公開
[河北啓史チームラボ株式会社]

【第1技】使い終わったイベントは削除

 不要になったイベントリスナをきちんとremoveEventListenerすることは、イベントをチューニングするうえでの基本中の基本です。なかでもEvent.ENTER_FRAMEのイベントリスナは、使用頻度も多いため、しっかりと管理する必要があります。イベントリスナの有効期限は、removeEventListenerされるか、オブジェクトもしくはリスナそのものが削除(ガベージ・コレクションなど)されるまでです。

 注意しなければならないのは、「addEventListenerした対象がremoveChildされたとしても、ENTER_FRAMEイベントは通知され続ける」という点です。これらをしっかりとremoveEventListenerしておかないと、処理負荷の原因となってしまいます。

 ですから、Event.ENTER_FRAMEやTimerEvent.TIMERなど、継続的に通知され続けるようなイベントに関しては、removeChildされたときや、不要になったときは必ずremoveEventListenerをする癖を付けましょう。

 以下に、その実装例を示します。

addEventListener( Event.ENTER_FRAME, trace );
addEventListener( Event.REMOVED_FROM_STAGE, _onRemoved );
                        
                        
// ステージから削除された際にremoveEventListenerする 
function _onRemoved(e:Event){
    removeEventListener( Event.ENTER_FRAME, trace );
    removeEventListener( Event.REMOVED_FROM_STAGE, _onRemoved );
}

注意!

Event.REMOVED_FROM_STAGEとEvent.REMOVEDはまったく別ものです。REMOVED_FROM_STAGEは自分自身がstageからremoveChildされた際にdispatchされますが、REMOVEDは、自分自身であろうと自分にaddChildされた子要素であろうと、stageからのremoveChildであろうとなかろうとdispatchされます。


 適切に削除されず残ってしまったイベントリスナは、目に見えないところでCPUにかなりの負荷を掛けます。removeEventListenerを徹底することは、サービス全体のパフォーマンスだけでなく、ユーザーのほかの作業への影響を減らすことにもつながります。

【第2技】リスナ関数の一括実行

 イベント関連の高速化手法で、特に描画速度に効果があるのが、リスナ関数の一括実行です。特に、Event.ENTER_FRAMEを使って大量のDisplayObjectを同時に動かす場合に効果を発揮します。

 おさらいでも説明したとおり、毎フレームで何かを実行する際にはEvent.ENTER_FRAMEを使います。しかし、大量のMovieClipなどをアニメーションさせる際に、オブジェクト指向的発想でEvent.ENTER_FRAMEを個々にaddEventListenerしてしまうと、かなりの処理負荷となってしまいます。

// 各オブジェジェクトが個々にaddEventListenerするサンプルコード
                      
import flash.display.MovieClip;
import flash.events.Event;
                       
class ExMovieClip extends MovieClip {
    public function ExMovieClip():void {
        addEventListener( Event.ENTER_FRAME, _onEnterFrame );
    }
    private function _onEnterFrame(e:Event):void {
        x += 10;
    }
}

 このような実装自体に問題はないのですが、インスタンスが1000個など大量になってくると、「自分に登録されたリスナ関数を調べて実行する」という処理も大量に実行されてしまうのです。そのため、描画うんぬんに処理負荷が掛かるという話以前に、イベントの処理部分の時間が増加してしまい、結果としてFPSが低下してしまいます。

おのおのにENTER_FRAMEを設定した場合(Flash以外の部分をクリックすると、停止します)

 そこで、おのおのでaddEventListenerを扱うのではなく、「実行したい関数(Listener)を内部的にVector.<Function>などの配列に格納し、少数(可能であれば1つ)のイベントリスナ内でfor文などを用いて一気に実行する」というやり方にすると、イベントの処理部分が大幅に減るため、FPSがかなり改善されます。

// イベントリスナを一括実行するサンプル
          
import flash.display.*;
import flash.events.Event;
          
class ExMovieClip extends MovieClip {
             
    // ENTER_FRAMEを共通して扱うためのSprite
    private static var _dispatcher:Sprite = new Sprite();
    // ENTER_FRAMEを共通して扱うためのSprite
    private static var _listeners:Vector.<Function> = new Vector.<Function>();
              
    public function ExMovieClip():void {
        // リスナ登録がない場合に登録
        if( !_dispatcher.hasEventListener(Event.ENTER_FRAME) ){
            _dispatcher.addEventListener(Event.ENTER_FRAME, function(e:Event):void{
                var len:uint = _listeners.length;
                for( var i:int = 0; i < len; i++ ){
                  _listeners[i](e);
              }
            })
        }
        addEventListener( Event.ENTER_FRAME, _onEnterFrame );
    }
              
    // addEventListenerをoverrideし、ENTER_FRAMEの場合は_listenersに登録
    override public function addEventListener( type:String, listener:Function, useCapture:Boolean = false,
                                               priority:int = 0, useWeakReference:Boolean = false ):void{
        if ( type == Event.ENTER_FRAME) {
            _listener.push(listener);
        }else {
            super.addEventListener(type, listener, useCapture, priority, useWeakReference);
        }
    }
              
    // removeEventListenerをoverrideし、ENTER_FRAMEの場合は_listenersから削除
    override public function removeEventListener( type:String, listener:Function,
                                                  useCapture:Boolean = false ):void{
        if ( type == Event.ENTER_FRAME) {
            for (var i:int = 0; i < _listener.length; i++) {
                if ( _listener[i] == listener ) _listener.splice(i, 1);
            }
        }else {
            super.removeEventListener(type, listener, useCapture);
        }
    }
          
    private function _onEnterFrame(e:Event):void {
        x += 10;
    }
}
ENTER_FRAMEを一括処理した場合(Flash以外の部分をクリックすると、停止します)

注意!

for文などで一気に処理する場合、addEventListenerのpriorityやuseCaptureの設定を加味しつつ処理させることは非常に難しいので、それらの設定を無視できるような処理で使いましょう。。


 余談ですが、このイベントリスナをまとめるやり方は、TimerEvent.TIMERでも使えます。同一のイベントで、かつ大量の処理が必要なイベントに関しては、このイベントリスナの一括実行を検討するのもいいでしょう。

 次ページでは、MouseEventのチューニングを解説します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。