敵の爆弾
敵からも爆弾を出すようにします。
敵の毎フレーム処理の中から爆弾を発生させます。
// 敵の爆弾
function setEnemyBomb(ix,iy){
Crafty.e('Bomb2, 2D, Canvas')
.attr({x:ix, y:iy})
.bind("EnterFrame", function(){
this.y -= 0.6;
if( this.y < 85 ){ // 海面まで来たか
this.destroy();
// 火柱
Crafty.e('Flame, 2D, Canvas')
.attr({x:this.x-5, y:this.y-44})
.timeout(function(){this.destroy();},300);
}
});
}
// 敵の発生
function setEnemy(){
const ypos = Crafty.math.randomInt(120, 415);
const speed = Crafty.math.randomNumber(0.5,1.5);
let sw, xpos;
if( Crafty.math.randomInt(0, 1) == 1 ){
sw = 1;
xpos = -70;
} else {
sw = -1;
xpos = 600;
}
const ce = Crafty.e('Enemy, 2D, Canvas')
.attr({x:xpos, y:ypos, dir:sw, spd:speed})
.bind("EnterFrame", function(){
if( Crafty.math.randomInt(1,1000) < 8 ){ // 敵爆弾の発生率
setEnemyBomb(this.x,this.y);
}
this.x += this.dir * this.spd;
if( (this.dir == 1 && this.x > 600) || (this.dir == -1 && this.x < -70) ){
this.destroy();
}
});
if( sw == -1 ){
ce.flip("X");
}
}
オレンジの爆弾が敵から発生するようになりました。
海面まで来ると爆弾は消えて火柱が表示されます。

