언리얼엔진5 공부

UE5 C++ 독(Poison) 시스템 설계 — 스택 누적 · 틱 데미지 · 델리게이트 연동

Client Side 2026. 6. 12. 17:32
UE5 C++ 독(Poison) 시스템 설계 — 스택 누적 · 틱 데미지 · 델리게이트 연동

개인프로젝트 / AFO_Refactor

UE5 C++ 독(Poison) 시스템 설계 — 스택 누적 · 틱 데미지 · 델리게이트 연동

UE5 C++ StatusEffect Poison Delegate GameplaySystem

🎯 이 글에서 다루는 것
UE5 C++ 기반 프로젝트에서 독(Poison) 상태이상 시스템 전체를 설계·구현한 과정을 정리한다.
플레이어/몬스터 공통 스택 누적 → 활성화 → 틱 데미지 → 자연 감소 흐름과,
오니 장신구 장착 이벤트를 델리게이트로 연동해 독 공격을 자동 활성화하는 구조까지 다룬다.


📌 시스템 전체 흐름

적 공격 적중 (기본 공격)

IT3Poisonable::ApplyPoisonStack() 호출 (독 공격 활성화 시)

CurrentPoisonStack 누적 → MaxPoisonStack 도달 시

ActivatePoison() — bIsPoisoned = true, 타이머 등록

매 1초마다 PoisonTick() — HP 차감

PoisonRemainingTime 소진 → DeactivatePoison()
독을 주는 쪽은 UT3CombatComponent, 독을 받는 쪽은 IT3Poisonable 인터페이스를 통해 분리되어 있다.
캐릭터(AT3CharacterBase)와 몬스터(AT3MonsterBase)가 각각 이 인터페이스를 구현한다.

🧪 플레이어 독 시스템 (AT3CharacterBase)

구현 위치: T3CharacterBase.cpp 1106~1273행 #pragma region Poison

① 스택 누적 — ApplyPoisonStack_Implementation

적 공격에 맞을 때마다 CurrentPoisonStack을 증가시킨다. 사망 중(bIsDead)이거나 이미 독 상태(bIsPoisoned)라면 무시한다.

void AT3CharacterBase::ApplyPoisonStack_Implementation(int32 Stacks)
{
    if (bIsDead || bIsPoisoned) return;

    CurrentPoisonStack += Stacks;

    if (CurrentPoisonStack >= MaxPoisonStack) // 기본값 100
    {
        ActivatePoison();
        CurrentPoisonStack = 0;
    }
}
⚠️ 이미 독 상태라면 스택이 쌓이지 않는다.
독이 풀린 뒤에야 다시 스택이 누적되는 구조다. 지속 중복 적용을 막기 위한 의도적인 설계.

② 독 활성화 — ActivatePoison

void AT3CharacterBase::ActivatePoison()
{
    bIsPoisoned = true;
    PoisonRemainingTime = PoisonDuration; // 기본 30초

    OnPoisonActivated.Broadcast(true);  // 델리게이트 브로드캐스트 (UI 등 연동)

    // 나이아가라 아우라 + 사운드 부착
    AttachPoisonVFX();

    // BP 이벤트 호출 (연출용)
    BP_OnPoisonStarted();

    // 1초 간격으로 PoisonTick 타이머 등록
    GetWorldTimerManager().SetTimer(
        PoisonTickHandle,
        this,
        &AT3CharacterBase::PoisonTick,
        PoisonTickInterval, // 1.0f
        true
    );
}

③ 틱 데미지 — PoisonTick

void AT3CharacterBase::PoisonTick()
{
    float PoisonDamage = MaxHP * PoisonDamagePercent; // MaxHP의 2%

    // TakeDamage 파이프라인 미경유 — 직접 HP 차감
    SetCurrentHP(CurrentHP - PoisonDamage);

    if (CurrentHP <= 0.f)
    {
        HandleDeath();
        return;
    }

    PoisonRemainingTime -= PoisonTickInterval;
    if (PoisonRemainingTime <= 0.f)
    {
        DeactivatePoison();
    }
}
⚠️ 플레이어의 PoisonTick은 TakeDamage 파이프라인을 거치지 않는다.
SetCurrentHP()로 직접 차감한다. 방어력, 피격 반응, 피격 이펙트 등이 발동되지 않는다.
"독은 조용히 갉아먹는다"는 게임 디자인 의도에 따른 구분.

④ 스택 자연 감소 — PoisonStackDecayTick

독이 활성화되지 않은 상태에서, 시간이 지나면 스택이 자연 감소한다.

void AT3CharacterBase::PoisonStackDecayTick()
{
    if (bIsPoisoned) return;

    CurrentPoisonStack = FMath::Max(0, CurrentPoisonStack - PoisonStackDecayRate); // 초당 2씩 감소
}

👾 몬스터 독 시스템 (AT3MonsterBase)

구현 위치: T3MonsterBase.cpp 246~344행

플레이어와의 차이점

