이번에는 AnimNotifyState를 이용해서 전투 모션의 타이밍을 제어한 내용을 정리해보려고 한다.
이전 글들에서는 CombatComponent, MeleeCombatComponent, RangedCombatComponent, 전투 UI 연동 등을 정리했다.
이번 글에서는 근접 공격, 회피, 피격 모션 등에서
공격 판정, 콤보 입력, 방향 전환, 전진, 무적 프레임, 무기 잔상 VFX 같은 처리를
애니메이션 타이밍에 맞춰 제어한 과정을 정리하려고 한다.
처음에는 공격 입력이 들어오면 단순히 몽타주를 재생하고,
공격 판정도 코드에서 일정 시간 동안 켜두는 방식으로 생각할 수 있었다.
하지만 근접 공격은 모션마다 실제 타격 타이밍, 콤보 입력 가능 구간, 전진 타이밍, 방향 전환 타이밍이 모두 달랐다.
그래서 코드에서 시간을 직접 맞추기보다는,
애니메이션 몽타주를 보면서 원하는 구간에 AnimNotifyState를 배치하는 방식으로 구현했다.
AnimNotifyState를 사용한 이유
AnimNotifyState를 사용한 가장 큰 이유는 몽타주 에디터에서 직접 보면서 조절할 수 있기 때문이다.
공격 판정이나 콤보 입력 구간을 코드의 타이머로 관리하면,
애니메이션 길이나 타격 타이밍이 바뀔 때마다 코드의 시간 값도 함께 수정해야 한다.
공격 모션 길이가 바뀜
→ 코드의 타이머 값 수정 필요
타격 타이밍이 바뀜
→ Trace 시작 / 종료 시간 수정 필요
콤보 입력 구간이 바뀜
→ 입력 허용 시간 수정 필요
이 방식은 공격 모션이 늘어날수록 관리하기 어렵다고 느꼈다.
반면 AnimNotifyState를 사용하면 몽타주 타임라인에서 직접 구간을 배치할 수 있다.
Notify Begin
- 구간 시작
Notify Tick
- 구간이 유지되는 동안 반복 처리
Notify End
- 구간 종료
그래서 공격 판정, 콤보 입력, 회전, 전진, 무적 프레임, 무기 잔상 같은 처리를
몽타주를 보면서 필요한 위치에 배치할 수 있었다.
이 점이 코드에서 시간을 맞추는 방식보다 훨씬 빠르고 직관적이었다.
공격 모션을 나누어 보기

기존 공격모션을 분석해보니, 공격 모션을 크게 세 구간으로 나눌 수 있었다.
공격 준비
- 공격이 나가기 전 준비 동작
- 방향 입력을 받을 수 있는 구간
공격
- 실제 무기가 휘둘러지는 구간
- 회전, 전진, Trace 판정, 사운드, VFX가 발생하는 구간
회복 모션
- 공격 이후 후딜레이 구간
- 다음 콤보 입력을 받을 수 있는 구간
이렇게 나누고 보니, 공격에 필요한 처리들이 전부 같은 타이밍에 발생하지 않는다는 것을 알 수 있었다.
예를 들어 공격 판정은 실제 무기가 휘둘러지는 구간에만 있어야 한다.
반대로 콤보 입력은 공격이 끝나가는 회복 모션 구간에서 받는 것이 자연스럽다.
또 공격 방향 입력은 공격이 나가기 직전 준비 구간에서 받아두고,
실제 공격 구간에서는 그 방향으로 회전하거나 전진하는 식으로 나누어 처리할 수 있었다.
사용한 AnimNotifyState
이번 전투 모션에서 사용한 NotifyState는 다음과 같다.
AnimNotifyState_MeleeTrace
- 실제 공격 판정 구간 제어
AnimNotifyState_ComboInput
- 다음 콤보 입력 가능 구간 제어
AnimNotifyState_DirectionInput
- 공격 준비 중 방향 입력 갱신
AnimNotifyState_AttackTurn
- 공격 중 저장된 방향으로 회전
AnimNotifyState_MoveForward
- 공격 중 전진 처리
AnimNotifyState_InvincibleFrame
- 회피 / 피격 중 무적 프레임 처리
AnimNotifyState_SlashTrail
- 무기 잔상 VFX 시작 / 종료
각 NotifyState는 전투 시스템의 전체 상태를 판단하기보다는,
몽타주 안에서 특정 구간에 필요한 처리를 호출하는 역할을 맡는다.
ComboInput NotifyState
콤보 입력은 아무 때나 받으면 안 된다고 생각했다.
공격 시작 직후부터 입력을 받아버리면 버튼을 연타했을 때 콤보가 너무 쉽게 이어질 수 있다.
반대로 입력 가능 구간이 너무 짧거나 늦으면 조작감이 답답해질 수 있다.
그래서 콤보 입력 가능 구간을 AnimNotifyState_ComboInput으로 분리했다.

