14. ボスの攻撃

はじめに

ボスが火の玉を吐くようにしました。5発のホーミング弾を連続して発射し、しばらく休む、を繰り返します。

現在は、プレイヤーだけが武器を持っていますが、敵も武器を持てるように修正しました。 合わせて武器周りのリファクタも進めました。

方針

  • 武器にOwnerコンポーネントを追加
    • コリジョン判定を適切に設定
      • 武器のOwnerがプレイヤー → 弾は敵に当たる
      • 武器のOwnerが敵 → 弾はプレイヤーに当たる
    • Owner死亡時に武器Entityを削除
  • ボス用の武器(BossFireballWeapon)を追加
    • BossFireballSystemを追加
      • 連続して発射→休憩 を繰り返す
    • ProjectileMovementSystemを追加
      • ホーミング用の関数を追加

実装

連続して発射と休憩

  • stateを使って、“idle”,“firing”,“waiting"のphaseを切り替え
    • 初期状態は"idle"で、後は"firing”↔“waiting"を繰り返し
  • shotCooldown, waitingCooldownで弾の間隔と休憩時間をコントロール
update(_delta: number) {
  for (const [weaponEntity, fireball] of ComponentManager.entries("bossFireball")) {
    // 武器のオーナーが死亡している場合は処理をスキップ
    const owner = ComponentManager.get("owner", weaponEntity).owner;
    if (ComponentManager.has("deadFlag", owner)) continue;

    const { state } = fireball;
    const params = EnemyConfig.enemies.boss.weapon!;

    switch (state.phase) {
      case "idle":
        // 発射状態を更新
        state.phase = "waiting";
        state.waitingCooldown = params.initialCooldown; // 初期待機時間
        break;
      case "firing":
        state.shotCooldown -= _delta;
        if (state.shotCooldown > 0) break;
        // 発射間隔が終了したら、弾を発射
        this.createBossFireballProjectiles(this.scene, owner);
        state.remainingShots -= 1; // 残弾数を減少
        state.shotCooldown = params.shotCooldown; // 発射間隔を設定
        // 残弾数が0になったら、待機状態に移行
        if (state.remainingShots <= 0) {
          state.phase = "waiting";
          state.waitingCooldown = params.waitingCooldown; // 待機時間を設定
          break;
        }
        break;
      case "waiting":
        state.waitingCooldown -= _delta;
        if (state.waitingCooldown > 0) break;
        // 待機時間が終了したら、発射状態に戻る
        state.phase = "firing";
        state.remainingShots = params.numberOfShots; // 残弾数を設定
        state.shotCooldown = params.shotCooldown; // 発射間隔を設定
        break;
    }
  }
}

ホーミング移動

  • turnRateは1秒間に何度回転するかをラジアン単位で指定するものです。PI/2〜PI/3程度が適切です。
private calcHomingVelocity(
  velocity: XY,
  position: XY,
  target: XY,
  speed: number,
  turnRate: number,
  delta: number
): XY {
  const desired = Math.atan2(target.y - position.y, target.x - position.x);
  let angle: number | undefined = undefined;
  // 初回や停止中は目標角度に設定
  if (Math.abs(velocity.x) < 0.001 && Math.abs(velocity.y) < 0.001) {
    angle = desired;
  } else {
    // 現在の角度と目標角度から新しい角度を計算
    const current = Math.atan2(velocity.y, velocity.x);
    const frameRotation = turnRate * (delta / 1000);
    angle = Phaser.Math.Angle.RotateTo(current, desired, frameRotation);
  }
  // 速度を計算
  return { x: Math.cos(angle) * speed, y: Math.sin(angle) * speed };
}

まとめ

ボスがホーミング火の玉を吐くようになりました。