phina.js が面白そうなのでお勉強中 その9
#GAME OVER を作る
前回までで当たり判定が一通りできたので、GAME OVER を作ります。
やりたいことは以下です。
・自機の残数が0の状態でやられると、GAME OVER を表示する。
・自機は消えるが、敵とか敵の弾は一定時間動き続ける。
・一定時間後、タイトル画面に行く
⇒タイトル画面はまだ無いので、
とりあえず、変数とか初期化してゲームが始まるようにする。
###数値ラベルクラスを作る
ラベルクラスでスコアとか残機数とか表示させるのが面倒になってきたので、ラベルクラスを拡張した数値ラベルクラスを作ります。もっと前に作っておけばよかった。
何が面倒かと言うと、数値を変更させるたびに、表示させる文字列を生成する必要がありました。で、数値を与えるだけで文字列を生成して表示してくれるクラスにしました。
// 数値ラベルクラス
phina.define('NumberLabel', {
// Labelを継承
superClass: 'Label',
// 初期化
// prefix: 数値の前に付ける文字
// x: x座標
// y: y座標
// length: 数値の長さ(0埋めする)
// fontSize: フォントサイズ
// color: 色
init: function(prefix ,x, y, length, fontSize, color) {
// 親クラスの初期化
this.superInit({
text: prefix + ('0'.repeat(length)),
fontSize: fontSize,
x: x,
y: y,
fill: color,
stroke: false,
});
// プレフィックス
this.prefix = prefix;
// 数字の長さ
this.length = length;
},
// 値設定
setValue: function(value) {
//this.text = this.prefix + ('0'.repeat(this.length) + value).slice(-this.length);
this.text = this.prefix + value.padding(this.length);
},
});
生成時のパラメータが多くなってしまったのが難点。
prefix ,x, y, length, fontSize, color を与える必要があります。
で、一度 NumberLabel を生成しておくと、setValue ってな関数で値を与えるだけで0埋めの文字列を生成して表示してくれる、というわけです。
※追記
コメントで、数値の0埋めは、phina.js では、Numberを拡張した
global.Number.padding( n, [ch] ) : String
が使える、と教えて頂き修正しました。
0埋め処理が直感的になりました。
###GAME OVER の文字を作る
GAME OVER を作る時に一番面倒なのが、GAME OVER の文字を作る所だったりする。
とりあえず GIMP を使って、ぱぱっと以下のような絵を作りました。
アセットに設定する絵がまた増えたな。
// アセット
var ASSETS = {
// 画像
image: {
'ship': 'ship.gif',
'enemy': 'enemy.gif',
'bullet': 'bullet.gif',
'explosion': 'EXPLOSION.gif',
'gameover' : 'GAMEOVER.gif',
},
で、GAME OVER 時にこの絵を表示すればいいのだけど、GAME OVER 時点で絵を表示するためにドタバタするのは面倒なので、最初から非表示でこの絵を用意しておいて、GAME OVER 時に表示させるだけにすれば簡単かな、と思いました。
###GAME OVER 処理の準備
MainScene の init 処理内で、GAME OVER 処理に関する準備を行います。
// スコア
this.score = 0;
// 残機数
this.remainingShip = 3;
// スコア表示
this.scoreLabel = NumberLabel('SCORE:', 80, 15, 10, 15, 'white').addChildTo(this.group.scoreGroup);
this.scoreLabel.setValue(this.score);
// 残機数表示
this.remainingShipLabel = NumberLabel('SHIP:', this.width - 50, 15, 2, 15, 'white').addChildTo(this.group.scoreGroup);
this.remainingShipLabel.setValue(this.remainingShip);
// GAMEOVERフラグ
this.isGameOver = false;
// スコアのグループにGAMEOVERを設定
this.gameOver = Sprite('gameover').addChildTo(this.group.scoreGroup);
this.gameOver.x = this.gridX.center();
this.gameOver.y = this.gridY.center();
// GAMEOVERを消す
this.gameOver.hide();
// シナリオ関連の初期化・・・
まず、数値ラベルクラスを作ったので、スコアとか残機の表示を数値ラベルクラスに変更しました。
で、MainScene に isGameOver というフラグを持たせます。これが true になっていれば、GAME OVER 中であると考えます。
次に、GAME OVER の画像からスプライトクラスを生成して、スコアとか表示するグループに非表示で配置してます。
###GAME OVER 処理
MainScene の update 処理内に、GAME OVER 処理を作ります。
どのように処理させれば良いか考えたのですが、GAME OVER 表示後一定時間そのままにする、っていうのを実装するのが面倒なので、tweener.wait() を使ったら簡単かなと思い、使ってみることにしました。
update: function () {
// GAMEOVERの場合
if (this.isGameOver && !this.gameOver.visible) {
// 自機を非表示
ship.hide();
// GAMEOVER表示
this.gameOver.show();
// GAMEOVERの処理
this.gameOver.tweener.wait(5000).call(
function() {
// 再配置処理
mainScene.relocation();
// スコアと残数をリセット
mainScene.score = 0;
mainScene.remainingShip = 3;
mainScene.scoreLabel.setValue(mainScene.score);
mainScene.remainingShipLabel.setValue(mainScene.remainingShip);
// シナリオ関連の初期化
// 面(0始まり)
mainScene.stage = 0;
// シナリオのフレーム数
mainScene.scenarioFrame = 0;
// 現在のシナリオ番号
mainScene.scenarioNo = 0;
// 現在のシナリオが始まるフレーム数
mainScene.scenarioNowFrame = 0;
// GAMEOVERを解除
mainScene.isGameOver = false;
// GAMEOVERを非表示
mainScene.gameOver.hide();
// 自機表示
ship.show();
}
).play();
}
// ランダムで星を生成する・・・
処理の内容ですが、isGameOver が true で GAME OVER の画像が非表示の場合は GAME OVER 処理がまだなので行うことにします。それ以外は通常の表示処理です。
GAME OVER 処理では、自機を非表示にして、GAME OVER を表示させます。
で、GAME OVER のスプライトで tweener を使っています。
wait(5000) で5秒待機、5秒後に call() 内の処理を行います。
call() 内の処理は、本当はタイトル画面を表示することになるのですが、とりあえず今は、スコア、残機数、シナリオを初期化して、GAME OVER 状態を解除し、ゲームを最初から始めるようにしています。
tweener を使って2点ハマりました。
まず、最後の.play(); なんですが、ドキュメントを見ると 「アニメーション開始」って書いてある。でもネット上の tweener のサンプルでは付いていないものもあるんですよ。
これが付いていないと、1回目だけ普通に動くんだけど、2回目以降動かない、というやっかいなバグが発生します。
もう1つが、call() 内の this について。
Javascript の一番ややこしい所だと思っているんですか、this の概念が java とかの概念と違います。
java とかの this は、そのクラスのインスタンスを指しているので迷うことは無いのですが、Javascript は関数の呼び出し元が this だから、call() 内の this は tweener を指しているんだと思う。(たぶん。)
だから、call() 内で mainScene のプロパティを更新する際は、this ではなく mainScene が入った変数を使ってます。
###GAME OVER 発生処理
GAME OVER の処理ができたので、GAME OVER を発生させる処理を作ります。
つまり、残機が0なのに自機がやられた時ですね。
shootDown : function () {
this.shotdownFrameCount++;
// 点滅させる
this.alpha = this.alpha == 1 ? 0 : 1;
// 100フレームを過ぎた場合、再配置処理に移る
if (this.shotdownFrameCount > 100) {
// GAMEOVERの判定
if (mainScene.remainingShip == 0) {
// GAMEOVERにする
mainScene.isGameOver = true;
return;
}
// 自機を減らす
mainScene.remainingShip--;
mainScene.remainingShipLabel.setValue(mainScene.remainingShip);
// 再配置処理
mainScene.relocation();
}
},
自機クラスの撃破処理で、GAME OVER の判定を入れています。
撃破後、点滅処理が終ってから自機の残数をチェックして、残数が0だったら GAME OVER にしてます。
GAME OVER 中は自機を描画しないので、自機の update 処理で、GAME OVER 中の場合は処理を抜けるようにしています。
update: function(app) {
// GAMEOVERの場合は処理を抜ける
if (mainScene.isGameOver) {
return;
}
// 撃墜されている場合
if (this.shotdownFrameCount !=0) {
// 撃墜処理
this.shootDown();
return;
}
##今日の成果
今日の成果をここに上げました。
自機の残数が0の時に、自機が敵とか敵の弾に当たると GAME OVER が5秒間表示されます。
http://hirotyan.my.coocan.jp/phinajs/Shooting/008/index.html