Notify Begin
→ OpenComboInput()
Notify End
→ CloseComboInput()
몽타주 타임라인에서 회복 모션 구간에 ComboInput NotifyState를 배치하면,
그 구간에서만 다음 콤보 입력을 받을 수 있다.
공격 초반 입력
→ 무시
ComboInput 구간 입력
→ 다음 콤보 섹션으로 이동
ComboInput 구간 이후 입력
→ 무시
콤보 섹션 구성과 JumpToSection을 이용한 콤보 전환은 이전 MeleeCombatComponent글에서 정리했기 때문에,
이번 글에서는 콤보 입력 구간을 몽타주에서 제어했다는 점만 짚고 넘어가려고 한다.
MeleeTrace NotifyState
근접 공격 판정도 AnimNotifyState로 제어했다.
테스트 단계에서는 공격 모션 전체 동안 판정을 켜도 동작은 한다.
하지만 실제 게임에서는 무기가 휘둘러지는 구간에만 공격 판정이 있어야 한다.
공격 준비 동작이나 회복 모션에서 적이 맞으면 어색하기 때문이다.
그래서 AnimNotifyState_MeleeTrace를 만들었다.

Notify Begin
→ BeginAttackTrace()
Notify End
→ EndAttackTrace()
이 NotifyState는 실제 무기가 휘둘러지는 구간에 배치한다.
공격 판정 자체는 MeleeCombatComponent에서 처리하고,MeleeTrace NotifyState는 판정의 시작과 종료 타이밍만 알려주는 역할을 한다.
MeleeTrace 시작
→ 공격 판정 활성화
→ HitActors 초기화
MeleeTrace 종료
→ 공격 판정 비활성화
이렇게 하면 공격마다 다른 타격 타이밍을 몽타주에서 직접 조절할 수 있다.
공격 대상 판별 방식 변경
공격 대상 판별도 처음에는 AEnemyBase 기준으로 처리했지만,
이후 부서지는 오브젝트도 같은 공격 판정으로 피해를 받아야 했다.
그래서 특정 클래스가 아니라 IDamageable 인터페이스를 기준으로 판별하도록 변경했다.
이렇게 하니 적 캐릭터와 부서지는 오브젝트 모두 같은 Trace 판정 흐름에서 데미지를 받을 수 있었다.
또 Trace 결과를 순회할 때 피격 대상이 아닌 액터를 만났다고 return을 사용하면
뒤의 결과까지 모두 무시될 수 있기 때문에, 피격 불가능한 대상은 continue로 넘기도록 수정했다.

