(ゲームイメージ)

別窓表示

ゲームオーバー画面を表示する

基準を変える

現状、ゲーム開始してから10秒でゲームオーバーだったと思いますが、これをドフォーレ同様残機制に変えたいと思います。

現状残機制の考え方をしていないということは、残機数を制御する必要が無いということで、つまり、値が設定されている器(変数)がありません。まずはこれを追加する必要があります。

では、残機数の値はどこに追加すればいいでしょうか。昔ゲームをやった思い出をひっくり返してみますと・・・。スコア表示の隣らへんにハートマークとかが出てきていて減ったり増えたりしていたような気がします。となると、スコアと扱いが近いような気がします。

とりあえず、スコアの近くに変数を作ってみましょうか。

    var score = 0;
    var attacking = 0; // 攻撃状態を保っているフレーム数
    var lifecount = 3; // 追加

適当にlifecountという変数をなんとなく3機で作ってみました。あとは、この値を減らす処理と、値が0になった時の処理を作ればよさそうです。

では、どういう時にこの値が減るか、ですが。敵が右端に到着した時、ということになるかと思います。この時の処理をいつもどおり2例書いてみます。

敵自身の状態として)

var Enemy = enchant.Class.create(enchant.Sprite, {
    initialize: function(){
//
// (中略)
//
    },
    onenterframe: function(){
        if(this.within(player) && player.isattacking >= 0){
            score++; //スコアを1足す
            game.rootScene.removeChild(this);
        }
        // ここに追加してみた。
        else if(this.x >= 320){ // 右端に到達した場合
            lifecount--; // 機数を減らす
            game.rootScene.removeChild(this); // カウントしたので消しておく
        }
    }
});
//
// (中略)
//
game.rootScene.tl.then(function() {
    var enemy = new Enemy();
}).delay(30).loop();
//
// (中略)
//
var score = 0;
var lifecount = 3; // 追加

画面の状態として)

var score = 0;
var attacking = 0;
var lifecount = 3;

game.rootScene.on('enterframe', function(){
    if( player.frame == 16) {
        attacking ++;
    }
    if( attacking == 3 ) {
        player.attackend();
        attacking = 0;
    }        
    for(var i = 0; i < enemies.length; i ++){
        if(player.within(enemies[i]) && attacking > 0 ) {
            game.rootScene.removeChild(enemies[i]);
            enemies.splice(i--, 1);
            score ++;
        }
        // ここに追加してみた
        else if(enemies[i].x >= 320){
            game.rootScene.removeChild(enemies[i]);
            enemies.splice(i--, 1); // カウントした対象は消しておく
            lifecount--;
        }
    }
});

次に、値が0になった時ですが、これはゲームオーバーの処理を変えることになりそうです。今のゲームオーバーの処理は、playerという始まり方をしているところで処理していますので、プレイキャラクターが存在して10秒、という測り方をしているようですね。まずはこれをコメントアウトしてしまいます。今回はプレイキャラクターの存在時間をベースにゲームオーバーを決めることはしませんので。

/*
player.tl.delay(game.fps * 10).then(function(){
    alert('game over! score: ' + score);
    game.stop();
});
*/

で、この代わりのゲームオーバーを別のところに作ってみます。

  • 作り先
    • 今回はゲームシーンの処理とする方がしっくりくるように思います。(もちろん、敵の定義[lifecountを0にしたらゲームオーバー処理へ]として追加してもいいですが)。
  • どういう処理を足すか
    • 「機数が0になっているならゲームオーバーのアラートを出しゲームをストップする」とすればいいはずです。

となると、こういう感じでしょうか。

var lifecount = 3;

game.rootScene.on('enterframe', function(){
//
// (中略)
//
    // 機数が0になった場合、アラートを出してゲーム終了
    if(lifecount === 0){
        alert('game over! score: ' + score);
        game.stop();
    }
});

これで一応、3回右端に到達したことが確認されたら「game over! score: xx」のアラートウィンドウが出て終わります。しかし、残りの機数やスコアがプレイ中に確認できないのは結構不便ですね。思い通り動いていない時の原因を推測しにくいですし。

というわけで、ついでにどこかに数字を出せるようにしてみます。今回は画像ではなく文字ですのでSpriteではなくLabelを使えば良さげです。

敵自身の状態として)

