using UnityEngine; using UnityEngine.AI; using System.Collections.Generic; [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(Animator))] public class BossController : MonoBehaviour { [SerializeField] private float maxHp = 500f; [SerializeField] private float currentHp = 500f; [field: SerializeField] public float ChaseRange { get; private set; } = 15f; [field: SerializeField] public float BiteRange { get; private set; } = 5f; [field: SerializeField] public float ChargeSpeed { get; private set; } = 14f; [field: SerializeField] public float ChargeDuration { get; private set; } = 2f; [field: SerializeField] public float ChargeMinimumDistance { get; private set; } = 6f; [field: SerializeField] public float ChargeCooldownDuration { get; private set; } = 4f; [Tooltip("突進する前の、その場での咆哮・溜め時間(秒)")] [field: SerializeField] public float ChargeRoarDuration { get; private set; } = 5f; // ★インスペクターに逃がして保守性を向上 [SerializeField] public Transform ceilingAnchor; [SerializeField] public Transform groundAnchor; [field: SerializeField] public float AttackRadius { get; private set; } = 10f; [field: SerializeField] public float JumpRoarDuration { get; private set; } = 2f; [SerializeField] private GameObject warningMarkerPrefab; [SerializeField] private GameObject webPrefab; public NavMeshAgent Agent { get; private set; } public Animator Anim { get; private set; } public Transform Player { get; private set; } public float ChargeCooldownTimer { get; private set; } = 0f; public float NormalSpeed { get; private set; } public float NormalAcceleration { get; private set; } private ISpiderState currentState; private Dictionary stateCache; // フェーズトリガー private bool triggered50 = false; private bool triggered25 = false; public bool PendingUltimate { get; set; } = false; private void Awake() { Agent = GetComponent(); Anim = GetComponent(); GameObject pObj = GameObject.FindGameObjectWithTag("Player"); if (pObj != null) Player = pObj.transform; InitializeFSM(); } private void Start() { if (Agent != null) { NormalSpeed = Agent.speed; NormalAcceleration = Agent.acceleration; } } private void InitializeFSM() { stateCache = new Dictionary { { typeof(SpiderIdleState), new SpiderIdleState(this) }, { typeof(SpiderChaseState), new SpiderChaseState(this) }, { typeof(SpiderBiteState), new SpiderBiteState(this) }, { typeof(SpiderChargeState), new SpiderChargeState(this) }, { typeof(SpiderJumpToCeilingState), new SpiderJumpToCeilingState(this) }, { typeof(SpiderCeilingAttackState), new SpiderCeilingAttackState(this) }, { typeof(SpiderStunState), new SpiderStunState(this) } }; ChangeState(); } private void Update() { currentState?.UpdateState(); if (ChargeCooldownTimer > 0f) { ChargeCooldownTimer -= Time.deltaTime; } } public void ChangeState() where T : ISpiderState { if (currentState != null) { currentState.ExitState(); ResetAllAnimationTriggers(); Debug.Log($"🕷️ BossSpider [Exit]: {currentState.GetType().Name}"); } currentState = stateCache[typeof(T)]; Debug.Log($"🕷️ BossSpider [Enter]: {currentState.GetType().Name}"); currentState.EnterState(); } public void TakeDamage(float damage) { currentHp -= damage; currentHp = Mathf.Clamp(currentHp, 0, maxHp); float hpPercentage = (currentHp / maxHp) * 100f; Debug.Log($"💥 ボスに {damage} ダメージ。残りHP: {hpPercentage:F1}%"); if (hpPercentage <= 25f && !triggered25) { triggered25 = true; PendingUltimate = true; TriggerPhaseTransition(); } else if (hpPercentage <= 50f && !triggered50 && hpPercentage > 25f) { triggered50 = true; PendingUltimate = true; TriggerPhaseTransition(); } } private void TriggerPhaseTransition() { if (currentState is not SpiderJumpToCeilingState && currentState is not SpiderCeilingAttackState && currentState is not SpiderStunState) { ChangeState(); } } public void TriggerParryStun() { if (currentState is SpiderBiteState || currentState is SpiderChargeState) { ChangeState(); } } public void ResetChargeCooldown() { ChargeCooldownTimer = ChargeCooldownDuration; } public void SpawnWarningAndWeb(Vector3 targetGroundPos, float delay, float webDuration) { StartCoroutine(SpawnWebRoutine(targetGroundPos, delay, webDuration)); } private System.Collections.IEnumerator SpawnWebRoutine(Vector3 pos, float delay, float duration) { GameObject marker = null; if (warningMarkerPrefab != null) { marker = Instantiate(warningMarkerPrefab, pos + new Vector3(0, 0.02f, 0), Quaternion.identity); } yield return new WaitForSeconds(delay); if (marker != null) Destroy(marker); if (webPrefab != null) { GameObject web = Instantiate(webPrefab, pos, Quaternion.identity); Destroy(web, duration); } } public void ResetAllAnimationTriggers() { if (Anim == null) return; Anim.ResetTrigger("Attack_Bite"); Anim.ResetTrigger("Attack_Roar"); Anim.ResetTrigger("ChargeStart"); Anim.ResetTrigger("JumpUp"); Anim.ResetTrigger("Land"); Anim.ResetTrigger("StunStart"); } } /// 共通データ(boss参照)を自動保持し、各子ステートの無駄なコードを極限まで削ります。 public abstract class SpiderStateBase : ISpiderState { protected readonly BossController boss; protected SpiderStateBase(BossController owner) { boss = owner; } public virtual void EnterState() { } public abstract void UpdateState(); public virtual void ExitState() { } } public interface ISpiderState { void EnterState(); void UpdateState(); void ExitState(); }