CHAPTER 12 · 全23項目
カプセルと連結ビーム砲
時間制限のある強化と、シールド・連結砲のような回数で管理する効果を分けます。
主な参照先:PU_DEFS / maybeDrop / applyPU / active / BEAM_OFFSETS / BEAM_LOSS / SPARE_BONUS
4種類の効果をデータで定義する
const PU_DEFS = {
wide: { label: "W", color: "#61e8ff", dur: 8, name: "3-WAY!" },
rapid: { label: "R", color: "#ffe066", dur: 8, name: "RAPID!" },
shield: { label: "S", color: "#7dff7a", dur: 0, name: "SHIELD!" },
beam: { label: "B", color: BEAM_COLOR, dur: 0, name: "BEAM LINK", max: BEAM_OFFSETS.length },
};| カプセル | 状態の持ち方 | 効果 |
|---|---|---|
| W | active.wide に残り秒数 | 8秒間、3方向へ射撃 |
| R | active.rapid に残り秒数 | 8秒間、射撃間隔を短縮 |
| S | active.shield に有無 | ダメージを1回防ぐ |
| B | active.beam に砲の数 | 1回の取得で1門ずつ左右交互に増加、最大4門。ビームは1門につき画面内1発まで。ミスで2門減る |
落とす、拾う
function maybeDrop(x, y) {
if (Math.random() > 0.08 || powerups.length >= 2) return;
const ks = Object.keys(PU_DEFS);
powerups.push({ x, y, type: ks[(Math.random() * ks.length) | 0], ph: Math.random() * 6.28 });
}通常の敵の撃破時に、約8%の確率でカプセルを作ります。同時に存在できるのは2個までです。カプセルは毎秒62 pxで落下し、自機より少し大きい回収用の四角形と重なると applyPU() が効果を反映します。
// --- パワーアップカプセル ---
const prInf = { x: player.x - 17, y: PLAYER_Y - 4, w: 34, h: 24 };
for (let i = powerups.length - 1; i >= 0; i--) {
const u = powerups[i];
u.y += 62 * dt;
if (u.y > GROUND_Y) { powerups.splice(i, 1); continue; }
if (player.alive && hit({ x: u.x - 8, y: u.y - 8, w: 16, h: 16 }, prInf)) {
applyPU(u.type);
powerups.splice(i, 1);
}
}カプセルを自機へ引き寄せる処理はないので、拾うには落ちてくる位置へ自機を動かします。地面の高さ GROUND_Y を越えたカプセルは消えます。回収できるのは自機が生存している間だけです。ループの途中で splice により要素を取り除くため、配列の末尾から先頭へ向かって回しています。
連結砲は時間で消えない
function applyPU(type) {
const d = PU_DEFS[type];
let name = d.name;
if (type === "shield") {
// すでにシールドがあるときは効果が重ならないので、代わりに得点にする。
if (active.shield > 0) { addScore(SPARE_BONUS); name = `SHIELD +${SPARE_BONUS}`; }
active.shield = 1;
} else if (type === "beam") {
const wasMax = active.beam === d.max;
active.beam = Math.min(d.max, active.beam + 1);
player.x = clampPlayerX(player.x);
name = `${d.name} ×${active.beam}!`;
if (wasMax) { addScore(SPARE_BONUS); name = `BEAM LINK MAX! +${SPARE_BONUS}`; } // 4 門そろった後も同じく得点に
spawnRing(player.x, PLAYER_Y + 8, d.color, 180, 0.35);
}
else active[type] = d.dur;
popup(player.x, PLAYER_Y - 24, name, d.color, 13);
sfx.pickup();
}Bカプセルは1個につき連結砲を1門追加し、d.max、つまり BEAM_OFFSETS.length の4門で止めます。取るたびに「BEAM LINK ×3!」のように現在の門数を表示します。門数が増えると外側の砲まで画面内に収める必要があるため、clampPlayerX() で自機の位置を補正し直します。
シールドがあるときのS、4門そろった後のBは、取っても効果が増えません。拾い損にならないよう、代わりに SPARE_BONUS の500点を加え、「SHIELD +500」「BEAM LINK MAX! +500」と表示します。
const BEAM_OFFSETS = [-24, 24, -42, 42]; // 連結砲の位置。カプセル 1 個で 1 門ずつ、この順に左右交互に増える
const BEAM_LOSS = 2; // ミス 1 回で失う連結砲の数
const SPARE_BONUS = 500; // 効果のないカプセル(シールド中の S、4 門そろった後の B)を拾ったときの得点砲の位置は BEAM_OFFSETS の先頭から順に使います。1門目は左 (-24)、2門目は右 (+24)、3門目はさらに外側の左 (-42)、4門目は右 (+42) なので、取るたびに左右交互に増えていきます。各砲は通常ショットと同じタイミングで縦長の弾を1発ずつ撃ちます。この弾は敵に当たると消える普通の弾で、貫通はしません。
// ビームは通常弾の上限に数えず、共通のクールダウンで同時発射する。
// ただし 1 門につき画面内 1 発までで、前のビームが消えるまでその砲は撃たない(4 門そろっても火力が際限なく増えない)。
let normalBulletCount = 0, beamSlots = 0;
for (const p of pBullets) {
if (p.beam) beamSlots |= 1 << p.slot;
else normalBulletCount++;
}
if ((shooting || keys.fire) && player.cool <= 0 && normalBulletCount < maxB) {
const mk = vx => pBullets.push({ x: player.x - 1, y: PLAYER_Y - SHOT_H, w: 2, h: SHOT_H, vx });
mk(0);
if (active.wide > 0) { mk(-70); mk(70); }
for (let i = 0; i < active.beam; i++) {
if (beamSlots & (1 << i)) continue;
const x = player.x + BEAM_OFFSETS[i];
pBullets.push({ x: x - 2, y: PLAYER_Y - 32, w: 4, h: 28, vx: 0, beam: true, slot: i });
spawnBurst(x, PLAYER_Y - 4, BEAM_COLOR, 3, 55);
}ビームは通常弾の同時発射数の上限には数えません。そのかわり、1門につき画面内に1発までです。発射前に pBullets を1回だけ見て、通常弾の数と、ビームが飛んでいる砲の番号 (slot) をビット (beamSlots) に記録します。ビットが立っている砲は、前の弾が敵に当たるか画面の外へ出るまで撃ちません。近くの敵ほど弾がすぐ消えるので連射が速くなり、4門そろっても火力が際限なく増えることはありません。
active.beam = Math.max(0, active.beam - BEAM_LOSS);連結砲はW・R・Sと併用でき、次のウェーブにも持ち越せます。シールドで防げなかったミスでは BEAM_LOSS の2門だけを失い、残りは復帰後もそのまま使えます。4門なら2門に、1門なら0門になります。シールドが攻撃を防いだ場合は1門も失いません。