var Enemy = enchant.Class.create(enchant.Sprite, {
    initialize: function(){
//
// (中略)
//
    },
    onenterframe: function(){
        if(this.within(player) && player.isattacking >= 0){ // 攻撃して倒した場合
            game.rootScene.removeChild(this);
            score++; //スコアを1足す
            info.text = 'Life : ' + lifecount + ' score : ' + score; // 追加。
        }
        // ここに追加してみた。
        else if(this.x >= 320){ // 攻撃を受けずに右端に到達した場合
            game.rootScene.removeChild(this);
            lifecount--; // 機数を減らす
            info.text = 'Life : ' + lifecount + ' score : ' + score; // 追加。
            game.rootScene.removeChild(this); // カウントしたので消しておく
        }
    }
});

//
// (中略)
//

var score = 0;
var lifecount = 3; // 追加
var info = new Label('Life : ' + lifecount + ' score : ' + score ); // 追加。画面左上に出すラベル
game.rootScene.addChild(info); // 追加。ラベルを画面に埋め込む

画面の状態として)

var lifecount = 3;
var info = new Label('Life : ' + lifecount + ' score : ' + score ); // 追加。画面左上に出すラベル
game.rootScene.addChild(info); // 追加。ラベルを画面に埋め込む

game.rootScene.on('enterframe', function(){
  if( player.frame == 16) {
      attacking ++;
  }
  if( attacking == 3 ) {
      player.attackend();
      attacking = 0;
  }        
  for(var i = 0; i < enemies.length; i ++){
      if(player.within(enemies[i]) && attacking > 0 ) {
          game.rootScene.removeChild(enemies[i]);
          enemies.splice(i--, 1);
          score ++;
          info.text = 'Life : ' + lifecount + ' score : ' + score; // 追加。
      }
      // 敵が右端ラインを超えた場合の処理
      else if(enemies[i].x >= 320){
          game.rootScene.removeChild(enemies[i]);
          enemies.splice(i--, 1);
          lifecount--;
          info.text = 'Life : ' + lifecount + ' score : ' + score; // 追加。
      }
  }
});

ゲームオーバー表示を変える

まだもう少し変えてみます。現状、アラートメッセージで「game over! score :xxx」とだけ出てきますが、あまり実際にやっているゲームでは見ないやり方だと思います。とりあえず、黒い画面でゲームオーバーのお知らせでも出してみます。

こんな感じで。

画像

実際のゲームオーバー画面の作り方ですが、ここで少しGoogle先生にお問い合わせしてみましょう。おそらくSceneを使えば…というのがいくつか出てくると思います。早速使ってみましょう。今までゲームオーバーの処理をしていたあたりに加えればいいでしょう。

before)

    game.rootScene.on('enterframe', function(){
//
// (中略)
//
        if(lifecount === 0){ // ゲームオーバーの状態と判定された時
            alert('game over! score: ' + score);
            game.stop();
        }
    });

このあたりを変更してみましょう。最後にゲームを終わらせることに変更は無いので、実際に変更するのはalertの1行だけです(追加量は結構ありますが)。

after)

game.rootScene.on('enterframe', function(){
//
// (中略)
//
    if(lifecount === 0){ // ゲームオーバーの状態と判定された時
        var endscene = new Scene(); // ゲームオーバー画面
        endscene.backgroundColor = '#666666'; // 背景色をやや濃いめの灰色にする
        var endlabel = new Label('ヤラレタ...<br>Score : ' + score); // ゲームオーバー画面で表示する文字
        endlabel.color='#cccccc'; // 文字色を薄めの灰色にする
        endlabel.y = 200; // 真ん中より少し下らへんに文字を表示
        endlabel.textAlign = 'center'; // 真ん中寄せ
        endscene.addChild(endlabel); // ラベルをゲームオーバー画面の中に設定
        game.pushScene(endscene); // 実際にゲームオーバーのシーンに変更する
/*
        alert('game over! score: ' + score);
*/
        game.stop();
    }
});

書き足す量は少々多いですが、ここまで読んでこれた方ならなんとか自力で追加することもできつつあるのではないかと思います。

ちょっと長いですが、ソース全体像もここに書いておきます。コメントや細かい順序は変えているかもしれませんが、そこはご愛嬌ということでお願いします。

敵/プレイキャラクター内での処理を多めにした場合)

enchant(); // initialize

