2012年1月21日土曜日

nineleap.enchant.jsの拡張(2)

前のエントリで紹介したNodeのフェードイン・アウト機能を利用しつつ、 enchant.js に標準添付されているプラグイン nineleap.enchant.js を拡張して、次の機能を実現するプラグインを作成します。

  • 「START」「GAME OVER」をフェードイン・アウトによるトランジション効果を利用して表示する
  • ローカル環境での実行時に、「GAME OVER」画面をタッチ・キーダウンすると「もう一度プレイ」できるようにする

これを、nineleap_fade.enchant.js というファイル名で作成するものとします。 なお、この開発にあたっては、次の点に注意しました。

  • nineleap.enchant.jsは修正しない。
  • ゲーム本体のプログラム(game.js)の修正は、最小限にとどめる。

というわけで、以下、これを実現するコードです。

/**
* ローカル環境でのデバッグ時に、ゲームオーバー画面のクリックまたはキーダウンで
* 「もう一度プレイする」を可能にするためには、この値をtrueにする。
* nineleapに投稿する際にはtrueにしておく。
*/
var NINELEAP_RELEASE_MODE = false;
var START_IMAGE = 'start.png';
var END_IMAGE = 'end.png';

enchant.nineleap_fade = {};

enchant.nineleap_fade.Game = enchant.Class.create(enchant.nineleap.Game, {
    initialize: function(width, height) {
        enchant.nineleap.Game.call(this, width, height);
        this.sceneTransition = false;
        this.overwriteLoadEventListener();
    },

    // enchant.nineleap.Gameにおけるloadイベントリスナを解除・上書き
    overwriteLoadEventListener: function(){
        this._listeners['load'] = [];
        this.addEventListener('load', function() {
            this.startScene = new SplashScene();
            this.startScene.id = 'startScene';
            this.startScene.image = this.assets[START_IMAGE];

            this.pushScene(this.startScene);
            fadeIn(this.startScene, 10);

            this.endScene = new SplashScene();
            this.endScene.id = 'endScene';
            this.endScene.image = this.assets[END_IMAGE];

            this.addTransitionEventListeners();
        });
    },

    // トランジションのためのイベントリスナを登録
    addTransitionEventListeners: function(){
        var game = this;
        this.startScene.addEventListener('touchend', function() {
            game.startScene.removeEventListener('touchend', arguments.callee);
            if (game.started == false && game.sceneTransition == false) {
                game.sceneTransition = true;
                if (game.onstart != null) game.onstart();
                game.onGameStartTouched();
            }
        });

        this.endScene.addEventListener('touchend', function(){
            if(game.sceneTransition == false){
                game.sceneTransition = true;
                game.endScene.removeEventListener('touched', arguments.callee);
                game.onGameEndTouched();//fadeout endScnene and popScene
            }
        });

        this.addEventListener('keydown', function() {
            if (game.currentScene == game.startScene && game.sceneTransition == false){
                game.sceneTransition = true;
                game.removeEventListener('keydown', arguments.callee);
                if (game.started == false) {
                    if(game.onstart != null) game.onstart();
                    game.onGameStartTouched();//fadeout startScnene and popScene
                }
            }
        });

        this.addEventListener('keydown', function() {
            if (game.currentScene == game.endScene && game.sceneTransition == false){
                game.sceneTransition = true;
                game.removeEventListener('keydown', arguments.callee);
                game.onGameEndTouched();//fadeout endScnene and popScene
            }
        });
    },

    //ユーザによるゲーム開始画面のタッチ後に実行される関数,
    onGameStartTouched: function(callback){
        var game = this;
        game.started = true;
        gameStart = true;   // deprecated
        fadeOut(game.startScene, 10, function(){
            if(game.currentScene == game.startScene){
                game.popScene();
            }
            if(callback){
                callback();
            }
            game.sceneTransition = false;          
        });
    },

    //ゲームオーバーのときの終了処理を実行する
    end: function(score, message){
        this.started = false;
        enchant.nineleap.Game.prototype.end.call(this, score, message);
        fadeIn(this.endScene, 10);
    },

    //ユーザによるゲーム終了画面のタッチ後に実行される関数
    onGameEndTouched: function(callback){
        var game = this;

        gameStart = false;   // deprecated

        fadeOut(game.endScene, 10, function(){
            if(game.currentScene == game.endScene){
                game.popScene();
            }
        });

        if(NINELEAP_RELEASE_MODE){
            return;
        }

        fadeOut(game.getGameNode(), 10, function(){

            if(game.reset){
                game.reset();
            }

            fadeIn(game.getGameNode(), 10, function(){

                game.pushScene(game.startScene);

                fadeIn(game.startScene, 10, function(){
                    game.addTransitionEventListeners();
                    if(callback){
                        callback();
                    }
                    game.sceneTransition = false;
                });
            });
        });
    },

    /**
     * ゲーム終了・リセット再開時に、
     * フェイドイン・フェイドアウトされるゲーム画面のnodeを返す関数。
     * デフォルトではgame.rootSceneを返すので、開発者側で必要に応じて
     * game.getGameNode = function(){ return fooScene };のように定義して
     * おくことで内部的に呼び出される。
     */
    getGameNode: function(){
        return this.rootScene;
    },

    /**
     * NINELEAP_RELEASE_MODE==falseのときに、自前でゲームを再開するための、
     * 各種ゲーム状態(スコア・自機位置・敵位置など)の初期化処理を記述するための関数。
     * game.reset = function(){....}; のように定義しておくことで内部的に呼び出される。
     */
    reset: function(){
        alert("reset関数を実装してください");
    }

});