피격 불가능한 대상
→ continue
피격 가능한 대상
→ 데미지 적용
DirectionInput NotifyState
공격 방향 입력도 별도의 NotifyState로 처리했다.
소울라이크식 전투에서는 공격 버튼을 눌렀을 때 캐릭터가 무조건 현재 바라보는 방향으로만 공격하면 답답하게 느껴질 수 있다.
예를 들어 카메라는 정면을 보고 있는데 캐릭터가 뒤돌아 있는 상태에서
W 키를 누르며 공격하면, 카메라 기준 전방으로 몸을 돌리며 공격하는 편이 더 자연스럽다.
그래서 AnimNotifyState_DirectionInput을 만들었다.

실제 구현에서는 NotifyBegin, NotifyEnd를 따로 사용하지 않고,NotifyTick에서 현재 WASD 입력 방향을 계속 갱신하는 방식으로 처리했다.
DirectionInput NotifyState 구간
Notify Tick
→ 현재 이동 입력 확인
→ 카메라 기준 공격 방향 갱신
이 NotifyState는 공격 준비 구간 동안 매 Tick마다 현재 WASD 입력을 확인하고,
입력이 있다면 카메라 기준 이동 방향을 공격 방향으로 갱신한다.
입력이 없다면 기존에 바라보던 방향을 유지하도록 했다.
방향 입력 있음
→ 카메라 기준 입력 방향 저장
방향 입력 없음
→ 현재 캐릭터 방향 유지
이렇게 하면 공격 준비 구간에서 입력한 방향을 기준으로,
실제 공격 구간에서 회전과 전진을 처리할 수 있다.
AttackTurn NotifyState
공격 준비 구간에서 방향을 저장했다면,
실제 공격 구간에서는 그 방향으로 캐릭터를 회전시켜야 한다.
이를 위해 AnimNotifyState_AttackTurn을 만들었다.

AttackTurn NotifyState 구간
Notify Tick
→ 저장된 공격 방향 확인
→ 해당 방향으로 캐릭터 회전
이렇게 하면 공격 준비 중 입력한 방향을 기준으로,
공격이 나가는 순간 캐릭터가 자연스럽게 방향을 틀 수 있다.
예를 들어 캐릭터가 뒤를 보고 있더라도,
카메라 기준 전방 입력과 함께 공격하면 앞으로 회전하며 공격할 수 있다.
MoveForward NotifyState
근접 공격은 제자리에서만 휘두르면 타격감이 약하게 느껴질 수 있다.
처음에는 애니메이션의 Root Motion과 Motion Warping을 사용해서 전진성을 주는 방법도 생각했다.
하지만 내가 사용한 공격 애니메이션들은 루트 모션 옵션을 켜더라도
애니메이션 자체에 앞으로 나아가는 움직임이 거의 없었다.
즉, Root Motion을 켜도 사용할 만한 전진성이 나오지 않았다.
그래서 직접 AnimNotifyState_MoveForward를 만들어서 공격 구간에 전진성을 부여했다.

MoveForward NotifyState 구간
Notify Tick
→ 캐릭터 전방 방향 계산
→ MoveSpeed * DeltaTime 만큼 이동
이 NotifyState를 공격 모션 중 일부 구간에만 배치해서,
무기를 휘두르는 순간 앞으로 밀고 나가는 느낌을 주도록 했다.
공격 준비
→ 방향 입력 감지
공격 구간
→ 회전
→ 전진
→ Trace 판정
회복 모션
→ 콤보 입력 대기
이렇게 하니 공격이 제자리에서만 나가는 느낌을 줄이고,
조금 더 적극적으로 파고드는 느낌을 줄 수 있었다.
InvincibleFrame NotifyState
무적 프레임은 회피 모션과 피격 모션에 사용했다.
처음에는 회피 중에만 무적 프레임이 필요하다고 생각했다.
하지만 피격 모션 중에도 잠깐 무적이 필요했다.
공격을 한 번 맞은 직후 계속 연타 공격을 받으면, 플레이어가 너무 쉽게 죽을 수 있기 때문이다.
그래서 회피 모션뿐만 아니라 피격 모션에도 AnimNotifyState_InvincibleFrame을 배치했다.

