강제 리스폰과 낙하 정리 - 자기 피격이 '적중'으로 집계되던 버그
1. 설계 결정 — 텔레포트를 새로 짜지 말고 "죽음"을 재사용
플레이어를 마지막 체크포인트로 되돌리는 가장 쉬운 방법은 텔레포트지만, 즉시 텔레포트는 월드 파티션 스트리밍/로딩 커버를 안 거쳐 도착 지점이 로드되지 않은 채 스폰될 위험이 있음.
그런데 이 게임엔 이미 사망 → Result → Retry → 체크포인트 리스폰 흐름이 있고, 여기엔 로딩 커버·스트리밍 보장이 전부 들어 있음. → 언스턱을 정규 사망 처리로 라우팅해서 그 흐름을 통째로 재사용.
[UI Btn_Respawn]
└→ PC::RequestUnstuck → Server_RequestUnstuck (클라 → 서버 RPC)
└→ GameMode::HandleUnstuck (IsRequestorHost 권한 가드)
└→ HealthComponent::KillOwner() (Health → 0)
└→ 정규 사망 파이프라인:
GA_Die → Channel.Player.Died → HandlePlayerDied
→ Result 화면 → 자동 Retry → 로딩 커버 + 마지막 체크포인트 리스폰
교훈: "A와 결과가 같은 견고한 경로 B가 이미 있으면, A를 새로 만들지 말고 B로 진입시킨다." 실제 사망과 100% 동일 경로라 언스턱만의 엣지케이스가 아예 안 생긴다.
2. KillOwner — Health를 직접 0으로 세팅해 사망을 "단일 소스"로 구동
사망 파이프라인의 트리거는 결국 Health 0. 그래서 데미지 GE를 만들지 않고 어트리뷰트 베이스를 직접 0으로 세팅 → HandleHealthChanged가 나머지를 알아서 굴림.
void URetrieveHealthComponent::KillOwner()
{
if (!AbilitySystemComponent || !AttributeSet) return;
if (bDeathStarted) return; // 이미 사망 처리 중이면 재진입 방지
// GE 파이프라인 우회 — Health 0 → GA_Die → ... → Result.
AbilitySystemComponent->SetNumericAttributeBase(UCombatAttributeSet::GetHealthAttribute(), 0.f);
}
- 재진입 가드:
bDeathStarted로 이미 죽는 중이면 no-op. SetNumericAttributeBasevs GE: 환경적 강제 사망은 데미지 계산·감쇠·면역이 필요 없으니 베이스 직접 세팅이 더 정확. (실데미지가 아니라 "상태를 죽음으로 만든다".)
3. 권한 라우팅 — 클라 UI에서 시작해 서버에서 실행
리스폰은 서버 권위 동작이라, 클라 UI 입력을 RPC로 서버에 넘기고 서버에서만 실행.
// PlayerController (클라 진입점 → 서버 RPC)
void ARetrievePlayerController::RequestUnstuck() { Server_RequestUnstuck(); }
void ARetrievePlayerController::Server_RequestUnstuck_Implementation()
{
if (ARetrieveGameMode* GM = GetWorld()->GetAuthGameMode<ARetrieveGameMode>())
GM->HandleUnstuck(this);
}
// GameMode (서버, 호스트만)
void ARetrieveGameMode::HandleUnstuck(APlayerController* Requestor)
{
if (!IsRequestorHost(Requestor)) return; // 요청자 권한 확인
APawn* Pawn = Requestor ? Requestor->GetPawn() : nullptr;
if (auto* CombatPawn = Cast<ARetrieveAlsCombatCharacter>(Pawn))
CombatPawn->GetHealthComponent()->KillOwner();
}
GetAuthGameMode<T>()는 서버에만 유효(클라에선 null) → 자연스러운 서버 가드. 여기에IsRequestorHost로 한 겹 더.- 곁들여
RequestQuitGame(포즈 팝업 게임 종료, 로컬QuitGame)도 같은 자리에 추가해 "빠져나가는" 동작을 한 곳에 모음.
4. (버그 수정) 낙하 데미지가 원소 게이지를 채우던 문제
강제 리스폰을 검증하려면 일부러 절벽에서 떨어져 죽어봐야 했는데, 낙사를 반복하다 보니 죽을 때마다 원소 게이지가 조금씩 차는 게 보였다. 원인은 공격자 측 "적중" 이벤트(원소 게이지 충전 등 "남을 때렸다" 피드백)가, 낙사/환경 데미지(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이 같으면 전투 적중이 아니다.
5. (튜닝) 낙법 실패 착지 몽타주 배속
착지 경직이 길게 느껴져, 낙법 실패 몽타주에 재생 배속 파라미터를 추가(값만, 로직 동일).
// 헤더: EditDefaultsOnly, ClampMin=0.1
float LandingFailMontagePlayRate = 1.f; // >1.0 = 짧게(빠르게)
// 재생:
PlayAnimMontage(LandingFailMontage, LandingFailMontagePlayRate);
로직은 그대로 두고 파라미터 하나만 늘린 것이라, 애니 에셋을 건드리지 않고 착지 경직 길이를 튜닝할 수 있게 됨.
6. 올리기 전 죽은 에셋 정리
스태프 테스트용으로 만들어두고 안 쓰던 OLD_AM 몽타주 7개(Blink/Burst/HeavyAttack 테스트본)를 삭제하고, 버스트 조합 데이터(DT_SkillCombination)를 최신 값으로 갱신. 데이터 주도로 몽타주를 정리한 뒤 죽어 있던 에셋이라 이번에 같이 제거.
비교 정리
| 구분 | ❌ 안 했다면 | ✅ 이번 구현 |
|---|---|---|
| 언스턱 방식 | 즉시 텔레포트(스트리밍 미보장) | 정규 사망 흐름 라우팅(로딩 커버·체크포인트 재사용) |
| 사망 트리거 | 데미지 GE 조작 | SetNumericAttributeBase(Health, 0) + 재진입 가드 |
| 실행 권한 | 클라에서 직접 | UI→PC RPC→GM(IsRequestorHost)→HealthComp |
| 낙하 데미지 | 원소 게이지 충전됨(버그) | instigator==target이면 적중 이벤트 제외 |
| 착지 경직 | 고정 길이 | LandingFailMontagePlayRate로 배속 튜닝 |
핵심 요약
- 강제 리스폰은 새 텔레포트 로직 대신 이미 견고한 "사망→Result→Retry→체크포인트" 흐름에 라우팅 — 로딩 커버/스트리밍/엣지케이스를 공짜로 얻는다.
- 사망 트리거는 Health를 직접 0으로(
SetNumericAttributeBase) 세팅해 파이프라인을 단일 소스로 구동,bDeathStarted로 재진입 방지. - 서버 권위 동작은 UI(클라) → PC RPC → GM 권한 가드 → 실행으로 라우팅하고,
GetAuthGameMode<T>()null 특성이 자연스러운 서버 가드가 된다. - "적중" 피드백은 instigator ≠ target일 때만 — self-damage(낙사 등)로 원소 게이지가 차는 버그를 이 한 줄로 막는다.
'내일배움캠프_Unreal > TIL' 카테고리의 다른 글
| 내일배움캠프_Unreal/TIL - Day 153 (260710) (0) | 2026.07.10 |
|---|---|
| 내일배움캠프_Unreal/TIL - Day 153 (260709) (1) | 2026.07.09 |
| 내일배움캠프_Unreal/TIL - Day 151 (260707) (0) | 2026.07.08 |
| 내일배움캠프_Unreal/TIL - Day 150 (260706) (0) | 2026.07.06 |
| 내일배움캠프_Unreal/TIL - Day 149 (260703) (0) | 2026.07.03 |