JavaScript のゲームプログラミング入門
7. そこそこジャンプ:壁と当たり判定
[ 6. そこそこジャンプを作ってみよう ] の続きです。
プレイヤーと地面を表示して、クリックでジャンプできるようにしました。
壁を表示しよう
壁を表示します。
簡単なので、一緒に移動処理も入れます。
Crafty.init(500,350, document.getElementById('game'));
Crafty.background('#87ceeb');
// 壁
Crafty.sprite(49,147,"wall.png",{Wall:[0,0]});
Crafty.e('Wall, 2D, Canvas')
.attr({x:500, y:250})
.bind('EnterFrame', function() {
this.x -= 5;
if (this.x < -50) this.x = 500;
});
// プレイヤー
Crafty.sprite(72,100,"player.png",{Player:[0,0]});
Crafty.e('Player, 2D, Canvas, Gravity, SpriteAnimation, Jumper')
.attr({x:50, y:50})
.reel('walk',300,[[0,0],[1,0]]) // 歩きアニメ設定
.animate('walk', -1) // 歩きアニメ再生
.jumpSpeed(350) // ジャンプの強さ
.gravity('Floor');
// 地面
Crafty.e('Floor, 2D, Canvas, Color')
.attr({x:0, y:320, w:500, h:40}).color('#830');
// マウスのクリックを取得 注意:ClickではなくMouseDownを使う
Crafty.s('Mouse').bind('MouseDown', function(e) {
Crafty("Player").jump();
});
実行してみます。
壁といってもなんかサボテンのような感じですね。
まず、プログラムを追加した場所に注目してください。
プレイヤーより上に入れました。
これは表示優先を低くするためです。
優先を変更する仕組みを使わない場合、先に処理したものが下に表示されます。
(あとから処理したものが上に表示される)
処理する順番で表示優先を変える方法は、お手軽なのでよく使います。
ただ、この方法だけで管理するのは難しいです。違う方法は別の機会に説明します。
移動処理について
フレームレート(fps)とか聞いたことありますか?
ざっくり言うと、1秒間に画面を書き換える回数です。
「このゲームは 60fps だ」なんて言っている場合、1秒間に60回も画面を書き換えています。
詳しくは検索してください。
このゲームでも、画面を書き換えることでオブジェクトを動かしています。
追加した壁のプログラムを見てください。
色のついた行が、毎フレーム処理されています。
// 壁
Crafty.sprite(49,147,"wall.png",{Wall:[0,0]});
Crafty.e('Wall, 2D, Canvas')
.attr({x:500, y:250})
.bind('EnterFrame', function() {
this.x -= 5;
if (this.x < -50) this.x = 500;
});
bind で EnterFrame を指定すると、毎フレームイベントが発生して function の中の処理を実行します。
左へ移動するには x座標を小さくしていきます。
下の式は this.x から 5 を引きます。
this.x -= 5;
壁が左へ進み画面外へ出たとき、条件分岐を使って右側に移動させます。
x座標が -50 より小さいとき、x座標を 500 にしています。
なぜ -50 かと言うと、壁の絵の幅が 49 あるからです。
if (this.x < -50) this.x = 500;
以前説明した条件分岐は、波かっこ { } を使ったものでした。
if( 条件式 ){
条件式が成り立ったとき ( 真のとき ) 処理を実行する
}
今回のように1つだけ処理したい場合には、続けて書くこともできます。
こうすることでプログラムがスッキリします。
if( 条件式 ) 条件式が成り立ったとき処理を実行する
なお、条件式はいろいろな比較演算子を使います。
以下、よく使うものです。
比較演算子 | 意 味 | 例 |
---|---|---|
式1 == 式2 | 式1 と 式2 が等しい | if ( a == 5 ) |
式1 != 式2 | 式1 と 式2 は等しくない | if ( b != 3 ) |
式1 < 式2 | 式1 が 式2 より小さい | if ( a < 6 ) |
式1 > 式2 | 式1 が 式2 より大きい | if ( a > 3 ) |
式1 <= 式2 | 式1 が 式2 と等しいか、小さい | if ( a <= b ) |
式1 >= 式2 | 式1 が 式2 と等しいか、大きい | if ( a >= b ) |
=== などもあります。
ここでは説明しないので、興味があればjavascript 比較演算子で検索してみましょう。
当たり判定を入れよう
プレイヤーと壁との当たり判定を入れます。
当たり判定とは、絵と絵が重なったかどうかを調べることです。
私は、当たり判定を入れるときが一番わくわくします。
ただの絵から物体に変わったような感じを受けるからです。
例えば 3D のドライビングゲームで、当たり判定がなく壁をすり抜けてどこでも走れるとしたら、現実感なんて何も感じられません。
それでは、プレイヤーに当たり判定を入れます。
// プレイヤー
Crafty.sprite(72,100,"player.png",{Player:[0,0]});
Crafty.e('Player, 2D, Canvas, Gravity, SpriteAnimation, Jumper, Collision')
.attr({x:50, y:50})
.reel('walk',300,[[0,0],[1,0]]) // 歩きアニメ設定
.animate('walk', -1) // 歩きアニメ再生
.jumpSpeed(350) // ジャンプの強さ
.onHit('Wall', function () { // 壁に当たったら
Crafty.pause(); // ポーズする
})
.gravity('Floor');
とりあえず実行してください。
プレイヤーと壁が当たると動きが停止します。
表示座標から計算で当たりを判定できますが、今回は CraftyJS の持つ仕組みを使います。
Collision をパラメータに追加することで利用できます。
Crafty.e('Player, 2D, Canvas, Gravity, SpriteAnimation, Jumper, Collision')
onHit を使って Wall と当たったときの処理を書きます。
これは bind と同じ書き方ですね。
.onHit('Wall', function () { // 壁に当たったら
Crafty.pause(); // ポーズする
})
Crafty.pause() を使うと、プログラム全体をポーズ(停止)させることができます。
当たり判定の範囲を変えよう
ページを更新して何回かテストしてみてください。
すると、この当たり判定は「精度が低い」ことに気付くはずです。
下図のように、プレイヤーの体と壁が接触していなくても当たったと判断するときがあります。
原因を探るため、当たりの範囲を目で見えるようにします。
壁に Collision と WiredHitBox を追加します。
プレイヤーには WiredHitBox を追加しましょう。
// 壁
Crafty.sprite(49,147,"wall.png",{Wall:[0,0]});
Crafty.e('Wall, 2D, Canvas, Collision, WiredHitBox')
.attr({x:500, y:250})
.bind('EnterFrame', function() {
this.x -= 5;
if (this.x < -50) this.x = 500;
});
// プレイヤー
Crafty.sprite(72,100,"player.png",{Player:[0,0]});
Crafty.e('Player, 2D, Canvas, Gravity, SpriteAnimation, Jumper, Collision, WiredHitBox')
.attr({x:50, y:50})
WiredHitBox を使うと当たりの範囲が赤い線で囲われます。
これで、すき間があるのになぜ当たったのか分かりますね。
すき間ができないように、プレイヤーの当たり判定範囲を小さくしたいと思います。
下の1行を追加します。
// プレイヤー
Crafty.sprite(72,100,"player.png",{Player:[0,0]});
Crafty.e('Player, 2D, Canvas, Gravity, SpriteAnimation, Jumper, Collision, WiredHitBox')
.attr({x:50, y:50})
.reel('walk',300,[[0,0],[1,0]]) // 歩きアニメ設定
.animate('walk', -1) // 歩きアニメ再生
.jumpSpeed(350) // ジャンプの強さ
.collision(15,30, 55,30, 55,85, 15,85) // 当たり判定の範囲を変更
.onHit('Wall', function () { // 壁に当たったら
Crafty.pause(); // ポーズする
})
.gravity('Floor');
実行してみましょう。
プレイヤーの当たり判定が小さくなり、絵が重なったときだけヒットするようになりました。
collision での範囲指定は、絵より小さく、凹みのない形で指定します。
5角形以上でも大丈夫です。
ゲームにおいて当たり判定をすべて正確に行う必要はないと思います。
すべて変更するのは面倒ですし、処理も重くなります。
私は、プレイヤーの不利になる点は正確に、有利になる点は手を付けないというルールを採用しています。
不利になる点とは、プレイヤーと敵が触れてもいないのにやられてしまう場合などです。
今回のように、プレイヤーの当たりを小さくすることが多いです。
逆に有利になる点とは、プレイヤーが撃った弾と敵の当たり判定などです。
ここはざっくりした当たり判定でもOKです。撃った弾が敵をすり抜けてしまう(ように見える)より全然いいです。
ゲームオーバーを表示しよう
プレイヤーと壁が当たったらゲームオーバーを表示します。
赤い枠線は必要ないので WiredHitBox を消します。
// 壁
Crafty.sprite(49,147,"wall.png",{Wall:[0,0]});
Crafty.e('Wall, 2D, Canvas, Collision')
.attr({x:500, y:250})
.bind('EnterFrame', function() {
this.x -= 5;
if (this.x < -50) this.x = 500;
});
// プレイヤー
Crafty.sprite(72,100,"player.png",{Player:[0,0]});
Crafty.e('Player, 2D, Canvas, Gravity, SpriteAnimation, Jumper, Collision')
.attr({x:50, y:50})
.reel('walk',300,[[0,0],[1,0]]) // 歩きアニメ設定
.animate('walk', -1) // 歩きアニメ再生
.jumpSpeed(350) // ジャンプの強さ
.collision(15,30, 55,30, 55,85, 15,85) // 当たり判定の範囲を変更
.onHit('Wall', function () { // 壁に当たったら
Crafty.pause(); // ポーズする
Crafty.e('2D, DOM, Text').attr({x:95, y:100, w:400})
.text("GAME OVER").textColor('#dc143c')
.textFont({size:'48px', weight:'bold'});
})
.gravity('Floor');
実行してみます。
テキストの表示は初めてですね。
Crafty.e(‘2D, DOM, Text’) の指定だけで簡単に表示できます。
Crafty.e('2D, DOM, Text').attr({x:95, y:100, w:400})
.text("GAME OVER").textColor('#dc143c')
.textFont({size:'48px', weight:'bold'});
フォントに対する指定は次の通りです。
.attr({x:95, y:100, w:400}) // 表示座標と幅
.text("GAME OVER") // 表示する文字列
.textColor('#dc143c') // 色の指定
.textFont({size:'48px', weight:'bold'}); // フォントサイズとスタイルを指定
1つ注意したいのが attr の w です。これは表示の幅を指定します。
例えば下のように w:200 で実行してみてください。
.attr({x:95, y:100, w:200}) // w:表示の幅
表示の幅を小さくしたため GAME OVER が改行されます。
次回、完結編です。