Notify Begin
→ 무적 활성화
Notify End
→ 무적 해제
StatComponent에는 무적 여부를 나타내는 값을 두고,
데미지를 받을 때 무적 상태라면 데미지를 무시하도록 했다.
회피 모션
→ 특정 구간에서 무적 활성화
→ 구간 종료 후 무적 해제
피격 모션
→ 피격 직후 잠깐 무적 활성화
→ 연속 피격 방지
→ 구간 종료 후 무적 해제
이렇게 하면 회피 조작감도 살릴 수 있고,
피격 직후 연속 공격으로 너무 쉽게 죽는 상황도 어느 정도 막을 수 있다.
SlashTrail NotifyState
무기 잔상 VFX도 AnimNotifyState로 제어했다.
언리얼에는 기존 Niagara Notify를 사용할 수 있다.
하지만 이번 프로젝트에서는 기본 Niagara Notify만으로는 조금 맞지 않는 부분이 있었다.
기존 Niagara Notify는 캐릭터의 특정 소켓을 기준으로 이펙트를 생성하기 좋다.
하지만 이번 프로젝트의 무기는 캐릭터 메시의 일부가 아니라,
별도의 무기 Actor로 장착된다.
그리고 무기 잔상은 캐릭터의 손 소켓이 아니라,
무기 자체의 끝부분이나 지정된 소켓을 기준으로 발생해야 했다.
기본 Niagara Notify
- 캐릭터 메시의 소켓 기준으로 사용하기 좋음
이번 프로젝트의 무기 잔상
- 장착된 무기 Actor 기준
- 무기 소켓 기준으로 Trail VFX 실행 필요
그래서 AnimNotifyState_SlashTrail을 직접 만들었다.

Notify Begin
→ 현재 장착 중인 근접 무기 찾기
→ 무기의 Trail VFX 시작
Notify End
→ 현재 장착 중인 근접 무기 찾기
→ 무기의 Trail VFX 종료
이렇게 하면 캐릭터 소켓이 아니라,
현재 장착된 무기의 소켓과 NiagaraComponent를 기준으로 잔상 효과를 제어할 수 있다.
공격 판정과 마찬가지로 Trail VFX도 몽타주 타임라인에서 직접 배치하니 조절하기 쉬웠다.
MeleeTrace
- 실제 데미지 판정 구간
SlashTrail
- 무기 잔상 VFX 구간
두 구간을 완전히 동일하게 둘 수도 있고,
연출에 따라 Trail을 조금 더 길게 두는 식으로 조정할 수도 있다.
사운드 처리 기준
처음에는 공격 사운드를 코드에서 재생했다.
하지만 실제로 구현해보니 공격 사운드는 공격 로직보다 애니메이션 타이밍에 더 강하게 묶여 있었다.
예를 들어 무기를 휘두르는 소리는 공격 입력 시점이 아니라,
실제 무기가 움직이는 타이밍에 맞아야 자연스럽다.
그래서 공격 사운드는 코드에서 재생하기보다 AnimNotify로 옮겼다.


