193 lines
12 KiB
C#
193 lines
12 KiB
C#
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
|
|
using UnityEngine.AI; // 길찾기 기능을 불러올거에요 -> UnityEngine.AI를
|
|
using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를
|
|
using System; // 시스템 기능을 사용할거에요 -> System을
|
|
|
|
public abstract class MonsterClass : MonoBehaviour, IDamageable // 추상 클래스를 선언할거에요 -> MonsterClass를
|
|
{
|
|
[Header("--- 최적화 ---")] // 인스펙터 제목을 달거에요 -> 최적화 설정을
|
|
protected Renderer mobRenderer; // 변수를 선언할거에요 -> 렌더러를
|
|
protected Transform playerTransform; // 변수를 선언할거에요 -> 플레이어 위치를
|
|
[SerializeField] protected float optimizationDistance = 40f; // 변수를 선언할거에요 -> 최적화 거리를
|
|
|
|
[Header("스탯")] // 인스펙터 제목을 달거에요 -> 스탯 설정을
|
|
[SerializeField] protected float maxHP = 100f; // 변수를 선언할거에요 -> 최대 체력을
|
|
[SerializeField] protected float attackDamage = 10f; // 변수를 선언할거에요 -> 공격력을
|
|
[SerializeField] protected int expReward = 10; // 변수를 선언할거에요 -> 경험치 보상을
|
|
[SerializeField] protected float moveSpeed = 3.5f; // 변수를 선언할거에요 -> 이동 속도를
|
|
|
|
protected float currentHP; // 변수를 선언할거에요 -> 현재 체력을
|
|
public event Action<float, float> OnHealthChanged; // 이벤트를 선언할거에요 -> 체력 변경 알림을
|
|
|
|
[Header("전투")] // 인스펙터 제목을 달거에요 -> 전투 설정을
|
|
[SerializeField] protected MonsterWeapon myWeapon; // 변수를 선언할거에요 -> 무기를
|
|
|
|
[Header("애니메이션")] // 인스펙터 제목을 달거에요 -> 애니메이션을
|
|
[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 StatusEffectProcessor statusProcessor; // 변수를 선언할거에요 -> 상태이상 처리기를
|
|
|
|
protected bool isHit, isDead, isAttacking; // 상태 변수들을 선언할거에요
|
|
public bool IsAggroed { get; protected set; } // 프로퍼티를 선언할거에요 -> 어그로 여부를
|
|
|
|
[Header("AI")] // 인스펙터 제목을 달거에요 -> AI 설정을
|
|
[SerializeField] protected float attackRestDuration = 1.5f; // 변수를 선언할거에요 -> 공격 후 휴식 시간을
|
|
protected bool isResting; // 변수를 선언할거에요 -> 휴식 여부를
|
|
|
|
public static 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() // 함수를 실행할거에요 -> 초기화 Awake를
|
|
{
|
|
mobRenderer = GetComponentInChildren<Renderer>(); // 가져올거에요 -> 렌더러를
|
|
animator = GetComponent<Animator>(); // 가져올거에요 -> 애니메이터를
|
|
agent = GetComponent<NavMeshAgent>(); // 가져올거에요 -> 에이전트를
|
|
audioSource = GetComponent<AudioSource>(); // 가져올거에요 -> 오디오 소스를
|
|
if (agent != null) agent.speed = moveSpeed; // 설정할거에요 -> 이동 속도를
|
|
statusProcessor = new StatusEffectProcessor(this, this, agent, animator); // 생성할거에요 -> 상태이상 처리기를
|
|
}
|
|
|
|
protected virtual void OnEnable() // 함수를 실행할거에요 -> 활성화 시
|
|
{
|
|
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 Update() => OnManagedUpdate(); // 실행할거에요 -> 관리형 업데이트를
|
|
|
|
public void ResetStats() // 함수를 선언할거에요 -> 스탯 초기화를
|
|
{
|
|
isDead = false; IsAggroed = false; currentHP = maxHP; // 초기화할거에요 -> 상태와 체력을
|
|
OnHealthChanged?.Invoke(currentHP, maxHP); // 알릴거에요 -> 체력 변경을
|
|
if (GetComponent<Collider>() != null) GetComponent<Collider>().enabled = true; // 켤거에요 -> 콜라이더를
|
|
if (agent != null) agent.speed = moveSpeed; // 복구할거에요 -> 속도를
|
|
OnResetStats(); // 실행할거에요 -> 추가 리셋을
|
|
}
|
|
|
|
// ⭐ [복구] 스포너에서 호출하는 재활성화 함수
|
|
public virtual void Reactivate() // 함수를 선언할거에요 -> 재활성화를
|
|
{
|
|
if (agent != null && agent.isOnNavMesh) // 조건이 맞으면 실행할거에요 -> 에이전트가 정상이면
|
|
{
|
|
agent.isStopped = false; // 명령을 내릴거에요 -> 이동 재개
|
|
if (IsAggroed && playerTransform != null) agent.SetDestination(playerTransform.position); // 이동할거에요 -> 플레이어에게
|
|
}
|
|
if (mobRenderer != null) mobRenderer.enabled = true; // 켤거에요 -> 렌더러를
|
|
}
|
|
|
|
protected virtual void Init() { }
|
|
protected abstract void ExecuteAILogic();
|
|
protected virtual void OnResetStats() { }
|
|
|
|
public virtual void OnManagedUpdate() // 함수를 선언할거에요 -> 최적화 업데이트를
|
|
{
|
|
if (isDead || playerTransform == null || !gameObject.activeInHierarchy) return; // 중단할거에요 -> 작동 불가면
|
|
|
|
float dist = Vector3.Distance(transform.position, playerTransform.position); // 계산할거에요 -> 거리를
|
|
if (dist > optimizationDistance && !IsAggroed) // 조건이 맞으면 실행할거에요 -> 최적화 대상이면
|
|
{
|
|
StopMovement(); // 멈출거에요 -> 이동을
|
|
if (mobRenderer != null) mobRenderer.enabled = false; // 끌거에요 -> 렌더러를
|
|
return; // 중단할거에요 -> 로직을
|
|
}
|
|
|
|
if (mobRenderer != null) mobRenderer.enabled = true; // 켤거에요 -> 렌더러를
|
|
if (agent != null && agent.isOnNavMesh && agent.isStopped) agent.isStopped = false; // 켤거에요 -> 이동을
|
|
ExecuteAILogic(); // 실행할거에요 -> AI 로직을
|
|
}
|
|
|
|
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) // 함수를 선언할거에요 -> 피격 처리를
|
|
{
|
|
if (isDead) return; // 중단할거에요 -> 죽었으면
|
|
IsAggroed = true; currentHP -= amount; // 적용할거에요 -> 어그로와 데미지를
|
|
OnHealthChanged?.Invoke(currentHP, maxHP); // 알릴거에요 -> UI에
|
|
if (currentHP <= 0) Die(); else if (!isHit) StartHit(); // 분기할거에요 -> 사망 또는 피격으로
|
|
}
|
|
|
|
protected virtual void StartHit() // 함수를 선언할거에요 -> 피격 시작을
|
|
{
|
|
isHit = true; isAttacking = false; isResting = false; StopAllCoroutines(); // 초기화할거에요 -> 상태를
|
|
OnStartHit(); // 실행할거에요 -> 추가 처리를
|
|
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); // 재생할거에요 -> 소리를
|
|
}
|
|
|
|
protected virtual void OnStartHit() { if (myWeapon != null) myWeapon.DisableHitBox(); } // 가상 함수를 선언할거에요 -> 무기 끄기
|
|
|
|
public virtual void OnHitEnd() // 함수를 선언할거에요 -> 피격 종료를
|
|
{
|
|
isHit = false; // 해제할거에요 -> 피격 상태를
|
|
if (agent && agent.isOnNavMesh) agent.isStopped = false; // 재개할거에요 -> 이동을
|
|
}
|
|
|
|
protected virtual void Die() // 함수를 선언할거에요 -> 사망 처리를
|
|
{
|
|
isDead = true; IsAggroed = false; OnDie(); // 설정할거에요 -> 사망 상태를
|
|
OnMonsterKilled?.Invoke(expReward); // 알릴거에요 -> 경험치 지급을
|
|
if (GetComponent<Collider>() != null) GetComponent<Collider>().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(nameof(ReturnToPool), 1.5f); // 예약할거에요 -> 풀 반환을
|
|
}
|
|
|
|
protected virtual void OnDie() { if (myWeapon != null) myWeapon.DisableHitBox(); } // 가상 함수를 선언할거에요 -> 사망 시 처리를
|
|
protected void ReturnToPool() => gameObject.SetActive(false); // 함수를 선언할거에요 -> 비활성화를
|
|
|
|
public virtual void OnAttackStart() // 함수를 선언할거에요 -> 공격 시작을
|
|
{
|
|
isAttacking = true; isResting = false; // 설정할거에요 -> 공격 중으로
|
|
if (myWeapon != null) myWeapon.EnableHitBox(); // 켤거에요 -> 무기 판정을
|
|
}
|
|
|
|
public virtual void OnAttackEnd() // 함수를 선언할거에요 -> 공격 종료를
|
|
{
|
|
if (myWeapon != null) myWeapon.DisableHitBox(); // 끌거에요 -> 무기 판정을
|
|
isAttacking = false; // 해제할거에요 -> 공격 상태를
|
|
if (!isDead && !isHit) StartCoroutine(RestAfterAttack()); // 시작할거에요 -> 휴식을
|
|
}
|
|
|
|
protected virtual IEnumerator RestAfterAttack() // 코루틴을 선언할거에요 -> 휴식 로직을
|
|
{
|
|
isResting = true; // 설정할거에요 -> 휴식 중으로
|
|
yield return new WaitForSeconds(attackRestDuration); // 기다릴거에요 -> 휴식 시간만큼
|
|
isResting = false; // 해제할거에요 -> 휴식 상태를
|
|
}
|
|
|
|
public void ApplyStatusEffect(StatusEffectType type, float dmg, float dur) // 함수를 선언할거에요 -> 상태이상 적용을
|
|
{
|
|
if (isDead) return; // 중단할거에요 -> 죽었으면
|
|
switch (type)
|
|
{ // 분기할거에요 -> 타입에 따라
|
|
case StatusEffectType.Burn: statusProcessor.ApplyBurn(dmg, dur); break; // 적용할거에요 -> 화상을
|
|
case StatusEffectType.Slow: statusProcessor.ApplySlow(dmg, dur); break; // 적용할거에요 -> 슬로우를
|
|
case StatusEffectType.Poison: statusProcessor.ApplyBurn(dmg, dur); break; // 적용할거에요 -> 독을
|
|
case StatusEffectType.Shock: statusProcessor.ApplyShock(dmg, dur); break; // 적용할거에요 -> 충격을
|
|
}
|
|
}
|
|
} |