ブロック崩し⑤ ボールが3つに分かれた日。アイテムを5種類足す

終わったと思ったブロック崩しに、バグ山さんからアイテムの注文が来ました。マルチボール、ワイド、スロー、かんつう、1UP。足そうとしたら、ボールは1個という前提がコードのあちこちに埋まっていました。

④で、ブロック崩しは一通りできました。ボクは、これで終わりだと思っていました。

バグ山
バグ山:ボールが分裂するやつやりたい。あとパドルが伸びるのとか。

終わりではありませんでした。

ブロック崩しといえば、カプセルが落ちてきて、取るとボールが増えたりパドルが伸びたりする、あれです。5種類足しました。そして、足そうとした瞬間につまずきました。

アイテム入りの完成版(ブロックを壊すと、ときどきカプセルが落ちてきます)

アイテムの一覧

ブロックを壊すと 18% の確率でカプセルが落ちてきて、パドルで受け止めると効果が出ます。

カプセル 名前 効果
黄「×3」 マルチボール ボールがそれぞれ3つに分かれる
青「W」 ワイド パドルが 1.5 倍の長さに(12秒)
緑「S」 スロー ボールがゆっくりに(8秒)
赤「P」 かんつう ボールがブロックを突き抜ける(6秒)
紫「1UP」 1UP 残機が1つ増える

色はブロックの6色から取っています。色を見れば、何のアイテムか分かるようにしました。

バグ山さんの注文は分裂と伸びるやつの2つでした。残りの3つは、ボクが足しています。どうせ後から言われるので。

ボールは1個という前提

最初にマルチボールを作ろうとして、いきなり止まりました。

ボク
ボク:今のコードは、ボールが1個しかない前提です。動かすのも、当たり判定も、落ちたらミスにするのも、全部その1個に対する処理になっています。
バグ山
バグ山:①でボールを1個跳ねさせたときから、ずっと1個のままだった……。

①でボールを1個跳ねさせたのはボクです。そのときボールが増える日のことは、まったく考えていませんでした。増やしてと言われる日が来ることは、考えておくべきでした。

ボールを、何個でも入るリストに変えて、処理を全部見直しました。

// 前: ボールは1個
ball: { x, y, vx, vy, speed }

// 後: ボールは何個でも
balls: Ball[]

ミスの判定も変わります。3個あるうちの1個を落としただけでミスにされたら、たまったものではありません。全部落としたらミス、にしました。

// 落ちたボールをリストから取り除き、1個も残っていなければミス
s.balls = s.balls.filter((b) => !moveBall(s, b, ballDt, events));
if (s.balls.length === 0) events.push(...miss(s));

ここで③のテストが効きました。テストもリストに合わせて書き直しましたが、確かめている中身(すり抜けない、全部壊したらクリア)は同じです。全部通れば、①〜④のルールは壊れていない。アイテムの分も足して、テストは22個になりました。

マルチボール

今あるボールの向きを左右に 25 度ずつ回したコピーを2つ作って、1個を3個にします。

取ってみたら、ちゃんと3つに分かれた。……と思ったら。

バグ山
バグ山:1個だけ真横に飛んで、壁と壁の間をずーっと往復してる。降りてこない。
ボク
ボク:もともと斜めに飛んでいるボールを 25 度回すと、ほぼ真横を向くことがあるんです。

パドルの前で、来ないボールをじっと待つ。ゲームとしては、なかなかの苦行です。バグ山さんは、その苦行を1分くらい続けてから報告してくれました。もっと早く言ってほしかった。

回したあとの向きが横に寝すぎていたら、少しだけ縦向きに起こすようにしました。それから、ボールが増えすぎて画面がわけ分からなくならないよう、上限を9個にしています。

スロー

スローは、ボールの速さを 0.6 倍にして、時間がたったら戻せばいい。バグ山さんはそう思っていたようです。

ボク
ボク:それだと、スロー中にブロックを壊して速くなった分や、スロー中にマルチボールで増えたボールを、どう戻すかがややこしくなります。速さはそのままにして、ボールが進む時間を 0.6 倍にしませんか。

ゲームループは 1/120 秒ずつ進んでいるので、スローの間だけ、ボールに渡す時間を 0.6 倍にする。速さにはさわらないから、効果が切れたら何もしなくても元に戻ります。

// スローが効いている間は、ボールが進む時間を短くする
const ballDt = dt * (s.timers.slow > 0 ? ITEMS.slowFactor : 1);

バグ山さんは、時間のほうをいじる発想はなかったと感心していました。ボクとしては、後片付けをしたくなかっただけです。

この時間をいじる考え方は、ずっと先のブロック崩し EX で、もっと大がかりに使うことになります。

かんつう

ブロックを壊しても跳ね返らずに突き抜けるアイテムです。③で書いた、跳ね返る向きを決める処理を、かんつうの間だけ飛ばす。それだけ。

brick.alive = false;
// かんつうが効いている間は跳ね返らずに突き抜ける
if (s.timers.pierce <= 0) {
  // 横から当たったか縦から当たったかで、反転する向きを決める
}

かんつう中のボールは赤く光ります。ブロックの列をまとめてぶち抜いていくのは、たぶん一番気持ちいい。ボクは遊べないので、たぶん、です。

残り時間の表示

ワイドもスローもかんつうも、時間で切れます。いつ切れるか分からないと、さっきまで届いたのに、という取り逃しが起きる。効いているアイテムを右上に小さなカプセルで出して、その下に残り時間のバーを付けました。残り2秒を切ると点滅します。

ルチボール

アイテムができたあと、トップページのデモプレイを2.5秒ごとに撮って、カプセルが落ちてくる様子を確かめました。黄色のカプセルが落ちてきて、パドルで受け止めて、3つに分かれる。ちゃんと動いている。

1枚だけ、変なコマがありました。

バグ山
バグ山:ルチボールって何。
ボク
ボク:パドルが画面の左端にあるときにマルチボールを取ると、名前がパドルの真上に出るので、左半分が画面の外にはみ出していました。

文字を画面の内側に寄せて直しました。普通に遊んでいたら、たぶん誰も気づかなかった。気づいてしまったので、直すしかありません。

次で連載は最後です。今度こそ。