ロゴシュミグラム

ボールの関数の作成

ボールの座標

ボールの上下左右の座標を次のように名づけます。

ブロック崩しのボールの座標

ボールの中心の座標がxとyです。

ボールの上下の頂点座標がtopYとbottomY、左右の頂点座標がleftXとrightXです。

ボールを移動するときはxとyの値を変えていきます。

ボールを生成する関数

balls.jsにボールのための関数を作成していきます。

今回のゲームではボールを複数増やせるようにします。そこで balls という変数に配列を用意して、その中にボールを生成していきます。

balls.js

let balls = [];
const createBall = (x, y) => {
const ball = {
x: x,
y: y,
dx: 3,
dy: -3,
__proto__: ballProto
};
balls.push(ball);
};
const ballProto = {
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となります。

ブロック崩しのボールの移動

操作バーを作ったときのようにゲッターで上下左右の座標を取得できるようにしています。

これらのゲッターはすべてのボールで同じ内容の関数なのでプロトタイプで渡しています。こうしないと一つ一つのボールに同じ関数が生成されて無駄にメモリを消費してしまいます。

ボールを初期配置する関数

balls.js

const initBall = () => {
balls = [];
createBall(bar.x, bar.y - ballRadius);
};

ボールを初期配置は操作バーの上に乗っているようにします。

ブロック崩しのボールを操作バーに乗せる

ボールをcanvasに描く関数

balls.js

const drawBall = (ball) => {
barBallsCtx.fillStyle = ballColor;
barBallsCtx.beginPath();
barBallsCtx.arc(ball.x, ball.y, ballRadius, 0, 2 * Math.PI);
barBallsCtx.fill();
};

引数で受け取ったボールをcanvasに描きます。

ボールを操作バーの上に移動させる関数

balls.js

const moveBallOnBar = () => {
balls[0].x = bar.x;
drawBall(balls[0]);
};

moveBallOnBar を呼び出すときはボールは1つだけの状態なので balls[0] のボールを操作バーの上に移動させています。

ボールを動かす関数

balls.js

const moveBalls = () => {
for (let i = balls.length - 1; i >= 0; i--) {
const ball = balls[i];
// ボールの座標を移動させる
ball.x += ball.dx;
ball.y += ball.dy;
// 画面端と衝突しているかの検証
checkEdgeCollision(ball);
// 操作バーと衝突しているかの検証
checkBarCollision(ball);
// ブロックと衝突しているかの検証
checkBlockCollision(ball);
// 画面下に落ちているかの検証
checkDropped(ball, i);
// ボールを描く
drawBall(ball);
}
};

ボールの座標を移動したあと、操作バーやブロックと衝突しているかの検証を行います。

ここでは balls の配列の末尾から順番に処理を行います。これは画面下に落ちているかの検証をして落ちていたら balls からそのボールを削除するためです。

もし配列の先頭から順番に処理をしていて、その配列内から要素を削除すると配列番号がズレてしまいます。

画面端と衝突しているか検証する関数

balls.js

const checkEdgeCollision = (ball) => {
// canvasの上端からはみ出している場合
if (ball.topY < 0) {
ball.y = ballRadius;
ball.dy = -ball.dy;
// canvasの左端からはみ出している場合
} else if (ball.leftX < 0) {
ball.x = ballRadius;
ball.dx = -ball.dx;
// canvasの右端からはみ出している場合
} else if (ball.rightX > canvasWidth) {
ball.x = canvasWidth - ballRadius;
ball.dx = -ball.dx;
}
};

canvasの上端や左右の端からはみ出しているかの検証を行います。

もしはみ出していたら、はみ出ないように座標を調整します。そして dxdy の符号を逆にします。こうすることでボールの進行方向を反転することができます。

ブロック崩しのボールの反転

操作バーと衝突しているか検証する関数

balls.js

const checkBarCollision = (ball) => {
if (
ball.rightX > bar.leftX
&& ball.leftX < bar.rightX
&& ball.bottomY > bar.y
&& ball.topY < bar.bottomY
) {
clideBar(ball);
}
};

ボールと操作バーの重なり合っている部分があれば、bar.jsで作成した clideBar を実行します。

ブロックと衝突しているか検証する関数

ブロックと衝突しているか検証するにはボールの上下左右の頂点座標を blocks の配列番号に変換して、そこにブロックが存在するかで判定します。

ブロック崩しのブロックとの衝突検証

この図ではボールの上側の頂点は blocks[3][2] に位置しています。この場所にはブロックが存在しているので衝突したと判定されます。

座標から配列番号に変換するには、座標÷ブロック幅を計算して小数点を切り捨てることで求めることができます。

たとえば、ボールのy座標が37でブロックの縦幅が10だとしたら、37÷10=3.7です。これの小数点を切り捨てると3となり、これが blocks の行番号となります。

balls.js

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]);
ball.dy = -ball.dy;
// ボールの下端がブロックと衝突した場合
} else if (blocks[bottomRowIndex] && blocks[bottomRowIndex][centerColumnIndex]) {
clideBlock(ball, blocks[bottomRowIndex][centerColumnIndex]);
ball.dy = -ball.dy;
// ボールの左端がブロックと衝突した場合
} else if (blocks[centerRowIndex] && blocks[centerRowIndex][leftColumnIndex]) {
clideBlock(ball, blocks[centerRowIndex][leftColumnIndex]);
ball.dx = -ball.dx;
// ボールの右端がブロックと衝突した場合
} else if (blocks[centerRowIndex] && blocks[centerRowIndex][rightColumnIndex]) {
clideBlock(ball, blocks[centerRowIndex][rightColumnIndex]);
ball.dx = -ball.dx;
}
};

上下左右の頂点の衝突判定を行って、衝突していたら clideBlock を実行してから、ボールの進行方向を反転させます。

画面下に落ちているか検証する関数

balls.js

const checkDropped = (ball, index) => {
if (ball.topY > canvasHeight) {
// ボールをballsから削除する
balls.splice(index, 1);
// ボールがすべて落ちた場合
if (balls.length === 0) {
changeGameState('waiting');
initBall();
}
}
};

もしボールがcanvasの下に落ちていたら balls からそのボールを削除します。そしてすべてのボールが落ちてしまったらゲームの状態を waiting にし、 initBall() でボールを初期配置(操作バーの上)に生成します。

以上でballs.jsは完成です。