ボールの座標
ボールの上下左右の座標を次のように名づけます。
ボールの中心の座標がxとyです。
ボールの上下の頂点座標がtopYとbottomY、左右の頂点座標がleftXとrightXです。
ボールを移動するときはxとyの値を変えていきます。
ボールを生成する関数
balls.jsにボールのための関数を作成していきます。
今回のゲームではボールを複数増やせるようにします。そこで balls
という変数に配列を用意して、その中にボールを生成していきます。
const createBall = (x, y) => {
get topY () { return this.y - ballRadius; },
get bottomY () { return this.y + ballRadius; },
get leftX () { return this.x - ballRadius; },
get rightX () { return this.x + ballRadius; },
引数でボールを作成する位置のx座標とy座標を受け取ります。
dx
はボールがx座標を移動する距離、 dy
はy座標を移動する距離です。 dx
の値がプラスであれば右方向へ、マイナスであれば左方向へボールが向かいます。 dy
の値がプラスであれば下方向へ、マイナスであれば上方向へボールが向かいます。
たとえば、ボールのx座標が50、y座標が100の位置にあったとします。ボールが移動したあとのx座標は53、y座標は47となります。
操作バーを作ったときのようにゲッターで上下左右の座標を取得できるようにしています。
これらのゲッターはすべてのボールで同じ内容の関数なのでプロトタイプで渡しています。こうしないと一つ一つのボールに同じ関数が生成されて無駄にメモリを消費してしまいます。
ボールを初期配置する関数
createBall(bar.x, bar.y - ballRadius);
ボールを初期配置は操作バーの上に乗っているようにします。
ボールをcanvasに描く関数
const drawBall = (ball) => {
barBallsCtx.fillStyle = ballColor;
barBallsCtx.arc(ball.x, ball.y, ballRadius, 0, 2 * Math.PI);
引数で受け取ったボールをcanvasに描きます。
ボールを操作バーの上に移動させる関数
const moveBallOnBar = () => {
moveBallOnBar
を呼び出すときはボールは1つだけの状態なので balls[0]
のボールを操作バーの上に移動させています。
ボールを動かす関数
const moveBalls = () => {
for (let i = balls.length - 1; i >= 0; i--) {
checkEdgeCollision(ball);
checkBlockCollision(ball);
ボールの座標を移動したあと、操作バーやブロックと衝突しているかの検証を行います。
ここでは balls
の配列の末尾から順番に処理を行います。これは画面下に落ちているかの検証をして落ちていたら balls
からそのボールを削除するためです。
もし配列の先頭から順番に処理をしていて、その配列内から要素を削除すると配列番号がズレてしまいます。
画面端と衝突しているか検証する関数
const checkEdgeCollision = (ball) => {
} else if (ball.leftX < 0) {
} else if (ball.rightX > canvasWidth) {
ball.x = canvasWidth - ballRadius;
canvasの上端や左右の端からはみ出しているかの検証を行います。
もしはみ出していたら、はみ出ないように座標を調整します。そして dx
や dy
の符号を逆にします。こうすることでボールの進行方向を反転することができます。
操作バーと衝突しているか検証する関数
const checkBarCollision = (ball) => {
&& ball.leftX < bar.rightX
&& ball.topY < bar.bottomY
ボールと操作バーの重なり合っている部分があれば、bar.jsで作成した clideBar
を実行します。
ブロックと衝突しているか検証する関数
ブロックと衝突しているか検証するにはボールの上下左右の頂点座標を blocks
の配列番号に変換して、そこにブロックが存在するかで判定します。
この図ではボールの上側の頂点は blocks[3][2]
に位置しています。この場所にはブロックが存在しているので衝突したと判定されます。
座標から配列番号に変換するには、座標÷ブロック幅を計算して小数点を切り捨てることで求めることができます。
たとえば、ボールのy座標が37でブロックの縦幅が10だとしたら、37÷10=3.7です。これの小数点を切り捨てると3となり、これが blocks
の行番号となります。
const checkBlockCollision = (ball) => {
const topRowIndex = Math.floor(ball.topY / blockHeight);
const centerRowIndex = Math.floor(ball.y / blockHeight);
const bottomRowIndex = Math.floor(ball.bottomY / blockHeight);
const leftColumnIndex = Math.floor(ball.leftX / blockWidth);
const centerColumnIndex = Math.floor(ball.x / blockWidth);
const rightColumnIndex = Math.floor(ball.rightX / blockWidth);
if (blocks[topRowIndex] && blocks[topRowIndex][centerColumnIndex]) {
clideBlock(ball, blocks[topRowIndex][centerColumnIndex]);
} else if (blocks[bottomRowIndex] && blocks[bottomRowIndex][centerColumnIndex]) {
clideBlock(ball, blocks[bottomRowIndex][centerColumnIndex]);
} else if (blocks[centerRowIndex] && blocks[centerRowIndex][leftColumnIndex]) {
clideBlock(ball, blocks[centerRowIndex][leftColumnIndex]);
} else if (blocks[centerRowIndex] && blocks[centerRowIndex][rightColumnIndex]) {
clideBlock(ball, blocks[centerRowIndex][rightColumnIndex]);
上下左右の頂点の衝突判定を行って、衝突していたら clideBlock
を実行してから、ボールの進行方向を反転させます。
画面下に落ちているか検証する関数
const checkDropped = (ball, index) => {
if (ball.topY > canvasHeight) {
if (balls.length === 0) {
changeGameState('waiting');
もしボールがcanvasの下に落ちていたら balls
からそのボールを削除します。そしてすべてのボールが落ちてしまったらゲームの状態を waiting
にし、 initBall()
でボールを初期配置(操作バーの上)に生成します。
以上でballs.jsは完成です。