프로젝트 매버릭 개발일지 21일차

June 25, 2026 (5d ago)

이번 브랜치에서 한 일

처음엔 피격 리액션을 기존 ActionIndex -> ActionStat -> Chooser 흐름에 얹으려고 했는데, 팀에서 합의한 구조는 그게 아니었다. 이제 ActionComponent는 액션을 고르는 컴포넌트가 아니라 실행기다. HitReactionComponentCombatComponent가 먼저 Chooser를 평가해서 최종 DataTable RowHandle과 시작 섹션을 얻고, ActionComponent는 그 row를 읽어 몽타주를 재생하고 이벤트만 뿌리는 구조로 바꿨다.

정리하면:

  • HitResolver가 대미지와 피격 타입을 계산
  • CharacterBase.OnHitResolved()가 자기 피격인지 확인하고 OnDamaged 브로드캐스트
  • StatComponent는 HP를 깎고, HitReactionComponent는 피격 리액션 row를 선택
  • ActionComponent는 선택된 row의 몽타주를 재생
  • 몽타주 안의 Notify/NotifyState가 착지 감지, 리커버리 window, 기본 Getup 전환 같은 후처리를 담당

HitReaction 테이블 구조

기존에는 DT_HR_P1_SH_F, DT_HR_P1_KD_B처럼 상황별 테이블이 쪼개져 있었는데, 작업하다보니 이게 너무 관리하기 어려웠다. 결국 DT_HR_P1 하나로 합치고, Chooser가 최종 FDataTableRowHandle을 반환하게 바꿨다.

RowName은 대충 이런 식이다.

  • HR_P1_SH_F_01
  • HR_P1_KD_B_01
  • HR_P1_Getup_F_01
  • HR_P1_EscapeDodge_B_L_01

앞쪽은 도메인과 캐릭터, 중간은 리액션/회복 타입, 뒤쪽은 방향과 인덱스다. 처음엔 ActionId, ActionKey, RowName 같은 값들이 섞여있어서 내가 봐도 헷갈렸는데, 지금은 실행 키를 RowName 중심으로 정리했다. 테이블 row가 이미 유니크한 키를 갖고 있는데 또 다른 id를 얹는 건 프로토타입 단계에선 오히려 방해가 됐다.

RecoveryEscapeWindow

이번에 제일 오래 걸린 부분.

처음엔 RecoveryEscapeWindow가 닫히면 바로 Getup을 재생하게 했는데, 그건 틀린 접근이었다. 이 window는 "기본 Getup 시작 시점"이 아니라 "저장된 입력으로 현재 리액션을 끊을 수 있는 구간"이다. 그래서 기본 Getup은 별도의 MV HitReaction Default Recovery Notify가 담당하게 했다.

현재 흐름은 이렇다.

  • SmallHit / LargeHit
    • 넘어지는 리액션이 아니기 때문에 Getup이 없다.
    • Recovery window 안에서 이동이나 Dodge 입력이 있으면 현재 피격 몽타주를 cancel.
  • KnockDown / Airborne
    • 본 리액션은 Intro, Land, Lying 같은 상태 표현까지만 담당.
    • window 안에 저장된 이동/Dodge 입력이 있으면 EscapeDodge recovery 액션으로 전환.
    • 입력이 없으면 DefaultRecovery Notify에서 별도 Getup 액션으로 전환.

특히 Airborne은 Fall 루프 구간이 있어서 MV Airborne Land Detector NotifyState를 추가했다. 캐릭터가 착지하면 Land 섹션으로 점프하고, 그 뒤 Lying을 거쳐 Getup/EscapeDodge로 넘어간다.

삽질했던 문제들

1. Lying에서 Getup으로 넘어갈 때 튐

에셋상으로는 Lying 마지막 프레임과 Getup 첫 프레임이 맞는데, 코드상으로 기존 몽타주를 먼저 Montage_Stop하고 새 몽타주를 재생하고 있었다. 그 사이에 기본 포즈가 한 프레임 끼는 느낌이 나서 튀었다.

그래서 TryTransitionAction... 경로에서는 기존 몽타주를 선행 Stop하지 않고, 액션 상태만 interrupt 처리한 뒤 새 몽타주 재생이 이전 몽타주를 대체하게 바꿨다. 취소와 전환을 같은 처리로 보면 안된다는 걸 배웠다.

2. EscapeDodge 후딜 캔슬이 안됨

EscapeDodge로 전환한 직후 HitReactionComponent가 active HR 상태를 지워버리고 있었다. 그래서 EscapeDodge 몽타주 안에 RecoveryEscapeWindow를 넣어도 HR 쪽에서 더 이상 추적하지 못했다.

해결은 단순했다. Getup / EscapeDodge recovery 액션도 active HR recovery row로 계속 추적한다. 대신 Dodge 입력은 HitReactionComponent가 먹지 않고 DodgeComponent가 받아서 일반 Dodge로 전환하게 했다.

3. 누운 방향 기준으로 EscapeDodge가 나감

넘어진 뒤 S를 눌렀을 때 컨트롤러 기준 뒤가 아니라 누워있는 캐릭터 기준 뒤로 구르는 문제가 있었다. 입력 자체는 controller-space로 저장되고 있었지만, root motion은 현재 actor yaw를 기준으로 재생되니까 생기는 문제였다.

일단 가장 단순하게 HR_EscapeDodge 시작 직전에 actor yaw를 컨트롤러 yaw로 맞췄다. 보간은 아직 없다. 그래도 지금 프로토타입에선 이게 맞다. 누운 방향이 어떻든 W/A/S/D는 항상 카메라 기준 앞/왼/뒤/오른쪽이 되어야 한다.

Dodge / Sprint도 같이 정리

Dodge도 기존 ActionIndex 흐름에서 빼서 Chooser가 최종 FMVDodgeActionRowHandle을 반환하도록 바꿨다. 그리고 중간에 launch 기반 회피를 포기하고 root motion 기반으로 돌아왔다.

이유는 간단하다. 4방향 애니메이션으로 8방향 회피를 만들려고 launch로 직접 제어했는데, 결국 애니메이션이 가진 이동감을 코드가 대신 만드는 일이 됐다. 프로토타입에서 이것까지 끌어안으면 계속 삐걱거릴 것 같았다. 지금은 대각 방향 입력이면 캐릭터 yaw를 입력 방향으로 돌리고, 4방향 root motion을 재생하는 쪽으로 정리했다.

Sprint는 Chooser까지 필요 없어서 단일 DT_Sprint row로 실행하게 했다.

현재 상태

이번 브랜치에서 끝난 것:

  • HitReactionComponent 기본 흐름
  • KD/AB Getup/EscapeDodge 전환
  • RecoveryEscapeWindow 후딜 캔슬
  • Airborne 착지 감지
  • HitReaction / Dodge / Sprint의 RowHandle 기반 실행
  • 직접 관리하는 uasset DataTable 구조
  • PIE 대화 이후 피격 테스트 사이드 윈도우

아직 남은 것:

  • CombatComponent도 같은 RowHandle 구조로 맞추기
  • 공격 NotifyState에서 CollisionComponent 호출
  • HitResolver에 무기 스탯 연결
  • 몽타주 priority 시스템
  • InputManager의 입력 소비 우선순위 정리

흠 길었다. 그래도 이제 피격 쪽은 눈으로 봐도 "게임 같다"는 느낌이 조금 난다.