window.onload = function(){

var game = new Core(320, 320);
game.preload('chara7.png', 'chara6.png', 'icon0.png'); // preload image
game.fps = 20;

game.onload = function(){

    // make new class for player
    var Player = enchant.Class.create(enchant.Sprite, {
        initialize: function(){
            enchant.Sprite.call(this, 32, 32);
            this.image = game.assets['chara7.png'];
            this.x = 288;// add:右側に表示。320-32の位置
            this.frame = 10;                   // set image data
            this.isattacking = -1; // 攻撃状態確認用。0未満がデフォル
            game.rootScene.addChild(this);     // add to canvas
        },
        attackstart: function(){
            this.frame = 16; // 斬っている瞬間の画像
            this.isattacking = 0; // 攻撃状態確認0回目
        },
        attackend: function(){
            if( this.isattacking < 0 ) return; // 攻撃状態では無い場合は後続処理をしない
            if( this.isattacking == 3 ) {
                this.frame = 10; // 普段の画像
                this.isattacking = -1;
            } else {
                this.isattacking ++;
            }
        }
    });

    // make new class for apple
    var Apple = enchant.Class.create(enchant.Sprite, {
        initialize: function(){
            enchant.Sprite.call(this, 16, 16);
            this.image = game.assets['icon0.png']; // set image
//            this.moveTo(16, player.y + 8);       // move to the position
            this.moveTo(300 , player.y + 8);       // 変更
//            this.tl.moveBy(320, 0, 30);        // set movement
            this.tl.moveBy(-320, 0, 30);        // 変更
            this.frame = 15;                   // set image data
            game.rootScene.addChild(this);     // add to canvas
        }
    });

    // make new class for enemy
    var Enemy = enchant.Class.create(enchant.Sprite, {
        initialize: function(){
            enchant.Sprite.call(this, 32, 32);
            this.image = game.assets['chara6.png']; // set image
//            this.moveTo(320, Math.floor(Math.random() * 320)); // set position
            this.moveTo(0, Math.floor(Math.random() * 320)); // 変更
            this.scaleX = -1;
//            this.tl.moveBy(-360, 0, 160);      // set movement
            this.tl.moveBy(360, 0, 160);      // 変更
            this.frame = 9;                   // set image data
            game.rootScene.addChild(this);     // canvas
        },
        onenterframe: function(){
            if(this.within(player) && player.isattacking >= 0){
                score++; //スコアを1足す
                info.text = 'Life : ' + lifecount + ' score : ' + score;
                game.rootScene.removeChild(this);
            }
            if(this.x >= 320){
                lifecount--; // ライフを1減らす
                info.text = 'Life : ' + lifecount + ' score : ' + score;
                game.rootScene.removeChild(this);
            }
        }
    });

    var player = new Player();

    // generate enemy every 60 frames
    game.rootScene.tl.then(function() {
        var enemy = new Enemy();
    }).delay(30).loop();                    // wait 60 frames and loop it!

    // add event listener (called when click/touch started)
    game.rootScene.on('touchstart', function(evt){
        player.y = evt.localY;    // set position to touch-y position
        player.attackstart();
    });

    // add event listener (called when click/touch moved)
    game.rootScene.on('touchmove', function(evt){
        player.y = evt.localY;    // set position to touch-y position
    });

    var score = 0;
    var lifecount = 3; // 追加
    var info = new Label('Life : ' + lifecount + ' score : ' + score );
    game.rootScene.addChild(info);

    game.rootScene.on('enterframe', function(){
        player.attackend();
        if(lifecount === 0) { // ゲームオーバーの状態
            var endscene = new Scene(); // ゲームオーバー画面
            endscene.backgroundColor = '#666666'; // 背景色をやや濃いめの灰色にする
            var endlabel = new Label('ヤラレタ...<br>Score : ' + score); // ゲームオーバー画面で表示する文字
            endlabel.color='#cccccc'; // 文字色を薄めの灰色にする
            endlabel.y = 200; // 真ん中より少し下らへんに文字を表示
            endlabel.textAlign = 'center'; // 真ん中寄せ
            endscene.addChild(endlabel); // ラベルをゲームオーバー画面の中に設定
            game.pushScene(endscene); // 実際にゲームオーバーの画面に変更する
/*
            alert('game over! score: ' + score);
*/
            game.stop();
        }
    });

/*
    player.tl.delay(game.fps * 10).then(function(){
        alert('game over! score: ' + score);
        game.stop();
    });
*/
};

    game.start(); // start your game!
};

画面の情報として状態管理した場合)

enchant(); // initialize

