ソースコード解説・目次

CHAPTER 12 · 全23項目

カプセルと連結ビーム砲

時間制限のある強化と、シールド・連結砲のような回数で管理する効果を分けます。

主な参照先:PU_DEFS / maybeDrop / applyPU / active / BEAM_OFFSETS / BEAM_LOSS / SPARE_BONUS

4種類の効果をデータで定義する

game.html · L829–834ソースを見る ↗
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 },
};
カプセル状態の持ち方効果
Wactive.wide に残り秒数8秒間、3方向へ射撃
Ractive.rapid に残り秒数8秒間、射撃間隔を短縮
Sactive.shield に有無ダメージを1回防ぐ
Bactive.beam に砲の数1回の取得で1門ずつ左右交互に増加、最大4門。ビームは1門につき画面内1発まで。ミスで2門減る

落とす、拾う

game.html · L1011–1015ソースを見る ↗
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() が効果を反映します。

game.html · L1712–1722ソースを見る ↗
  // --- パワーアップカプセル ---
  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 により要素を取り除くため、配列の末尾から先頭へ向かって回しています。

連結砲は時間で消えない

game.html · L1017–1035ソースを見る ↗
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」と表示します。

game.html · L808–810ソースを見る ↗
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発ずつ撃ちます。この弾は敵に当たると消える普通の弾で、貫通はしません。

game.html · L1611–1627ソースを見る ↗
  // ビームは通常弾の上限に数えず、共通のクールダウンで同時発射する。
  // ただし 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門そろっても火力が際限なく増えることはありません。

game.html · L1533–1533ソースを見る ↗
  active.beam = Math.max(0, active.beam - BEAM_LOSS);

連結砲はW・R・Sと併用でき、次のウェーブにも持ち越せます。シールドで防げなかったミスでは BEAM_LOSS の2門だけを失い、残りは復帰後もそのまま使えます。4門なら2門に、1門なら0門になります。シールドが攻撃を防いだ場合は1門も失いません。