내일배움캠프_Unreal/TIL

내일배움캠프_Unreal/TIL - Day 145 (260629)

하임 2026. 6. 29. 23:46

레거시 경로 제거 & 점프어택 임팩트 타이밍 분리


1. 코드 안전교체 - 검증 후 레거시를 지운다

6/26엔 순수 가산 + opt-in(default 꺼짐) 으로 신규 Bounds 경로를 레거시 소켓 경로 옆에 깔았음. 무회귀가 목적이었음. 데이터로 켜서 검증이 끝났으니, 이제 2단계 = 레거시 경로 삭제.

❌ Before — 두 경로 공존 (폴백 분기 + 죽은 코드)

if (HitParts.IsEmpty())
{
    BuildTracePoints(CurrentPoints);        // 레거시 소켓 보간
    if (CurrentPoints.IsEmpty()) return;
}
// ... 파트별로 bUseBoundsTrace ? 바운드 : 소켓 폴백 분기

✅ After — Bounds 단일 경로

if (HitParts.IsEmpty())
{
    return;                                 // opt-in 안 했으면 그냥 끝
}
// 파트별 분기는 '형상'만: 검=SweptLongAxis / 방패=SingleSphere
bBuilt = (Part.BoundsTraceShape == ERetrieveBoundsTraceShape::SingleSphere)
    ? URetrieveWeaponTraceLibrary::BuildBoundsSphere(...)
    : URetrieveWeaponTraceLibrary::BuildBoundsTrace(...);
  • BuildTracePoints() 함수 통째로 삭제, 단일 메시 SweepSegment/PreviousTracePoints/bHasValidPreviousTracePoints 경로 전부 제거.
  • 파트 분기에서 bUseBoundsTrace(소켓이냐 바운드냐) 갈래도 제거 → 이제 Bounds가 유일 경로, 분기는 형상(검/방패)뿐.
  • 순효과: GA_Attack.cpp −106줄. 분기·죽은 필드·이중 상태가 한꺼번에 사라짐.

교훈: opt-in 가산은 마이그레이션의 절반일 뿐임. 검증이 끝나면 레거시를 지워야 분기·죽은 상태가 안 쌓인다. "안전하게 도입"과 "과감하게 제거"는 한 세트.


2. 점프어택 ① — AoE 중심을 소켓 → 검 바운드로 정렬

착지 AoE 중심이 Weapon_R 소켓 위치였음 → #6에서 평타가 검 바운드를 쓰는데 점프슬램만 소켓이라 시각/판정이 어긋남. 평타와 동일하게 히트볼륨 메시의 바운드 중심으로 통일.

TArray<FRetrieveEquippedWeaponMesh> HitParts;
CachedWeaponComponent->GetHitVolumeMeshes(HitParts);
if (HitParts.Num() > 0 && IsValid(HitParts[0].Mesh))
{
    // 평타와 같은 지오메트리: 컴포넌트 트랜스폼으로 월드 바운드 중심
    Center = HitParts[0].Mesh->CalcBounds(HitParts[0].Mesh->GetComponentTransform()).Origin;
}
else { /* 소켓 폴백 (히트볼륨 opt-in 안 한 무기) */ }

6/26 TIL이 예고한 "#3 점프슬램이 검 바운드를 그대로 가져다 씀"이 실제로 실현된 지점. 판정 지오메트리를 한 소스로 모으니 스킬 간 일관성이 공짜로 따라옴.


3. 점프어택 ② — 임팩트 타이밍을 물리 착지에서 분리 + 멱등 가드

문제: AoE가 "물리 착지(HandleLanded)" 한 곳에만 묶여 있었음

물리 착지 = 캐릭터 캡슐이 바닥에 닿는 순간. 하지만 검이 바닥을 내려찍는 시각적 임팩트 프레임과는 다름 → AoE가 늦거나 어긋나 보임.

해결: 애님 노티파이 게임플레이 이벤트를 1순위 트리거로

몽타주의 임팩트 프레임에 AnimNotifyState_AttackImpactGameplayEvent.Attack.Impact를 쏘고, GA가 그걸 듣는다.

ImpactEventTask = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
    this, RetrieveGameplayTags::GameplayEvent_Attack_Impact, nullptr, false, true);
ImpactEventTask->EventReceived.AddDynamic(this, &ThisClass::HandleImpactEvent);
ImpactEventTask->ReadyForActivation();

void UGA_JumpAttack::HandleImpactEvent(FGameplayEventData) { ApplyLandingAoe(); }

문제: 트리거가 여러 개가 되니 AoE가 중복으로 터질 수 있음

이제 ApplyLandingAoe를 부르는 곳이 3군데 — ① 임팩트 노티파이(원하는 타이밍) ② 물리 착지 HandleLanded(폴백) ③ HandleMontageCompleted(노티파이·착지 둘 다 놓쳤을 때 최후 보루).

bool 멱등 가드로 "정확히 한 번" 보장. 먼저 부른 놈이 이기고 나머지는 no-op.

void UGA_JumpAttack::ApplyLandingAoe()
{
    if (bLandingAoeApplied) return;   // ← fire-once 가드
    bLandingAoeApplied = true;
    // ... AoE 적용
}
// EndAbility에서 bLandingAoeApplied = false 로 리셋

핵심: 게임플레이 효과 타이밍을 물리 이벤트가 아니라 애님 노티파이에 묶으면 시각적으로 정확해짐. 단, 여러 트리거가 한 효과로 수렴하면 멱등 가드는 선택이 아니라 필수(중복 데미지 = 버그). 가드만 있으면 호출 순서/경합은 신경 안 써도 됨.


비교 정리

구분 ❌ Before ✅ After
평타 트레이스 경로 Bounds + 소켓 폴백 공존 Bounds 단일 (소켓 경로·BuildTracePoints 삭제)
파트 분기 소켓/바운드 + 형상 형상(검/방패)만
점프 AoE 중심 Weapon_R 소켓 위치 히트볼륨 메시 바운드 중심 (평타와 통일)
점프 AoE 타이밍 물리 착지 1곳 애님 임팩트 이벤트 1순위 + 착지/몽타주완료 폴백
중복 방지 (트리거 1개라 불필요) bLandingAoeApplied 멱등 가드 (3트리거 fire-once)

핵심 요약

  • opt-in 가산은 마이그레이션의 절반. 검증이 끝나면 레거시 경로를 지워야 분기·죽은 상태가 안 쌓인다. ("안전 도입" + "과감 제거" 한 세트)
  • 판정 지오메트리를 한 소스(검 바운드) 로 모으면 평타·점프슬램 등 스킬 간 일관성이 공짜로 따라온다.
  • 게임플레이 효과 타이밍은 물리 이벤트보다 애님 노티파이 게임플레이 이벤트에 묶어야 시각적으로 정확하다 (물리 착지는 폴백).
  • 한 효과로 수렴하는 트리거가 둘 이상이면 bool 멱등 가드로 fire-once를 강제한다 — 그러면 호출 순서·경합을 신경 쓸 필요가 없다.