演出とゲームオーバー

スコアを表示する

ブロックを積めるようになったのでスコアを表示します。

変数scoreを作りました。
色の変えた行を追加しましょう。

Crafty.scene("main", function() {
    // ブロック(土台)
    Crafty.e('Block, 2D, Canvas, Color')
        .attr({x:150, y:350, w:150, h:24})
        .color(0,150,255);
    let score = 0;
    // プレイヤー
    Crafty.e('Player, 2D, Canvas, Color, Collision')
        .attr({x:0, y:30, w:150, h:24, xx:5, yy:0, z:1000})
        .color(0,191,255)
        .bind('KeyDown', function(e) {
            if( e.key == Crafty.keys.Z ) {
                if( this.y == 30 ) {    // 初期配置y座標にいるときのみ受け付け
                    this.xx = 0;
                    this.yy = 5;
                }
            }
        })
        .onHit('Block', function (hitDatas) {  // ブロックに当たった
            let vv = hitDatas[0].obj;       // 当たったブロックを取得
            let ww = vv.w;   // ブロックの幅
            if( vv.x < this.x ){  // 土台の右側に乗ったとき
                ww -= (this.x - vv.x);
            }
            if( vv.x > this.x ){  // 土台の左側に乗ったとき
                ww -= (vv.x - this.x);
                this.x = vv.x;    // 土台の左端を基点にする
            }
            // 土台に残すブロックを作成する
            Crafty.e('Block, 2D, Canvas, Color')
                .attr({x:this.x, y:vv.y-25, w:ww, h:24})
                .color(this._red,this._green,this._blue);
            // 当たったブロックは当たり判定対象から外す
            vv.removeComponent("Block");
            // プレイヤーを画面上部へ戻して横移動
            this.x = 0;
            this.y = 30;
            this.xx = 5;
            this.yy = 0;
            this.w = ww;    // プレイヤーの幅を変更
            const ii = Crafty.math.randomInt(0, 11);
            this.color(coltbl[ii])  // 色を変更する
            score += 100;
            Crafty("Score").text("score: "+score);
        })
        .bind('EnterFrame', function () {
            this.x += this.xx
            this.y += this.yy
            if( this.x < 0 ){
                this.x = 0;
                this.xx = -this.xx
            }
            if( this.x > 500 - this.w ){
                this.x = 500 - this.w;
                this.xx = -this.xx            
            }
            if( this.y >= 375 ){    // 画面下まで来た
                this.y = 375;
            }
        });
    // スコア
    Crafty.e("Score, DOM, 2D, Text")
        .attr({ x:10, y:3, w:280, h:20 })
        .textFont({size:'20px', weight:'bold'})
        .textColor('#eeeeee')
        .text("score: 0");
});

ブロックを1つ積むごとに100点を追加します。
スコアの処理は今までとほぼ同じなので解説は省略です。

ブロック1つ積むごとにスコア100点追加

 

一定の高さでブロックを下へずらす

ブロックを上手に操作すれば、プレイヤーのところまで積めてしまいます。
こうなるとゲームとして破綻するので、阻止するルールを入れます。

