Projext/Assets/Scripts/Enemy/AI/MonsterClass.cs
2026-02-02 17:30:23 +09:00

149 lines
5.4 KiB
C#

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<float, float> 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<int> 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<Renderer>();
animator = GetComponent<Animator>();
agent = GetComponent<NavMeshAgent>();
audioSource = GetComponent<AudioSource>();
}
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<Collider>();
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);
}