以下、使い方です。

  • 開発者は、この冒頭の NINELEAP_RELEASE_MODE 定数を、 9leapに投稿時には true に、ローカルでデバッグしたり遊んだりするときには false に しておく必要があります。
  • Gameをインスタンス化した後に、そのメンバとして getGameNode関数と、reset関数を上書き再定義しておく必要があります。
  • ゲームオーバー時なには、game.started変数をfalseにセットする必要があります。
  • ゲーム終了時・再開時の「ゲーム画面は見えているが、プレイすることができない瞬間」を実現するために、 enterframeのイベントリスナの関数内の処理の冒頭で、game.started変数をチェックして、以降の処理をキャンセルする コードを必要に応じて追加する必要があります。
  • ゲームの実行時に登録したイベントリスナは、不要になった時点で削除してください。たとえば、 this.removeEventListener('enterframe', arguments.callee); のようなコードを 適切な箇所で実行してください。余計なイベントリスナが残っていると、ゲームを「もう一度プレイ」する際、 イベントリスナの登録が多重的に実行されてしまい、ゲームが正常に動作しなくなることがあります。

たとえば、enchant.jsのサンプルである、examples/action/game.jsにおいて、 この nineleap_fade.enchant.js を適用するならば、次のような修正をすることになります。

*** examples/action/game.js 2012-01-11 03:47:36.000000000 +0900
--- examples/action/game_fade.js 2012-01-20 21:47:17.000000000 +0900
***************
*** 62,68 ****
--- 62,85 ----
          bear.jumping = true;
          bear.jumpBoost = 0;
          bear.image = game.assets['chara1.gif'];
+ 
+  game.reset = function(){
+         bear.x = 8;
+         bear.y = -32;
+         bear.vx = 0;
+         bear.vy = 0;
+         bear.ax = 0;
+         bear.ay = 0;
+         bear.pose = 0;
+         bear.jumping = true;
+         bear.jumpBoost = 0;
+         stage.x = 0;
+  };
+ 
          bear.addEventListener('enterframe', function(e) {
+             if(game.started == false){
+                 return;
+             }
              var friction = 0; 
              if (this.vx > 0.3) {
                  friction = -0.3;
***************
*** 162,167 ****
--- 179,185 ----
              this.y = dest.y-2;
  
              if (this.y > 320) {
+                 game.started = false;
                  game.assets['gameover.wav'].play();
                  var score = Math.round(bear.x);
                  this.frame = 3;
***************
*** 171,185 ****
                      this.y += Math.min(Math.max(this.vy, -10), 10);
                      if (this.y > 320) {
                          game.end(score, score + 'mで死にました');
                      }
                  });
-                 this.removeEventListener('enterframe', arguments.callee);
              }
          });
!         var stage = new Group();
          stage.addChild(map);
          stage.addChild(bear);
          stage.addEventListener('enterframe', function(e) {
              if (this.x > 64 - bear.x) { 
                  this.x = 64 - bear.x;
              }
--- 189,206 ----
                      this.y += Math.min(Math.max(this.vy, -10), 10);
                      if (this.y > 320) {
                          game.end(score, score + 'mで死にました');
+                 this.removeEventListener('enterframe', arguments.callee);
                      }
                  });
              }
          });
!         stage = new Group();
          stage.addChild(map);
          stage.addChild(bear);
          stage.addEventListener('enterframe', function(e) {
+                 if(game.started == false){
+                     return;
+                 }
              if (this.x > 64 - bear.x) { 
                  this.x = 64 - bear.x;
              }

0 件のコメント:

コメントを投稿