개인프로젝트 / AFO_Refactor
UE5 C++ 독(Poison) 시스템 설계 — 스택 누적 · 틱 데미지 · 델리게이트 연동
🎯 이 글에서 다루는 것
UE5 C++ 기반 프로젝트에서 독(Poison) 상태이상 시스템 전체를 설계·구현한 과정을 정리한다.
플레이어/몬스터 공통 스택 누적 → 활성화 → 틱 데미지 → 자연 감소 흐름과,
오니 장신구 장착 이벤트를 델리게이트로 연동해 독 공격을 자동 활성화하는 구조까지 다룬다.
📌 시스템 전체 흐름
↓
IT3Poisonable::ApplyPoisonStack() 호출 (독 공격 활성화 시)↓
CurrentPoisonStack 누적 → MaxPoisonStack 도달 시↓
ActivatePoison() — bIsPoisoned = true, 타이머 등록↓
매 1초마다
PoisonTick() — HP 차감↓
PoisonRemainingTime 소진 → DeactivatePoison()
캐릭터(
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();
}
}
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 | ✅ 있음 | ❌ 없음 |
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);
}
}
헤더에 주석("독 룬/장비가 켜는 용도")만 있고 실제 연동이 빠진 상태였다.
→ 오늘 작업(델리게이트 연동)으로 보강했다.
🔗 오니 장신구 델리게이트 연동 — 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("비활성화")
);
}
게임 저장/로드 시나리오에서 이미 오니 장신구를 장착한 채로 게임을 재시작할 수 있다.
이 경우 델리게이트는 이미 발동된 상태여서 다시 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 업데이트)
'언리얼엔진5 공부' 카테고리의 다른 글
| 2026-03-11 TIL (0) | 2026.03.11 |
|---|---|
| [TIL] UE5 나이아가라 잔상 시스템 및 AI 협동 분신 구현 (0) | 2026.03.08 |
| [TIL]UE 5 '신의 심판' 스킬 시스템 구현 (0) | 2026.02.20 |
| [TIL] UE 5 스킬 시스템 핵심 로직 - 스왑 및 장착 메커니즘 (0) | 2026.02.10 |
| [TIL] UE5 콜리젼 & 오버랩 초기화 순서에 따른 이슈 (0) | 2026.02.09 |