using UnityEngine; // ========================================== // ■ STATE: IDLE (待機状態) // ========================================== public class SpiderIdleState : ISpiderState { private BossController boss; public SpiderIdleState(BossController owner) => boss = owner; public void EnterState() => boss.Anim.SetTrigger("Idle"); public void ExitState() { } public void UpdateState() { if (boss.Player == null) return; // プレイヤーを検知したら即座に追跡へ if (Vector3.Distance(boss.transform.position, boss.Player.position) <= boss.chaseRange) { boss.ChangeState(); } } } // ========================================== // ■ STATE: CHASE (追跡・行動分岐状態) // ========================================== // ========================================== // ■ STATE: CHASE (追跡・行動分岐状態) - 13x13ステージ最適化版 // ========================================== // ========================================== // ■ STATE: CHASE (遠距離即座突進・最適化版) // ========================================== public class SpiderChaseState : ISpiderState { private BossController boss; public SpiderChaseState(BossController owner) => boss = owner; public void EnterState() => boss.Agent.isStopped = false; public void ExitState() => boss.Agent.isStopped = true; public void UpdateState() { if (boss.Player == null) return; float distance = Vector3.Distance(boss.transform.position, boss.Player.position); // ─── ★最優先判定:遠距離(6m以上)かつクールダウン終了 ➔ 1歩も歩かずに即突進 ─── if (distance >= boss.chargeMinimumDistance && boss.ChargeCooldownTimer <= 0f) { boss.ChangeState(); return; // 移行したので以下の歩き処理は実行しない } // 判定2:至近距離(2.5m以内)➔ 通常噛みつき if (distance <= boss.biteRange) { boss.ChangeState(); return; } // 判定3:中間距離(2.5m~6m)、または遠距離だけどクールダウン中の場合 // 💡 ここで初めて「通常歩き(Walk)」でじりじりとプレイヤーに近づきます boss.Agent.SetDestination(boss.Player.position); boss.Anim.SetFloat("Speed", boss.Agent.velocity.magnitude); } } // ========================================== // ■ STATE: BITE (通常噛みつき - パリィ可) // ========================================== public class SpiderBiteState : ISpiderState { private BossController boss; private float stateTimer; public SpiderBiteState(BossController owner) => boss = owner; public void EnterState() { stateTimer = 1.5f; // 攻撃アニメーションの想定想定想定 boss.Anim.SetTrigger("Attack_Bite"); Debug.Log("🎯 蜘蛛ボス:噛みつき攻撃!(パリィ受付中)"); } public void ExitState() { } public void UpdateState() { stateTimer -= Time.deltaTime; if (stateTimer <= 0f) { // 攻撃が終わったら、フェーズ移行チェックを挟みつつ追跡に戻る if (boss.PendingUltimate) boss.ChangeState(); else boss.ChangeState(); } } } // ========================================== // ■ STATE: CHARGE (走って体当たり突進 - パリィ可) // ========================================== // ========================================== // ■ STATE: CHARGE (咆哮・溜めロック ➔ ロケット発射) // ========================================== public class SpiderChargeState : ISpiderState { private BossController boss; private float roarTimer; private float chargeTimer; private Vector3 chargeDirection; private bool isChargingPhase; public SpiderChargeState(BossController owner) => boss = owner; public void EnterState() { roarTimer = 1.5f; // 咆哮・溜めロックの時間(1.5秒) chargeTimer = boss.chargeDuration; isChargingPhase = false; boss.Agent.enabled = false; // その場に固定するためNavMeshを即オフ boss.ResetChargeCooldown(); // 突進のクールダウンを開始 // 1. 遠距離を検知した「その場」で、即座に咆哮威嚇を再生 boss.Anim.SetTrigger("Attack_Roar"); Debug.Log("🔊 蜘蛛ボス:【遠距離検知】その場で咆哮・突進の溜めを開始!"); } public void ExitState() { boss.Anim.SetBool("IsCharging", false); boss.Agent.enabled = true; } public void UpdateState() { // ─── フェーズ1:咆哮・溜め(動かずプレイヤーの方向を完全にロック) ─── if (!isChargingPhase) { roarTimer -= Time.deltaTime; // プレイヤーが動いても、突進直前までずーーっとその方向を向いてロックオンし続ける if (boss.Player != null) { Vector3 lookDir = (boss.Player.position - boss.transform.position).normalized; lookDir.y = 0; if (lookDir != Vector3.zero) { // 凄まじい速度でプレイヤーを正面に捉え続ける boss.transform.rotation = Quaternion.Slerp(boss.transform.rotation, Quaternion.LookRotation(lookDir), Time.deltaTime * 10f); } } // 溜め時間が終わったら「発射」 if (roarTimer <= 0f) { isChargingPhase = true; boss.Anim.SetTrigger("ChargeStart"); boss.Anim.SetBool("IsCharging", true); // 発射した瞬間のプレイヤーのベクトルを最終確定(ロック完了) if (boss.Player != null) { chargeDirection = (boss.Player.position - boss.transform.position).normalized; chargeDirection.y = 0; boss.transform.rotation = Quaternion.LookRotation(chargeDirection); } Debug.Log("🚀 蜘蛛ボス:ロックオン完了、全速力で発射!"); } return; } // ─── フェーズ2:発射(端から端まで全速力ダッシュ) ─── // 【安全対策:レイキャストによる壁検知】 // 13x13ステージの端(壁や柱)に激突してめり込むのを防ぐため、ボスの目の前1.5mに障害物がないかチェック Ray ray = new Ray(boss.transform.position + Vector3.up * 0.5f, chargeDirection); if (Physics.Raycast(ray, 1.5f)) { Debug.Log("🔒 蜘蛛ボス:ステージの端(壁)を検知したため、突進を安全に終了します。"); boss.ChangeState(); return; } // 全速力で直線移動 boss.transform.position += chargeDirection * boss.chargeSpeed * Time.deltaTime; chargeTimer -= Time.deltaTime; if (chargeTimer <= 0f) { // 時間を駆け抜けたら追跡に戻る boss.ChangeState(); } } } // ========================================== // ■ STATE: JUMP TO CEILING (天井への大ジャンプ) // ========================================== public class SpiderJumpToCeilingState : ISpiderState { private BossController boss; private float roarTimer; private float jumpProgress; private Vector3 startPos; private bool isJumpingPhase; public SpiderJumpToCeilingState(BossController owner) => boss = owner; public void EnterState() { roarTimer = 2.0f; // ★天井に飛ぶ前の大威嚇の時間 (2.0秒) jumpProgress = 0f; startPos = boss.transform.position; isJumpingPhase = false; boss.Agent.enabled = false; // 1. 必殺技移行を知らせる威嚇の咆哮 boss.Anim.SetTrigger("Attack_Roar"); Debug.Log("🚨 蜘蛛ボス:必殺技前の一大威嚇(咆哮)! このあと天井へ飛びます。"); } public void ExitState() { } public void UpdateState() { // フェーズ1:飛び立つ前の威嚇ポーズ(その場で足止め) if (!isJumpingPhase) { roarTimer -= Time.deltaTime; if (roarTimer <= 0f) { isJumpingPhase = true; boss.Anim.SetTrigger("JumpUp"); // ジャンプアニメーションへ } return; } // フェーズ2:本来の大ジャンプ空中移動 if (boss.ceilingAnchor == null) return; jumpProgress += Time.unscaledDeltaTime * 1.5f; Vector3 currentPos = Vector3.Lerp(startPos, boss.ceilingAnchor.position, jumpProgress); currentPos.y += Mathf.Sin(jumpProgress * Mathf.PI) * 3f; boss.transform.position = currentPos; if (jumpProgress >= 1f) { boss.transform.position = boss.ceilingAnchor.position; boss.ChangeState(); } } } // ========================================== // ■ STATE: CEILING ATTACK (必殺技:天井糸吐き) // ========================================== public class SpiderCeilingAttackState : ISpiderState { private BossController boss; private float stateDuration = 7.0f; // 必殺技全体の長さ(秒) private float shotInterval = 0.4f; // 糸を降らせる間隔 private float timer = 0f; private float shotTimer = 0f; public SpiderCeilingAttackState(BossController owner) => boss = owner; public void EnterState() { timer = stateDuration; shotTimer = 0f; boss.Anim.SetBool("IsOnCeiling", true); boss.PendingUltimate = false; // 発動権を消化 Debug.Log("🕸️ 蜘蛛ボス:天井から無数の糸を乱射中!"); } public void ExitState() { boss.Anim.SetBool("IsOnCeiling", false); // 地上の着地座標へ位置をリセットしてNavMeshを復帰 if (boss.groundAnchor != null) boss.transform.position = boss.groundAnchor.position; boss.Agent.enabled = true; boss.Anim.SetTrigger("Land"); } public void UpdateState() { timer -= Time.deltaTime; shotTimer -= Time.deltaTime; if (shotTimer <= 0f) { ExecuteRandomWebAttack(); shotTimer = shotInterval; } // 時間が来たら、地上へ復帰 if (timer <= 0f) { boss.ChangeState(); } } private void ExecuteRandomWebAttack() { // ボスの中央(地上座標)を基準に、ランダムな円内に糸を降らせる Vector2 randomCircle = Random.insideUnitCircle * boss.attackRadius; Vector3 targetGroundPos = new Vector3( boss.groundAnchor.position.x + randomCircle.x, boss.groundAnchor.position.y, boss.groundAnchor.position.z + randomCircle.y ); // 予兆を出し、1.0秒後に2.5秒間残る糸を生成する boss.SpawnWarningAndWeb(targetGroundPos, 1.0f, 2.5f); } } // ========================================== // ■ STATE: STUN (チャンスタイム - パリィ成功時) // ========================================== public class SpiderStunState : ISpiderState { private BossController boss; private float stunTimer; public SpiderStunState(BossController owner) => boss = owner; public void EnterState() { stunTimer = 4.0f; // 4秒間の完全な無防備状態(チャンスタイム) boss.Anim.SetTrigger("StunStart"); boss.Anim.SetBool("IsStunned", true); if (boss.Agent.enabled) boss.Agent.isStopped = true; Debug.Log("🛡️ パリィ成功! 蜘蛛ボスがひっくり返ってスタン中! チャンスタイム!"); } public void ExitState() { boss.Anim.SetBool("IsStunned", false); if (boss.Agent.enabled) boss.Agent.isStopped = false; } public void UpdateState() { stunTimer -= Time.deltaTime; if (stunTimer <= 0f) { // スンが明けたら、もしHPトリガーが裏で引かれていれば優先して天井へ if (boss.PendingUltimate) boss.ChangeState(); else boss.ChangeState(); } } }