CHAPTER 16 · 全23項目
編隊から突入する敵
敵に一時的な飛行状態を追加し、急降下・ジグザグ・横方向の突入を切り替えます。
主な参照先:launchFlight / updateFlights / alienRectInto / flightT / DIVER_SHOT_WAVE
同じ敵に飛行データを付け足す
function launchFlight(a, mode) {
const r = alienRect(a);
a.flight = { x: r.x, y: r.y, startX: r.x, t: 0, warning: 0.65, mode, phase: "exit",
vx: clamp((player.x - r.x) * 0.5, -90, 90), side: r.x < W / 2 ? -1 : 1 };
}突入用の別の敵を作るのではなく、元の敵へ flight を付けます。編隊の行と列は残したまま、現在座標、経過時間、0.65秒の予告、飛び方、左右の向きなどを保存します。予告中も編隊の共通移動から外れます。
3種類の軌道
| モード | 位置の更新 |
|---|---|
dive | 下へ進みながら、開始時に決めた横速度を加える |
zigzag | Math.sin(f.t * 5) * 54 で左右に揺れながら降下 |
flank | 一度横へ退場し、自機の少し上の高さから横切る |
突入はウェーブ2以降で、ボス戦と開始バナー中には発生しません。候補は生きていてまだ飛行していない敵です。同時に飛べる数と発生間隔はウェーブに応じて変化します。モードは3種類を順番に使います。
const speed = Math.min(260, 155 + wave * 7);
if (f.mode === "flank" && f.phase === "exit") {
f.x += f.side * speed * dt;
if (f.x < -30 || f.x > W + 30) {
f.phase = "attack"; f.y = PLAYER_Y - 160;
f.x = f.side < 0 ? -28 : W + 4;
}
} else {
f.y += speed * dt;
if (f.mode === "zigzag") f.x = clamp(f.startX + Math.sin(f.t * 5) * 54, 5, W - 29);
else f.x += (f.mode === "flank" ? -f.side * speed * 1.5 : f.vx) * dt;
// ウェーブ 11 からは、降下の途中で(自機から 200px 以上離れているうちに)自機を狙って 1 発撃つ。
if (wave >= DIVER_SHOT_WAVE && !f.shot && f.y > PLAYER_Y - 320 && f.y < PLAYER_Y - 200 &&
f.x > 0 && f.x < W - 24 && eBullets.length < maxEnemyBullets()) {
const t = TYPES[a.type];
f.shot = true;
enemyShot(f.x + t.w / 2 - 1.5, f.y + t.h, true);
}
if (player.alive && player.inv <= 0 && hit(alienRect(a), playerRect())) damagePlayer();
if (f.y > GROUND_Y + 24 || f.x < -60 || f.x > W + 60) a.flight = null;
}
if (state !== "play") return;ウェーブ11 (DIVER_SHOT_WAVE) からは、降下の途中で自機を狙って1発撃ちます。撃つのは自機から200〜320 px上にいる間だけなので、弾が届くまでに避ける時間があります。f.shot に撃ったことを記録して、1回の突入で1発に限ります。画面の外にいる間と、画面内の敵弾が上限に達しているときは撃たず、範囲内にいる間に上限が空けば撃ちます。弾は編隊の狙い撃ちと同じ enemyShot() で作ります(「07 敵の編隊とウェーブ生成」)。
画面外へ出たら編隊へ戻す
飛行範囲から出た敵は a.flight = null に戻します。次の当たり判定・描画では、元の行と列に現在の編隊原点を足した位置を使います。敵は生存したままなので、撃破するまでウェーブが続きます。