一定の高さまで来たら、積まれたブロックを下へずらします。

    // ブロック(土台)
    Crafty.e('Block, 2D, Canvas, Color, Move, Tween')
        .attr({x:150, y:350, w:150, h:24})
        .color(0,150,255);
    let score = 0;
    // プレイヤー
    Crafty.e('Player, 2D, Canvas, Color, Collision')
        .attr({x:0, y:30, w:150, h:24, xx:5, yy:0, z:1000})
        .color(0,191,255)
        .bind('KeyDown', function(e) {
            if( e.key == Crafty.keys.Z ) {
                if( this.y == 30 ) {    // 初期配置y座標にいるときのみ受け付け
                    this.xx = 0;
                    this.yy = 5;
                }
            }
        })
        .onHit('Block', function (hitDatas) {  // ブロックに当たった
            let vv = hitDatas[0].obj;       // 当たったブロックを取得
            let ww = vv.w;   // ブロックの幅
            if( vv.x < this.x ){  // 土台の右側に乗ったとき
                ww -= (this.x - vv.x);
            }
            if( vv.x > this.x ){  // 土台の左側に乗ったとき
                ww -= (vv.x - this.x);
                this.x = vv.x;    // 土台の左端を基点にする
            }
            // 土台に残すブロックを作成する
            Crafty.e('Block, 2D, Canvas, Color, Move, Tween')
                .attr({x:this.x, y:vv.y-25, w:ww, h:24})
                .color(this._red,this._green,this._blue);
            // ブロックが高い位置まで積まれたら、下へずらす
            if( this.y < 200 ){
                Crafty("Move").each(function() {
                    this.tween({y:this.y+25}, 300, "smootherStep");
                });
            }
            // 当たったブロックは当たり判定対象から外す
            vv.removeComponent("Block");
            // プレイヤーを画面上部へ戻して横移動

 

Tweenを使ってブロックを動かしています。
とりあえず実行して、ブロックがずれるまで積んでみましょう。

tweenを使ってブロックを動かす

Tweenという機能は、属性の値を指定したところまでスムーズに変更してくれるものです。
今回は移動で使いましたが、回転、透明度、拡縮と様々に使えます。

 

Crafty.e(‘Block, 2D, Canvas, Color, Move, Tween’)

初めに作る土台ブロックと、土台に残すブロックどちらにも
Move Tweenを追加します。
Move はラベルです。(好きな名前を付けられます)
あとでこのラベルで検索します。

Tween は機能を使うための指定です。当たり判定を使いたいとき Collision を指定するのと同じことです。

 

if( this.y < 200 ){

新しく積んだブロックのy座標が 200 よりも小さければ、ブロック全体をずらす処理をします。

Crafty(“Move”).each(function() {

Move という名前で検索し、該当するオブジェクトを見つけ出します。
無名関数で、このオブジェクトに対する処理を書きます。

this.tween({y:this.y+25}, 300, “smootherStep”);

thisは検索して見つけたオブジェクトそのものです。
それに対しy座標をthis.y+25まで、300ミリ秒で移動させます。

smootherStepはイージングの指定です。
時間経過に伴うパラメータの変化率が変わります。
他に、“linear”, “smoothStep”, “easeInQuad”, “easeOutQuad”, “easeInOutQuad” があります。

 

 

削り落とす演出を入れる

いま、土台よりはみ出た部分は消しています。
これを削り落とす演出に変えます。

当たり判定 onHit の中に追加します。

        .onHit('Block', function (hitDatas) {  // ブロックに当たった
            let vv = hitDatas[0].obj;       // 当たったブロックを取得
            let ww = vv.w;   // ブロックの幅
            let xp = this.x; // 削るブロックのx座標
            if( vv.x < this.x ){  // 土台の右側に乗ったとき
                ww -= (this.x - vv.x);
                xp = vv.x+vv.w;
            }
            if( vv.x > this.x ){  // 土台の左側に乗ったとき
                ww -= (vv.x - this.x);
                this.x = vv.x;    // 土台の左端を基点にする
            }
            // 削り落とすブロック
            Crafty.e('2D, Canvas, Color, Tween')
                .attr({x:xp, y:this.y, w:this.w-ww, h:24})
                .color(this._red,this._green,this._blue)
                .tween({alpha: 0.0,y:this.y+25}, 200, "smootherStep")
                .timeout(function(){this.destroy();},200);
            // 土台に残すブロックを作成する
            Crafty.e('Block, 2D, Canvas, Color, Move, Tween')
                .attr({x:this.x, y:vv.y-25, w:ww, h:24})
                .color(this._red,this._green,this._blue);
            // ブロックが高い位置まで積まれたら、下へずらす
            if( this.y < 200 ){
                Crafty("Move").each(function() {
                    this.tween({y:this.y+25}, 300, "smootherStep");
                });
            }
            // 当たったブロックは当たり判定対象から外す
            vv.removeComponent("Block");
            // プレイヤーを画面上部へ戻して横移動
            this.x = 0;
            this.y = 30;
            this.xx = 5;
            this.yy = 0;
            this.w = ww;    // プレイヤーの幅を変更
            const ii = Crafty.math.randomInt(0, 11);
            this.color(coltbl[ii])  // 色を変更する
            score += 100;
            Crafty("Score").text("score: "+score);
        })

 

let xp = this.x;

変数 xp は削るブロックのx座標を入れるため用意しました。
初期値はプレイヤーのx座標です。

xp = vv.x+vv.w;

プレイヤーが土台の右側に乗ったとき、土台の右端が削るブロックのx座標になります。

Crafty.e(‘2D, Canvas, Color, Tween’)

削るブロックを新しく作ります。
使い捨てなので名前は必要ないです。
これも Tween で動かしますが、単独で動かすためラベル名 Move は必要ありません。

.attr({x:xp, y:this.y, w:this.w-ww, h:24})

削るブロックの幅は、プレイヤーの幅から土台に残す幅を引いて算出します。

.tween({alpha: 0.0,y:this.y+25}, 200, “smootherStep”)

半透明 alpha を 0 に設定すると、0(完全な透明)に向かって値が変化します。
y座標は先ほどと同じです。
今回は 200 ミリ秒で変化させます。

.timeout(function(){this.destroy();},200);

タイムアウト処理です。
200 ミリ秒後に無名関数内の処理を行います。
つまり、200 ミリ秒後にこのオブジェクトを消滅させます。

 

実行してみましょう。
土台からはみ出た部分は、削り落ちるとき透明になりながら消えていきます。

削り落ちる演出ができた

 

 

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

ゲームオーバーを入れます。

これで最後なので、全コードです。

Crafty.init(500,400, document.getElementById('game'));
Crafty.background('#101010');

const coltbl = ['#ff7f7f','#ff7fbf','#ff7fff','#bf7fff','#7f7fff','#7fbfff',
'#7fffff','#7fffbf','#7fff7f','#bfff7f','#ffff7f','#ffbf7f'];

Crafty.scene("title", function() {
    Crafty.e('2D, DOM, Text').attr({x:100, y:40, w:300})
        .text("ブロックつみつみ").textColor('#00ff7f')
        .textFont({size:'36px', weight:'bold'});
    Crafty.e('2D, DOM, Text').attr({x:150, y:100, w:230})
        .text("タイミングよく Z キーを押して四角をつみ上げてください")
        .textColor('#00ff7f')
        .textFont({size:'14px'});
    Crafty.e('2D, DOM, Text')
        .attr({x:180, y:160, w:300, cnt:0})
        .text("Z キーを押してください").textColor('#ff7f00')
        .bind('KeyDown', function(e) {
            if( e.key == Crafty.keys.Z ) {
                Crafty.scene("main");   // ゲームメイン処理へ
            }
        })
        .bind('EnterFrame', function () {
            this.cnt++;
            if( this.cnt %80 < 50 ){
                this.textColor('#ff7f00');
            } else {
                this.textColor('#333333');            
            }
        })
        .textFont({size:'14px'});
    for( let lp=0; lp<6; lp++ ){
        Crafty.e('2D, Canvas, Color')
            .attr({x:150+lp*15, y:350-lp*26, w:200-lp*30, h:25})
            .color(0,250,lp*50);    
    }
});

Crafty.scene("main", function() {
    // ブロック(土台)
    Crafty.e('Block, 2D, Canvas, Color, Move, Tween')
        .attr({x:150, y:350, w:150, h:24})
        .color(0,150,255);
    let score = 0;
    let gameover = 0;
    // プレイヤー
    Crafty.e('Player, 2D, Canvas, Color, Collision')
        .attr({x:0, y:30, w:150, h:24, xx:5, yy:0, z:1000})
        .color(0,191,255)
        .bind('KeyDown', function(e) {
            if( e.key == Crafty.keys.Z ) {
                if( this.y == 30 ) {    // 初期配置y座標にいるときのみ受け付け
                    this.xx = 0;
                    this.yy = 5;
                }
                if( gameover == 2 ){
                    Crafty.scene("title");   // タイトル画面へ
                }
            }
        })
        .onHit('Block', function (hitDatas) {  // ブロックに当たった
            let vv = hitDatas[0].obj;       // 当たったブロックを取得
            let ww = vv.w;   // ブロックの幅
            let xp = this.x; // 削るブロックのx座標
            if( vv.x < this.x ){  // 土台の右側に乗ったとき
                ww -= (this.x - vv.x);
                xp = vv.x+vv.w;
            }
            if( vv.x > this.x ){  // 土台の左側に乗ったとき
                ww -= (vv.x - this.x);
                this.x = vv.x;    // 土台の左端を基点にする
            }
            // 削り落とすブロック
            Crafty.e('2D, Canvas, Color, Tween')
                .attr({x:xp, y:this.y, w:this.w-ww, h:24})
                .color(this._red,this._green,this._blue)
                .tween({alpha: 0.0,y:this.y+25}, 200, "smootherStep")
                .timeout(function(){this.destroy();},200);
            // 土台に残すブロックを作成する
            Crafty.e('Block, 2D, Canvas, Color, Move, Tween')
                .attr({x:this.x, y:vv.y-25, w:ww, h:24})
                .color(this._red,this._green,this._blue);
            // ブロックが高い位置まで積まれたら、下へずらす
            if( this.y < 200 ){
                Crafty("Move").each(function() {
                    this.tween({y:this.y+25}, 300, "smootherStep");
                });
            }
            // 当たったブロックは当たり判定対象から外す
            vv.removeComponent("Block");
            // プレイヤーを画面上部へ戻して横移動
            this.x = 0;
            this.y = 30;
            this.xx = 5;
            this.yy = 0;
            this.w = ww;    // プレイヤーの幅を変更
            const ii = Crafty.math.randomInt(0, 11);
            this.color(coltbl[ii])  // 色を変更する
            score += 100;
            Crafty("Score").text("score: "+score);
        })
        .bind('EnterFrame', function () {
            this.x += this.xx
            this.y += this.yy
            if( this.x < 0 ){
                this.x = 0;
                this.xx = -this.xx
            }
            if( this.x > 500 - this.w ){
                this.x = 500 - this.w;
                this.xx = -this.xx            
            }
            if( this.y >= 375 ){    // 画面下まで来た
                this.y = 375;
                if( gameover == 0){
                    gameover = 1;
                    disp_gameover();    // ゲームオーバーの表示
                }
            }
        });
    // ゲームオーバーの表示
    function disp_gameover(){
        Crafty.e('2D, DOM, Text').attr({x:125, y:100, w:300})
            .text("GAME OVER").textColor('#00ff7f')
            .textFont({size:'36px', weight:'bold'});
        Crafty.e("Delay").delay(function() {    // 1秒後に実行される
            gameover = 2;   // リトライ受付開始
            Crafty.e('2D, DOM, Text').attr({x:145, y:160, w:300, cnt:0})
                .text("Z キーを押してください").textColor('#ff7f00')
                .textFont({size:'18px'})
                .bind('EnterFrame', function () {
                    this.cnt++;
                    if( this.cnt %80 < 50 ){
                        this.textColor('#ff7f00');
                    } else {
                        this.textColor('#333333');            
                    }
                });
        }, 1000, 0);    // delayの時間はここで設定(単位はミリ秒)
    }
    // スコア
    Crafty.e("Score, DOM, 2D, Text")
        .attr({ x:10, y:3, w:280, h:20 })
        .textFont({size:'20px', weight:'bold'})
        .textColor('#eeeeee')
        .text("score: 0");
});

Crafty.scene("title");

 

ほぼ、シーン title と同じことをしています。
違う点を解説します。

変数gameoverで処理の流れを管理しています。

gameoverが0のとき、ゲームプレイ中。
1のとき、ゲームオーバーの表示。(シフトキーはまだ受け付けない)
2のとき、シフトキーを押してくださいの表示。(シフトキー受け付け中)
また、ここでシフトキーが押されたらシーン title へ移行します。

 

なお、シフトキーの受け付けまで1秒待っています。
ゲームプレイでもシフトキーを使っているため、すぐに処理すると
意図せずシーン title へ移行してしまう可能性があるからです。

この待つ処理には.delay()を使いました。

Crafty.e(“Delay”).delay()(function(){}, 1000, 0)

これは1000ミリ秒後に1回だけ function の中を実行します。
1000の後の0はリピート回数です。0のときは1回だけ実行されます。

 

それでは、実行してみます。
ゲームオーバーの表示とリトライが正常に行われるかチェックしましょう。

ゲームオーバーの表示とリトライ

 

完成です

これで完成です。お疲れさまでした。

これからは自分で考えてゲームを作ってみましょう。
いいアイデアが思いつかないときは、他のゲームを参考にしてみてはどうでしょうか。

何もしないと学んだことを忘れてしまいます。
自分の発想で作って初めてスキルとして身に付いていきます。

 

 

『ブロックつみつみ』

ゲームを作ってみよう

  1. ブロックつみつみに挑戦
  2. タイトルと操作
  3. ブロックを積み上げる
  4. 演出とゲームオーバー