ブロックとの当たり判定
プレイヤーとブロックに当たり判定を入れます。
四角を表示しているので、当たり判定も見た目と同じで大丈夫です。
ですからプレイヤーのみにCollisionを入れます。
// プレイヤー
Crafty.e('Player, 2D, Canvas, Color, Collision')
.attr({x:0, y:30, w:150, h:24, xx:5, yy:0})
.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; // 当たったブロックを取得
// 土台に残すブロックを作成する
Crafty.e('Block, 2D, Canvas, Color')
.attr({x:this.x, y:vv.y-25, w:this.w, h:24})
.color(this._red,this._green,this._blue);
// プレイヤーを画面上部へ戻して横移動
this.x = 0;
this.y = 30;
this.xx = 5;
this.yy = 0;
})
.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(‘Player, 2D, Canvas, Color, Collision’)
Collision を追加します。当たり判定には必須です。
.onHit(‘Block’, function (hitDatas) {
let vv = hitDatas[0].obj;
Block との当たり判定を拾ったら、変数vvに取得します。
後々、いろいろと使います。
.attr({x:this.x, y:vv.y-25, w:this.w, h:24})
.color(this._red,this._green,this._blue);
土台のブロックにのせるプレイヤーの分身を作ります。
プレイヤー情報は this で取得できます。
y座標だけは土台の情報から算出(vv.y-25)します。
プレイヤーと土台が当たっているということは、重なっているということです。
実際には少し開けてのせるため下のブロックから逆算します。
実行してみましょう。
プレイヤーを落下させ土台に当たると、新しくブロックが作られます。
そして、プレイヤーは画面上部に戻ります。

今のままだと下図のように、どのブロックにものってしまいます。
ルールとしては、最上部に積まれたブロックにのみ積みたいですね。

最上部のブロックのみに積む
最上部のブロックのみに当たり判定を残します。
方法は簡単で、当たったブロックから Block 属性を消すだけです。
先ほど追加した onHit の中に2行追加します。
.onHit('Block', function (hitDatas) { // ブロックに当たった
let vv = hitDatas[0].obj; // 当たったブロックを取得
// 土台に残すブロックを作成する
Crafty.e('Block, 2D, Canvas, Color')
.attr({x:this.x, y:vv.y-25, w:this.w, h:24})
.color(this._red,this._green,this._blue);
// 当たったブロックは当たり判定対象から外す
vv.removeComponent("Block");
// プレイヤーを画面上部へ戻して横移動
this.x = 0;
this.y = 30;
this.xx = 5;
this.yy = 0;
})
プレイヤーと当たったブロックから Block 属性を消したので、この属性を持っているのは直近で作ったブロックのみです。つまり、最上部のブロックのみ Block 属性があります。
テストしてみましょう。
他のブロックに当てようとしても、すり抜けてプレイヤーは最下部まで落ちるようになりました。

プレイヤーの色を変更する
青いブロックも見飽きたのでプレイヤーの色を変えます。
まず、カラーテーブルcoltblを作ります。
プログラムのはじめの方(3行目)に追加します。
一度、乱数で色を変更してみたら、なんか汚い画面になってしまいました。
ここはテーブルを作ってランダムに参照するようにします。
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})
シーン main の中でなく外に宣言した理由は、今後、シーン title でも使えるようにするためです。
(実際には使いませんでした。)
次は色の変更です。
変更のタイミングは、ブロックと当たって画面上部に戻す時です。
onHit の中に追加します。
.onHit('Block', function (hitDatas) { // ブロックに当たった
let vv = hitDatas[0].obj; // 当たったブロックを取得
// 土台に残すブロックを作成する
Crafty.e('Block, 2D, Canvas, Color')
.attr({x:this.x, y:vv.y-25, w:this.w, h:24})
.color(this._red,this._green,this._blue);
// 当たったブロックは当たり判定対象から外す
vv.removeComponent("Block");
// プレイヤーを画面上部へ戻して横移動
this.x = 0;
this.y = 30;
this.xx = 5;
this.yy = 0;
const ii = Crafty.math.randomInt(0, 11);
this.color(coltbl[ii]) // 色を変更する
})
プレイする度にブロックの色がランダムに変わります。

土台の右側に乗ったとき
ここからゲームシステムの要です。
まずは土台の右側に、プレイヤーを乗せたときの処理を作ります。
.onHit('Block', function (hitDatas) { // ブロックに当たった
let vv = hitDatas[0].obj; // 当たったブロックを取得
let ww = vv.w; // ブロックの幅
if( vv.x < this.x ){ // 土台の右側に乗ったとき
ww -= (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]) // 色を変更する
})
土台の右側に乗せてみましょう。
はみ出た部分は削られます。

let ww = vv.w;
幅を計算するための変数wwを作り、まず土台の幅を入れます。
ww -= (this.x - vv.x);
土台のxよりプレイヤーのxが大きいとき、つまり土台の右側に乗ったときに幅を計算します。
土台からはみ出た長さは、プレイヤーxから土台xを引いた値と同じです。
この値を引けばいいのです。

.attr({x:this.x, y:vv.y-25, w:ww, h:24})
土台に残すブロックの幅に、計算した幅をセットします。w:ww
this.w = ww;
プレイヤーの幅にも計算した幅をセットします。
土台の左側に乗ったとき
土台の左側に乗ったときの処理を作ります。
.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]) // 色を変更する
})
左側に乗せていくと次のようになります。

ww -= (vv.x - this.x);
土台よりはみ出た長さを引くだけです。
this.x = vv.x;
土台に残すブロックはプレイヤー情報を元に作っています。
そのためプレイヤーxに土台のxを入れて、ここを基点とします。
それなりに遊べるようになりました。
ゲームシステムの8割くらい出来ましたね。
表示優先を高くする
今、遊んでみて違和感がある、と思った人は鋭いです。
プレイヤーが最上部の土台に乗らず落下していくとき、
ブロックの後ろを通過するため見えなくなります。
ゲーム作りの鉄則として、プレイヤーキャラの表示優先は一番高くするものです。
(演出で隠れるようにすることはあります)

積み重ねているブロックは新規に作っています。
それに対しプレイヤーは使い回しです。
描画をするとき新規に作ったオブジェクトの方が、表示優先が高くなるのです。
プレイヤーの表示優先を高くします。
方法はz属性を大きくするだけです。
// プレイヤー
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) {
z:1000
適当に大きい数値をzに入れてみました。
x y 属性(座標)と同じように使えます。
大きい数値は手前に、小さい数値は後ろになります。
マイナス値も使えますよ。