몬스터 피격 · 사망 처리 설계

목차

1. 요구 사항

2. 설계 목표

3. 흐름도

4. 구현

        4.1. DAMAGED(피격) 상태 처리

       4.2. DIE(사망) 상태 처리

5. 개발 의도

1. 시스템 요구 사항

몬스터는 외부로부터 데미지를 입력받았을 때, 체력 감소와 상태 전이가 명확한 규칙에 따라 처리되어야 한다.

체력 감소는 단일 진입점에서 이루어져야 하며, 그 결과에 따라 실행 계층에서 DAMAGED 또는 DIE 상태로 전이되어야 한다.

피격은 단순 수치 감소가 아니라 즉각적인 시각적 피드백(애니메이션)으로 이어져야 하며, 체력이 0 이하가 되면 반드시 단일 기준(curHp ≤ 0)에 의해 사망 상태로 전환되어야 한다.

사망 시에는 이동 및 AI 실행이 완전히 차단되어야 하며, 사망 연출이 재생된 이후 드랍 생성 및 외부 시스템(퀘스트, 스폰 관리 등)에 사망 사실이 전달되어야 한다.

또한 몬스터는 퀘스트 조건 판단이나 재스폰 결정 같은 외부 도메인 로직을 직접 수행하지 않아야 하며, 단지 사망 사실을 통보하는 역할만 수행해야 한다.

실행 책임은 각 시스템이 독립적으로 가져야 한다.

2. 설계  목표

- 체력 감소와 실행 로직의 명확한 계층 분리

- 사망 판정 기준의 단일화(curHp ≤ 0)

- DAMAGED와 DIE 상태의 역할 구분

- 사망 이후 AI 완전 차단

- 드랍 및 외부 시스템 연동의 책임 분리

- 애니메이션 연출과 상태 전이의 동기화

3. 흐름도

[외부 데미지 입력]

       ↓

  GetDamage()

       ↓

  curHp 감소

       ↓

  (curHp > 0) → State = DAMAGED

  (curHp ≤ 0) → State = DIE

       ↓

Update()에서 상태 소비

       ↓

DAMAGED → 피격 애니메이션 실행 → 다음 상태 전이

DIE → 사망 애니메이션 → 이동 차단 → 드랍 생성 → 외부 시스템 통보 → Destroy

이 구조에서 중요한 점은 체력 감소와 실행 로직이 분리되어 있다는 것이다.

GetDamage는 데이터 계층이며, Update에서 상태를 소비하는 부분이 실행 계층이다.

즉, 체력 변화는 데이터를 수정하고, 그 결과로 상태가 전이되며, 실행은 상태를 해석하는 자식 클래스가 수행한다.

4. 구현

4.1. DAMAGED(피격) 상태 처리
protected virtual void UpdateDamaged()
{
    anime.SetTrigger("Damage");

    if (curHp <= 0)
    {
        state = State.DIE;
    }
    else
    {
        if (isRaidMonster) state = State.IDLE;
        else state = State.CHASE;
    }
}

UpdateDamaged는 DAMAGED상태일 때 호출된다.

플레이어가 공격하였을 때, 몬스터가 DAMAGED 상태가 되고, 이는 피격 연출을 실행하기 위한 중간 상태다.

체력 감소는 이미 베이스 클래스의 GetDamage에서 처리되었으며, 여기서는 그 결과를 소비하는 실행 계층이다.

anime.SetTrigger("Damage")는 Unity Animator의 Trigger 파라미터를 사용한 방식이다.

Trigger는 Bool과 달리 자동으로 false로 돌아가므로, 단발성 이벤트에 적합하다.

피격은 지속 상태가 아니라 순간 이벤트이기 때문에 Trigger를 선택하였다.

curHp <= 0이면 즉시 DIE 상태로 전환한다.

사망 판정을 단일 조건으로 고정함으로써, 중복 사망 처리나 드랍 중복 발생 같은 오류를 방지한다.

레이드 몬스터의 경우 IDLE로 복귀하는 구조를 둔 것은, 레이드 AI가 집단 로직에 의해 다시 타겟을 설정받도록 하기 위함이다.

일반 몬스터는 즉시 CHASE로 복귀해 전투 흐름을 유지한다.

4.2. DIE(사망) 상태 처리
protected virtual void Die()
{
    anime.SetTrigger("Die");
    agent.isStopped = true;
    isDie = true;

    UiManager_.instance.DropItem(dropItemPrefabs, 3);
    KillCount(4);
    MonsterKill();
    Destroy(gameObject, 1f);
}

DIE는 실행 종결 상태다.

anime.SetTrigger("Die")는 사망 애니메이션을 실행한다.

사망 역시 단발 이벤트이므로 Trigger가 적합하다.

agent.isStopped = true는 NavMeshAgent의 이동 업데이트를 완전히 차단한다.

이 처리가 없으면 내부 이동 계산이 계속 수행되어 사망 애니메이션 중에도 위치가 변할 수 있다.

isDie = true는 Update 루프 최상단의 조기 return 조건과 연결된다.

Unity의 Update는 상태와 무관하게 계속 호출되므로, 별도의 실행 차단 플래그를 두어 AI 루프를 완전히 중단한다.

이는 상태 값과 실행 차단을 분리한 설계다.

DropItem은 아이템 생성 시스템에 위임되고, KillCount는 퀘스트 시스템에 사망 사실을 전달한다.

MonsterKill은 스폰 관리 시스템에 사망 사실을 전달한다.

몬스터는 직접 퀘스트 조건을 판단하거나 재스폰 로직을 실행하지 않는다.

단지, 사망 이벤트를 전달할 뿐이다.

이 설계는 전투 시스템과 퀘스트/스폰 시스템 간의 결합도를 낮춘다.

Destroy는 Unity에서 오브젝트를 제거하는 API다.

즉시 제거하면 사망 애니메이션이 재생되기 전에 오브젝트가 사라질 수 있다.

1초 지연을 둔 이유는 사망 연출이 완료될 시간을 보장하기 위함이다.

Destroy는 프레임 종료 시점에 제거를 예약하는 방식이므로, 지연 시간 동안 오브젝트는 정상적으로 존재하며 애니메이션과 드랍 연출이 유지된다.

5. 개발 의도

피격과 사망 처리는 전투 루프의 종결 단계다.

이 구조에서 핵심은 데이터 계층과 실행 계층의 분리다.

체력 감소는 베이스 클래스에서 처리하고, 실행 로직은 상태 소비 방식으로 동작한다.

사망 판정은 단일 기준으로 통일하여 예외 상황을 제거했고, 사망 이후에는 이동과 AI 실행을 명확히 차단했다.

드랍과 외부 시스템 연동은 이벤트 전달 수준으로 제한하여 도메인 책임을 분리하였다.

결과적으로 이 구조는 전투 시작(추적·공격)부터 전투 종료(사망·정리)까지 상태 기반 실행 아키텍처 위에서 일관되게 동작하는 종결 계층을 구성한다.