항목 플레이어 (AT3CharacterBase) 몬스터 (AT3MonsterBase)
MaxPoisonStack 100 10 낮음
PoisonDamagePercent MaxHP의 2% MaxHP의 1% 낮음
PoisonDuration 30초 20초 짧음
PoisonTick 데미지 처리 SetCurrentHP() 직접 차감 HealthComponent→HandleTakeDamage() 정식 파이프라인
OnPoisonActivated 델리게이트 ✅ 있음 ❌ 없음
PoisonStackDecayTick ✅ 있음 ❌ 없음
몬스터의 PoisonTick은 HealthComponent->HandleTakeDamage()를 통해 정식 TakeDamage 파이프라인을 거친다.
덕분에 몬스터에게는 독 데미지에 의한 피격 반응, 킬 판정, 스탯 집계 등이 정상 동작한다.
// 몬스터 PoisonTick — 정식 파이프라인 경유
void AT3MonsterBase::PoisonTick()
{
    float PoisonDamage = MaxHP * PoisonDamagePercent;

    FT3DamageEvent DmgEvent;
    DmgEvent.DamageType = ET3DamageType::Poison;

    HealthComponent->HandleTakeDamage(PoisonDamage, DmgEvent, nullptr, nullptr);

    PoisonRemainingTime -= PoisonTickInterval;
    if (PoisonRemainingTime <= 0.f)
    {
        DeactivatePoison();
    }
}

⚔️ 독 공격 적용 (UT3CombatComponent)

구현 위치: UT3CombatComponent.cpp 858~864행

기본 공격 히트 처리 시, 공격자에게 독 공격이 활성화되어 있고 피격 대상이 IT3Poisonable을 구현한 경우에만 ApplyPoisonStack()을 호출한다.

// 기본 공격 적중 처리 내부
if (OwnerChar->IsPoisonAttackEnabled())
{
    if (IT3Poisonable* PoisonTarget = Cast<IT3Poisonable>(HitActor))
    {
        PoisonTarget->ApplyPoisonStack(PoisonStacksPerHit);
    }
}
⚠️ 초기 구현 당시 bPoisonAttackEnabled를 켜주는 코드가 없었다.
헤더에 주석("독 룬/장비가 켜는 용도")만 있고 실제 연동이 빠진 상태였다.
→ 오늘 작업(델리게이트 연동)으로 보강했다.

🔗 오니 장신구 델리게이트 연동 — OnOniAccessoryEquipped

가장 핵심이 되는 보강 작업이다. bPoisonAttackEnabled를 아이템 장착 이벤트와 자동으로 동기화하도록 델리게이트를 연결했다.

설계 목표

아이템팀이 SetOniAccessoryEquipped(true/false)를 호출하면 → OnOniAccessoryEquipped.Broadcast() → 캐릭터의 독 공격이 자동으로 켜지고/꺼진다.
BeginPlay 시점에 기존 장착 상태도 그대로 반영된다.

헤더 선언 (T3CharacterBase.h)

// 오니 장신구 장착/해제 델리게이트(EquipComp->OnOniAccessoryEquipped) 바인딩용
// 장착 시 독 공격 활성화, 해제 시 비활성화
UFUNCTION()
void OnOniAccessoryEquippedChanged(bool bEquipped);

BeginPlay 바인딩 (T3CharacterBase.cpp)

// EquipComp 유효성 체크 블록 내
if (EquipComp)
{
    EquipComp->OnOniAccessoryEquipped.AddDynamic(
        this,
        &AT3CharacterBase::OnOniAccessoryEquippedChanged
    );

    // BeginPlay 시점 기존 장착 상태 즉시 동기화
    OnOniAccessoryEquippedChanged(EquipComp->GetOniAccessoryEquipped());
}

핸들러 구현 (독 시스템 구역 최상단)

void AT3CharacterBase::OnOniAccessoryEquippedChanged(bool bEquipped)
{
    SetPoisonAttackEnabled(bEquipped);

    UE_LOG(LogTemp, Log,
        TEXT("[독] %s 오니 장신구 %s → 독 공격 %s"),
        *GetName(),
        bEquipped ? TEXT("장착") : TEXT("해제"),
        bEquipped ? TEXT("활성화") : TEXT("비활성화")
    );
}
왜 BeginPlay에서 즉시 동기화하는가?
게임 저장/로드 시나리오에서 이미 오니 장신구를 장착한 채로 게임을 재시작할 수 있다.
이 경우 델리게이트는 이미 발동된 상태여서 다시 Broadcast되지 않는다.
GetOniAccessoryEquipped()로 현재 상태를 직접 읽어 한 번 강제 동기화하는 이유다.

📊 전체 파라미터 정리

파라미터 플레이어 몬스터 설명
MaxPoisonStack 100 10 독 활성화에 필요한 스택 수
PoisonDamagePercent 2% 1% 매 틱당 MaxHP 대비 피해량
PoisonDuration 30초 20초 독 지속 시간
PoisonTickInterval 1초 1초 틱 데미지 간격
PoisonStackDecayRate 초당 2 비독 상태 시 초당 스택 감소량
PoisonStacksPerHit 공격마다 적용 (CombatComp 설정값) 타격당 부여 스택 수

💡 배운 것 / 설계 포인트

  • 플레이어와 몬스터 모두 IT3Poisonable 인터페이스로 독 대상을 추상화했다. CombatComponent는 누가 맞는지 알 필요 없다.
  • 플레이어 PoisonTick은 SetCurrentHP() 직접 차감, 몬스터는 HealthComponent->HandleTakeDamage() 경유 — 각 클래스의 HP 관리 구조에 맞게 분기했다.
  • 델리게이트 바인딩 후 BeginPlay 시점에 즉시 동기화하는 패턴은 저장/로드 시나리오에서 중요하다.
  • 독 공격 ON/OFF는 bPoisonAttackEnabled 단일 변수로 관리되며, 아이템 장착 이벤트와 자동 연동된다. 아이템팀과의 결합도를 최소화할 수 있다.
  • 스택 자연 감소(PoisonStackDecayTick)로 플레이어가 안전지대에 있으면 서서히 독 게이지가 풀리는 게임플레이 리듬을 만들 수 있다.

다음 포스팅: 독 상태이상 UI 연동 (OnPoisonActivated 델리게이트 → WBP 업데이트)