スコアを表示する
ブロックを積めるようになったのでスコアを表示します。
変数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点を追加します。
スコアの処理は今までとほぼ同じなので解説は省略です。
一定の高さでブロックを下へずらす
ブロックを上手に操作すれば、プレイヤーのところまで積めてしまいます。
こうなるとゲームとして破綻するので、阻止するルールを入れます。
一定の高さまで来たら、積まれたブロックを下へずらします。
// ブロック(土台)
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という機能は、属性の値を指定したところまでスムーズに変更してくれるものです。
今回は移動で使いましたが、回転、透明度、拡縮と様々に使えます。
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回だけ実行されます。
それでは、実行してみます。
ゲームオーバーの表示とリトライが正常に行われるかチェックしましょう。
完成です
これで完成です。お疲れさまでした。
これからは自分で考えてゲームを作ってみましょう。
いいアイデアが思いつかないときは、他のゲームを参考にしてみてはどうでしょうか。
何もしないと学んだことを忘れてしまいます。
自分の発想で作って初めてスキルとして身に付いていきます。