爆弾を発生させる仕組みは、これまでも説明してきたことなので省略します。
火柱も「ブロックつみつみ」と同じことをしてますが、少しおさらいしましょう。
// 火柱
Crafty.e('Flame, 2D, Canvas')
.attr({x:this.x-5, y:this.y-44})
.timeout(function(){this.destroy();},300);
this.xとthis.yは爆弾の座標です。
ただ、絵のサイズが違うのでx座標で -5 の調整をしています。
また海面下で消す爆弾の座標を使っているので、海面上に表示するためy座標に -44 の調整をしています。
timeoutを使って 0.3 秒後に自動消滅させています。
うーん、説明できるところが少ない。 今回はホントに復習編ですね。
プレイヤーと火柱の当たり判定
プレイヤーと火柱の当たり判定を入れます。
火柱に当たったら撃沈する演出までやります。
ざっくり判定でいいのでプレイヤーのみCollisionを付けます。
let bomb1_cnt; // プレイヤー爆弾カウンター
let stock_cnt; // 爆弾のストック
let score;
let gameover; // ゲームオーバー(プレイヤーのやられフラグ兼用)
Crafty.scene("main", function() {
let enemyCnt = Crafty.frame() +100; // +100:敵の発生タイミング
bomb1_cnt = 0;
stock_cnt = 0;
score = 0;
gameover = 0; // 0ならプレイヤーはやられていない
Crafty.e('Sky, 2D, Canvas').attr({x:0, y:0, w:600});
Crafty.e('Player, 2D, Canvas, Twoway, Collision')
.attr({x:250, y:45})
.bind('EnterFrame', function () {
if( this.x < 0 ) this.x = 0;
if( this.x > 600-this.w ) this.x = 600 - this.w;
if( gameover > 0 ){ // 撃沈演出
this.y += 0.3;
if( this.y > 85 ) this.destroy();
}
})
.bind('KeyDown', function(e) {
if( e.key == Crafty.keys.Z && gameover == 0 ) {
setPlayerBomb(this.x+this.w/2,this.y+30); // プレイヤー爆弾
}
})
.onHit('Flame', function (hitDatas) { // 火柱に当たったら
if( gameover == 0 ){
gameover = 1;
this.disableControl(); // 入力イベント応答しないように
this.vx = 0; // 移動を停止
}
})
.twoway(100, 0); // 移動速度, jump速度
Crafty.e('Sea, 2D, Canvas')
.bind("EnterFrame", function(){
if( Crafty.frame() > enemyCnt ){
enemyCnt += Crafty.math.randomInt(30, 150); // 敵の発生間隔
setEnemy();
}
stockBomb();
})
.attr({x:0, y:85, w:600});
// スコア
Crafty.e("Score, DOM, 2D, Text")
.attr({ x:10, y:3, w:280, h:20 })
.textFont({size:'20px', weight:'bold'})
.textColor('#222222')
.text("SCORE: 0");
});
変数gameoverはプレイヤーのやられフラグとしても使います。
0 ならやられていない、1以上ならやられた状態です。
追加、変更したところを見てみましょう。
Crafty.e('Player, 2D, Canvas, Twoway, Collision')
当たり判定するため Collision を追加します。
if( gameover > 0 ){ // 撃沈演出
this.y += 0.3;
if( this.y > 85 ) this.destroy();
}
撃沈演出です。gameover が1以上なら下へ移動させます。
海へ沈んだら消滅させます。
ちなみに下へ移動させただけで、撃沈したように見えるのは
「空」「船」「海」
この順番で絵を表示しているため「海」の表示優先が一番高いからです。
シンプルですが沈んでいくように見えます。
if( e.key == Crafty.keys.Z && gameover == 0 ) {
ゲームオーバーではないとき、zキー押下を受け付けます。
船が沈んでいるのに、爆弾を投下できたら変ですからね。
.onHit('Flame', function (hitDatas) { // 火柱に当たったら
if( gameover == 0 ){
gameover = 1;
this.disableControl(); // 入力イベント応答しないように
this.vx = 0; // 移動を停止
}
})
火柱Flameとの当たり判定をしています。
gameover が0なら1にしてゲームオーバーとします。
一回だけ処理したいときなど、このように書きます。
this.disableControl() は入力を無効にしています。つまり Twoway の無効です。
ただ、これだけでは移動中の絵を止めることはできません。
そのため this.vx = 0 にて止めます。
この this.vx はオブジェクトに働く力(x軸)です。
移動中に入力を無効にしてもこの力はそのまま残ります。つまり、移動を続けてしまうのです。
それからゲームオーバーになってからも敵の爆弾がわらわらと出現するのは変なのでやめます。
敵の発生プログラムの一部を変更します。
const ce = Crafty.e('Enemy, 2D, Canvas')
.attr({x:xpos, y:ypos, dir:sw, spd:speed})
.bind("EnterFrame", function(){
if( Crafty.math.randomInt(1,1000) < 8 && gameover == 0 ){ // 敵爆弾の発生率
setEnemyBomb(this.x,this.y);
}
this.x += this.dir * this.spd;
if( (this.dir == 1 && this.x > 600) || (this.dir == -1 && this.x < -70) ){
this.destroy();
}
});
ゲームオーバーの表示
ゲームオーバーを表示します。
下の処理をシーンの外に作ります。
私は main シーンのあと、爆弾のストック表示 stockBomb の前に入れました。
// ゲームオーバーの表示
function disp_gameover(){
Crafty.e('Gameover, 2D, Canvas, Tween')
.attr({x:45, y:160, z:100, h:1})
.tween({h:102}, 800, "easeOutQuad");
Crafty.e("Delay").delay(function() { // 指定秒後に実行する
Crafty.e('2D, DOM, Text').attr({x:200, y:280, w:300, z:100, cnt:0})
.text("Zキーを押してください").textColor('#ff7f00')
.textFont({size:'18px'})
.bind('KeyDown', function(e) {
if( e.key == Crafty.keys.Z ) {
Crafty.scene("title"); // タイトルへ
}
})
.bind('EnterFrame', function () {
this.cnt++;
if( this.cnt %80 < 50 ){
this.textColor('#ff7f00');
} else {
this.textColor('#333333');
}
});
}, 2000, 0); // delayの時間はここで設定(単位はミリ秒)
}
ゲームオーバー表示は Flame の当たり判定のところから呼び出します。
Delay を使って 1.5 秒後に呼び出します。
.onHit('Flame', function (hitDatas) { // 火柱に当たったら
if( gameover == 0 ){
gameover = 1;
this.disableControl(); // 入力イベント応答しないように
this.vx = 0; // 移動を停止
Crafty.e("Delay").delay(disp_gameover, 1500, 0);
}
})
実行してみます。
沈み始めてから 1.5 秒後に GAME OVER が表示されます。

プログラムを見てみましょう。
Crafty.e('Gameover, 2D, Canvas, Tween')
.attr({x:45, y:160, z:100, h:1})
.tween({h:102}, 800, "easeOutQuad");
GAME OVER の表示です。
Tween を使って絵の高さhを 1 から 102 に変えています。
attr 内で z:100 を入れることで表示優先を高くしています。これを入れないと潜水艦が上に表示されることがあります。
Crafty.e("Delay").delay(function() { // 指定秒後に実行する
// zキーを押してくださいの表示処理
}, 2000, 0); // delayの時間はここで設定(単位はミリ秒)
関数disp_gameover()が呼ばれてから2秒後に、無名関数内の処理が行われます。
zキーを押してくださいの説明はこれまでと同じなので省略します。
この表示も z:100 にして表示優先を上げている点に注意してください。
完成です
お疲れさまでした。
一つ一つの処理を見れば、それほど難しいものではなかったと思います。
それからやっぱり、プログラミングできるようになるには
自分で考えたものを形にすることです。
これからもゲームのプログラミングを続けていきましょう。
作りたいものがなければ、とりあえず改良してスキルを身につけましょう。
・プレイヤー爆弾の数を増やす
・ハイスコアの表示
・潜水艦の絵の種類を増やす
・敵の破壊からアイテムを出現させる
・バリアアイテムを作り、無敵時間を作る
・ゆっくりと移動するボス敵を出現させる(爆弾を数発当てないと撃沈できない)
などなど頑張ってみてください。
次のページに全ソースコードを載せておきます。