2012年1月20日金曜日

Nodeのフェードイン/アウト処理の実現方法

「Nodeをフェードイン・フェードアウトさせるしくみ」を開発するにあたって、 まずは、「ある対象ノードに対してフレームごとに何らかの処理を実行させるしくみ」を作ってみます。

対象となるノードとして、Spriteではなく、GroupやSceneを与えても、ちゃんと動作させることが目的です。 現在のenchant.jsでのGroupやSceneのオブジェクトは、階層的なひとまとまりのHTML要素ではないので、 単なるjQueryによるアニメーション指定などでは、うまく動かないのです。fpsがうまく合わないと、 画面がちらつく原因にもなります。

なお、ひとつのNodeに対して、たとえばフェードインとフェードアウトが同時に実行されるのを許してしまうと、 画面がちらついたり、悪くすれば無限ループに陥ってしまうようなことがあるかもしれません。 そこで、たとえばフェードイン処理が最後まで完了してから、 次のフェードアウト処理が開始されるというように、 処理をキューに格納して順次実行するような仕組みを作ることにします。

具体的には、以下のようになります。

enchant.effect = {  };

/**
 * ある対象ノードに対してフレームごとの処理をするしくみ
 */
enchant.effect.Action = enchant.Class.create({
    /**
     * フレームごとの処理
     * @param {Node} taregtNode 対象となるノード
     * @param {Function} tickFunc フレームごとに実行される関数
     */
    initialize: function(targetNode, tickFunc){
     this.targetNode = targetNode;
     this.tickFunc = tickFunc;
     this.frame = 0;

     if(! targetNode.queue){
         //対象となるノードにキューがなければ作る
         targetNode.queue = [];
         targetNode.addEventListener('enterframe', function(){
          //フレームごとの処理
          if(targetNode.queue &&0 < targetNode.queue.length){
              //キューが空でなければ最初のアクションを実行
              targetNode.queue[0].tick();
          }else{
              //キューが空ならフレームごとの処理を終了
              targetNode.removeEventListener('enterframe', arguments.callee);
              delete targetNode.queue;
          }
         });
     }
     // キューに自身を登録する
     targetNode.queue.push(this);
    },

    /**
     * フレームごとの処理
     */
    tick: function(){
     this.tickFunc(this);
     this.frame++;
    }
});

次に、上記のコードを利用しつつ、指定したノードについて フェードイン・フェードアウトをするしくみを実現します。


/**
 * ノードとその子ノードに対して、透明度を設定する
 */
enchant.effect.setOpacity = function(targetNode, opacity){
    if(targetNode instanceof Entity){
     targetNode.opacity = opacity;
    }
    if(targetNode.childNodes){
        for (var i = 0, len = targetNode.childNodes.length; i < len; i++) {
         var node = targetNode.childNodes[i];
         setOpacity(node, opacity);
     }
    }
};

/**
 * ノードとその子ノードに対して、徐々に透明度を変化させる
 * @param {Node} taregtNode 対象となるノード
 * @param {Number} from 透明度の初期値
 * @param {Number} to 透明度の最終値
 * @param {Number} time 透明度変化の所要フレーム数
 * @param {Function} onEndCallback 終了時に実行される関数
 */
enchant.effect.fade = function(targetNode, from, to, time, onEndCallback){

    setOpacity(targetNode, from);

    new Action(targetNode, function(action){
     if(action.frame < time){
         var opacity = from + (action.frame / time) * (to - from) ;
         setOpacity(targetNode, opacity);
     }else{
         setOpacity(targetNode, to);
         if(onEndCallback){
          onEndCallback();
         }
         targetNode.queue.shift();
     }
    });
};

/**
 * フェードイン
 * @param {Node} taregtNode 対象となるノード
 * @param {Number} time 透明度変化の所要フレーム数
 * @param {Function} onEndCallback 終了時に実行される関数
 */
enchant.effect.fadeIn = function(targetNode, time, onEndCallback){
    fade(targetNode, 0, 1, time, onEndCallback);
};

/**
 * フェードアウト
 * @param {Node} taregtNode 対象となるノード
 * @param {Number} time 透明度変化の所要フレーム数
 * @param {Function} onEndCallback 終了時に実行される関数
 */
enchant.effect.fadeOut = function(targetNode, time, onEndCallback){
    fade(targetNode, 1, 0, time, onEndCallback);
};

jQueryによるアニメーションのプログラミングように、メソッドチェインを用いていろいろな効果を重ね合わせたり、 thenメソッドの呼び出し時の引数で、フェード処理が終了した時のコールバック関数を指定するといった書き方を できるようにするというのも、面白いかもしれませんが、ひとまずこのくらいの内容でとどめておくことにします。

0 件のコメント:

コメントを投稿