공격 사운드
- AnimNotify에서 재생
재장전 삽입 사운드
- 탄창이 들어가는 Notify 타이밍에 재생
반대로 코드에서 처리하는 것이 더 적절한 사운드도 있다.
탄약 부족 사운드
- 실제로 발사에 실패했을 때
입력 실패 사운드
- 행동 조건을 만족하지 못했을 때
UI 클릭 사운드
- UI 이벤트와 직접 연결될 때
정리하면 기준은 다음과 같다.
애니메이션 타이밍에 의존하는 사운드
→ AnimNotify에서 처리
게임 로직 결과에 따라 재생되는 사운드
→ 코드에서 처리
이렇게 구분하니 사운드가 애니메이션 타이밍과 더 자연스럽게 맞고,
코드에서는 실제 게임 로직에 필요한 사운드만 처리할 수 있었다.
정리
이번에는 AnimNotifyState를 이용해서 전투 모션의 타이밍을 제어한 내용을 정리했다.
핵심은 코드에서 모든 타이밍을 직접 관리하는 것이 아니라,
몽타주 타임라인에서 직접 보면서 필요한 구간에 NotifyState를 배치하는 것이었다.
MeleeTrace NotifyState
- 공격 판정 구간 제어
ComboInput NotifyState
- 다음 공격 입력 허용 구간 제어
DirectionInput NotifyState
- 공격 준비 구간에서 방향 입력 갱신
AttackTurn NotifyState
- 공격 구간에서 저장된 방향으로 회전
MoveForward NotifyState
- 공격 구간에서 전진 처리
InvincibleFrame NotifyState
- 회피 / 피격 중 무적 구간 제어
SlashTrail NotifyState
- 무기 잔상 VFX 구간 제어
AnimNotify Sound
- 애니메이션 타이밍에 맞는 사운드 재생
이 구조 덕분에 공격별로 다른 타격 타이밍, 입력 가능 구간, 전진 구간, 회전 구간을
애니메이션 안에서 직접 조절할 수 있게 되었다.
또 이후 새로운 무기나 공격 모션이 추가되어도,
코드를 크게 수정하기보다 몽타주와 Notify 배치를 조정해서 확장할 수 있는 형태가 되었다.
실행 예시, 적용한 모습


마무리
이번 작업을 하면서 근접 공격은 단순히 몽타주를 재생하는 것으로 끝나는 기능이 아니라는 것을 느꼈다.
공격이 자연스럽게 느껴지려면 실제 타격 판정, 콤보 입력, 방향 전환, 전진, 사운드, VFX가
각각 애니메이션의 적절한 타이밍에 맞춰 실행되어야 했다.
특히 이번에는 몽타주 에디터에서 직접 구간을 보면서 조절할 수 있다는 점 때문에 AnimNotifyState를 적극적으로 사용했다.
공격 판정 구간, 콤보 입력 구간, 회전과 전진 구간, 무적 프레임, 무기 잔상 VFX를
몽타주 안에서 직접 조절할 수 있으니 작업 속도도 빨라졌고, 공격 감각을 맞추기도 훨씬 쉬웠다.
이번 작업을 통해 AnimNotifyState는 단순히 애니메이션 중간에 함수를 호출하는 용도가 아니라,
전투 모션의 구간을 나누고 각 구간에 필요한 게임 로직과 연출을 연결하는 중요한 도구라는 것을 알게 되었다.
다음 글에서는 팀 프로젝트를 마무리하면서 진행한 통합 작업을 정리해보려고 한다.
팀원이 적용한 레벨 스트리밍 구조에서 서브레벨이 비동기로 로드되며 플레이어가 바닥으로 빠지는 문제가 있었고,
나는 이를 해결하기 위해 레벨 스트리밍 완료 시점과 게임 시작 흐름을 동기화했다.
또 로딩 화면과 층별 BGM 출력 같은 마무리 작업도 함께 정리할 예정이다.
'언리얼 엔진 > 프로젝트' 카테고리의 다른 글
| [언리얼 엔진] 멀티플레이 상품 Drop 중 HardSnap 문제 해결 (0) | 2026.06.22 |
|---|---|
| [언리얼 엔진] 팀 프로젝트 기록 11 - 레벨 스트리밍 동기화와 프로젝트 마무리 (0) | 2026.05.29 |
| [언리얼 엔진] 팀 프로젝트 기록 09 - 전투 UI와 데미지 팝업 구현하기 (0) | 2026.05.22 |
| [UE5 C++] 팀 프로젝트 기록 08 - RangedCombatComponent로 사격과 재장전 구현하기 (0) | 2026.05.21 |
| [언리얼 엔진] 팀 프로젝트 기록 07 - MeleeCombatComponent로 근접 공격과 콤보 구현하기 (0) | 2026.05.20 |