window.onload = function(){

var game = new Core(320, 320);
game.preload('chara7.png', 'chara6.png', 'icon0.png'); // preload image
game.fps = 20;

game.onload = function(){

    // make new class for player
    var Player = enchant.Class.create(enchant.Sprite, {
        initialize: function(){
            enchant.Sprite.call(this, 32, 32);
            this.image = game.assets['chara7.png'];
            this.x = 288;// add:右側に表示。320-32の位置
            this.frame = 10;                   // set image data
            game.rootScene.addChild(this);     // add to canvas
        },
        attackstart: function(){
            this.frame = 16; // 斬っている瞬間の画像
        },
        attackend: function(){
            this.frame = 10; // 普段の画像
        }
    });

    // make new class for apple
    var Apple = enchant.Class.create(enchant.Sprite, {
        initialize: function(){
            enchant.Sprite.call(this, 16, 16);
            this.image = game.assets['icon0.png']; // set image
//            this.moveTo(16, player.y + 8);       // move to the position
            this.moveTo(300 , player.y + 8);       // 変更
//            this.tl.moveBy(320, 0, 30);        // set movement
            this.tl.moveBy(-320, 0, 30);        // 変更
            this.frame = 15;                   // set image data
            game.rootScene.addChild(this);     // add to canvas
        }
    });

    // make new class for enemy
    var Enemy = enchant.Class.create(enchant.Sprite, {
        initialize: function(){
            enchant.Sprite.call(this, 32, 32);
            this.image = game.assets['chara6.png']; // set image
//            this.moveTo(320, Math.floor(Math.random() * 320)); // set position
            this.moveTo(0, Math.floor(Math.random() * 320)); // 変更
            this.scaleX = -1;
//            this.tl.moveBy(-360, 0, 160);      // set movement
            this.tl.moveBy(360, 0, 160);      // 変更
            this.frame = 9;                   // set image data
            game.rootScene.addChild(this);     // canvas
        }
    });

    var player = new Player();
    var enemies = []; //add

    // generate enemy every 60 frames
    game.rootScene.tl.then(function() {
        enemies.push(new Enemy()); //mod
    }).delay(30).loop();                    // wait 60 frames and loop it!

    // add event listener (called when click/touch started)
    game.rootScene.on('touchstart', function(evt){
        player.y = evt.localY;    // set position to touch-y position
        player.attackstart();
    });

    // add event listener (called when click/touch moved)
    game.rootScene.on('touchmove', function(evt){
        player.y = evt.localY;    // set position to touch-y position
    });

    var score = 0;
    var attacking = 0;
    var lifecount = 3;
    var info = new Label('Life : ' + lifecount + ' score : ' + score );
    game.rootScene.addChild(info);

    game.rootScene.on('enterframe', function(){
        if( player.frame == 16) {
            attacking ++;
        }
        if( attacking == 3 ) {
            player.attackend();
            attacking = 0;
        }        
        for(var i = 0; i < enemies.length; i ++){
            if(player.within(enemies[i]) && attacking > 0 ) {
                game.rootScene.removeChild(enemies[i]);
                enemies.splice(i--, 1);
                score ++;
            info.text = 'Life : ' + lifecount + ' score : ' + score; // 追加。
            }
            else if(enemies[i].x >= 320){
                game.rootScene.removeChild(enemies[i]);
                enemies.splice(i--, 1);
                lifecount--;
            info.text = 'Life : ' + lifecount + ' score : ' + score; // 追加。
            }
        }
        if(lifecount === 0){ // ゲームオーバーの状態
            var endscene = new Scene(); // ゲームオーバー画面
            endscene.backgroundColor = '#666666'; // 背景色をやや濃いめの灰色にする
            var endlabel = new Label('ヤラレタ...<br>Score : ' + score); // ゲームオーバー画面で表示する文字
            endlabel.color='#cccccc'; // 文字色を薄めの灰色にする
            endlabel.y = 200; // 真ん中より少し下らへんに文字を表示
            endlabel.textAlign = 'center'; // 真ん中寄せ
            endscene.addChild(endlabel); // ラベルをゲームオーバー画面の中に設定
            game.pushScene(endscene); // 実際にゲームオーバーの画面に変更する
/*
            alert('game over! score: ' + score);
*/
            game.stop();
        }
    });
/*
    player.tl.delay(game.fps * 10).then(function(){
        alert('game over! score: ' + score);
        game.stop();
    });
*/
};
    game.start(); // start your game!
};

いずれも長く読みにくくなっていると思います。この点については次のドキュメントで。