내일배움캠프_Unreal/TIL

내일배움캠프_Unreal/TIL - Day 150 (260706)

하임 2026. 7. 6. 23:54

언스턱(강제 리스폰) - 새 로직 대신 "정규 사망 흐름" 재사용


1. KillOwner - Health를 직접 0으로 세팅해 사망을 "단일 소스"로 구동

사망 파이프라인의 트리거는 결국 Health 0. 그래서 데미지 GE를 만들지 않고 어트리뷰트 베이스를 직접 0으로 세팅 → HandleHealthChanged가 나머지를 알아서 굴림.

void URetrieveHealthComponent::KillOwner()
{
    if (!AbilitySystemComponent || !AttributeSet) return;
    if (bDeathStarted) return;   // 이미 사망 처리 중이면 재진입 방지

    // GE 파이프라인 우회 — ResetHealth와 동일 기법. Health 0 → GA_Die → ... → Result.
    AbilitySystemComponent->SetNumericAttributeBase(UCombatAttributeSet::GetHealthAttribute(), 0.f);
}
  • 재진입 가드: bDeathStarted로 이미 죽는 중이면 no-op.
  • SetNumericAttributeBase vs GE: 환경적 강제 사망은 데미지 계산·감쇠·면역이 필요 없으니 베이스 직접 세팅이 더 정확. (실데미지가 아니라 "상태를 죽음으로 만든다".)

2. UI 가드 이중화 + 버튼 부재 안전 처리

"강제 리스폰" 버튼은 인게임에서만 의미가 있음. 메인 메뉴에서 설정을 열면 숨겨야 하고, 세션 상태가 바뀐 뒤 눌릴 가능성도 막아야 함.

// NativeConstruct — 인게임이 아니면 버튼 숨김. WBP에 버튼이 아직 없으면 FindWidget이 null → 안전 무시.
if (UButton* RespawnButton = FindWidget<UButton>(this, TEXT("Btn_Respawn")))
{
    const bool bInGame = GS && GS->GetSessionState() == ERetrieveSessionState::InGame;
    RespawnButton->SetVisibility(bInGame ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
}

// HandleRespawn — 클릭 시에도 세션 상태 재확인(가시성 가드가 뚫려도 방어)
if (!GS || GS->GetSessionState() != ERetrieveSessionState::InGame) return;
if (auto* PC = Cast<ARetrievePlayerController>(GetOwningPlayer())) PC->RequestUnstuck();
RequestClose();   // 요청 후 패널 닫아 게임 입력 복귀
  • 이중 가드: 표시(Visibility) + 실행(핸들러) 양쪽에서 인게임 확인.
  • 버튼 부재 내성: FindWidget null 허용 → C++ 선반영 후 WBP에 버튼을 나중에 추가해도 크래시 없음(배선 순서 독립).
  • 곁들여 RequestQuitGame(포즈 팝업 게임 종료, 로컬 QuitGame)도 추가.

3. (버그 수정) 낙하 데미지로 원소 게이지가 차던 문제

공격자 측 "적중" 이벤트(원소 게이지 충전 등 "남을 때렸다" 피드백)가, 낙사/환경 데미지(instigator == target인 self-damage)에도 발행되던 게 원인.

❌ Before — 무조건 공격자 적중 이벤트 발행

EventData.EventTag = AttackerEventTag;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(AttackerActor, AttackerEventTag, EventData);

✅ After — 자기 자신에게 입힌 데미지는 "적중" 아님 → 제외

if (TargetActor != AttackerActor)   // self-damage(낙하 등) 제외
{
    EventData.EventTag = AttackerEventTag;
    UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(AttackerActor, AttackerEventTag, EventData);
}

낙사 강제 리스폰을 붙이니 드러난 버그. "적중" 피드백은 남을 때렸을 때만 - instigator와 target이 같으면 전투 적중이 아니다.


4. (튜닝) 낙법 실패 착지 몽타주 배속

착지 경직이 길게 느껴져, 낙법 실패 몽타주에 재생 배속 파라미터를 추가(값만, 로직 동일).

// 헤더: EditDefaultsOnly, ClampMin=0.1
float LandingFailMontagePlayRate = 1.f;
// 재생: 배속 전달 (>1.0 = 짧게)
PlayAnimMontage(LandingFailMontage, LandingFailMontagePlayRate);

비교 정리

구분 ❌ 안 했다면 ✅ 이번 구현
사망 트리거 데미지 GE 조작 SetNumericAttributeBase(Health, 0) + 재진입 가드
리스폰 버튼 항상 노출/무가드 Visibility + 핸들러 재확인 + FindWidget null 내성
낙하 데미지 원소 게이지 충전됨(버그) instigator==target이면 적중 이벤트 제외
착지 경직 고정 길이 LandingFailMontagePlayRate로 배속 튜닝

핵심 요약

  • 사망 트리거는 Health를 직접 0으로(SetNumericAttributeBase) 세팅해 파이프라인을 단일 소스로 구동, bDeathStarted로 재진입 방지..
  • UI 버튼은 표시·실행 이중 가드 + FindWidget null 내성으로, C++를 먼저 넣고 WBP 배선을 나중에 해도 안전하게 한다.
  • "적중" 피드백은 instigator ≠ target일 때만 — self-damage(낙사 등)로 원소 게이지가 차는 버그를 이 한 줄로 막는다.