using UnityEngine; using UnityEngine.AI; using System.Collections; using System; public abstract class MonsterClass : MonoBehaviour, IDamageable { [Header("--- 최적화 설정 ---")] protected Renderer mobRenderer; protected Transform playerTransform; [SerializeField] protected float optimizationDistance = 40f; [Header("스탯")] [SerializeField] protected float maxHP = 100f; protected float currentHP; public event Action OnHealthChanged; [Header("피격 / 사망 / 대기 애니메이션")] // ⭐ Monster_Idle 추가 [SerializeField] protected string Monster_Idle = "Monster_Idle"; [SerializeField] protected string Monster_GetDamage = "Monster_GetDamage"; [SerializeField] protected string Monster_Die = "Monster_Die"; protected Animator animator; protected NavMeshAgent agent; protected AudioSource audioSource; protected bool isHit, isDead, isAttacking; [Header("AI 설정 (공격 후 휴식)")] [SerializeField] protected float attackRestDuration = 1.5f; protected bool isResting; [Header("경험치")] [SerializeField] protected int expReward = 10; public static System.Action OnMonsterKilled; [Header("공통 사운드/이펙트")] [SerializeField] protected AudioClip hitSound, deathSound; [SerializeField] protected GameObject deathEffectPrefab; [SerializeField] protected ParticleSystem hitEffect; [SerializeField] protected Transform impactSpawnPoint; protected virtual void Awake() { mobRenderer = GetComponentInChildren(); animator = GetComponent(); agent = GetComponent(); audioSource = GetComponent(); } protected virtual void OnEnable() { isDead = false; currentHP = maxHP; OnHealthChanged?.Invoke(currentHP, maxHP); playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; if (mobRenderer != null) mobRenderer.enabled = true; Init(); if (MobUpdateManager.Instance != null) MobUpdateManager.Instance.RegisterMob(this); } protected virtual void OnDisable() { if (MobUpdateManager.Instance != null) MobUpdateManager.Instance.UnregisterMob(this); } protected virtual void Init() { } protected abstract void ExecuteAILogic(); public void OnManagedUpdate() { if (isDead || playerTransform == null || !gameObject.activeInHierarchy) return; float distance = Vector3.Distance(transform.position, playerTransform.position); // 1. 범위 밖 최적화 if (distance > optimizationDistance) { StopMovement(); // ⭐ 여기서 Idle 애니메이션을 강제합니다. if (mobRenderer != null && mobRenderer.enabled) mobRenderer.enabled = false; return; } if (mobRenderer != null && !mobRenderer.enabled) mobRenderer.enabled = true; // 2. 시야 밖 최적화 if (mobRenderer != null && !mobRenderer.isVisible) { StopMovement(); return; } if (agent != null && agent.isOnNavMesh && agent.isStopped) { agent.isStopped = false; } ExecuteAILogic(); } // ⭐ [핵심 수정] 멈출 때 Idle 애니메이션을 즉시 실행합니다. protected void StopMovement() { if (agent != null && agent.isOnNavMesh) { agent.isStopped = true; agent.velocity = Vector3.zero; } if (animator != null) { animator.SetFloat("Speed", 0f); // 블렌드 트리용 animator.Play(Monster_Idle); // ⭐ 상태 기반 애니메이션 강제 전환 } } public virtual void TakeDamage(float amount) { OnDamaged(amount); } public virtual void OnDamaged(float damage) { if (isDead) return; currentHP -= damage; OnHealthChanged?.Invoke(currentHP, maxHP); if (currentHP <= 0) { Die(); return; } if (!isHit) StartHit(); } protected virtual void StartHit() { isHit = true; isAttacking = false; isResting = false; StopAllCoroutines(); if (agent && agent.isOnNavMesh) { agent.isStopped = true; agent.velocity = Vector3.zero; } animator.Play(Monster_GetDamage, 0, 0f); if (hitEffect) hitEffect.Play(); if (hitSound) audioSource.PlayOneShot(hitSound); } public virtual void OnAttackStart() { isAttacking = true; isResting = false; } public virtual void OnAttackEnd() { isAttacking = false; if (!isDead && !isHit) StartCoroutine(RestAfterAttack()); } protected IEnumerator RestAfterAttack() { isResting = true; yield return new WaitForSeconds(attackRestDuration); isResting = false; } public virtual void OnHitEnd() { isHit = false; if (agent && agent.isOnNavMesh) agent.isStopped = false; } protected virtual void Die() { if (isDead) return; isDead = true; OnMonsterKilled?.Invoke(expReward); Collider col = GetComponent(); if (col != null) col.enabled = false; if (agent && agent.isOnNavMesh) { agent.isStopped = true; agent.velocity = Vector3.zero; } animator.Play(Monster_Die, 0, 0f); if (deathSound) audioSource.PlayOneShot(deathSound); Invoke("ReturnToPool", 1.5f); } public bool IsDead => isDead; protected void ReturnToPool() => gameObject.SetActive(false); }