애님 몽타주와 노티파이의 기초
공격 몽타주를 구성하고 노티파이로 타격 시점의 게임 로직을 호출해 일회성 전투 애니메이션을 완성합니다.
앞의 두 글에서는 Idle, Walk, Run처럼 계속 이어지는 움직임을 스테이트 머신과 블렌드 스페이스로 처리했습니다. 하지만 게임에는 정해진 순간에 한 번 재생되고 끝나는 동작도 많습니다. 공격, 스킬 사용, 피격, 구르기, 문 열기 같은 동작이 그렇습니다. 이런 동작을 이동 스테이트 머신에 상태로 하나씩 더하면 전환 규칙이 금세 늘어나고, "칼이 적에게 닿는 순간에 피해를 준다"처럼 애니메이션의 특정 시점에 맞춰 게임 로직을 실행하기도 어렵습니다.
언리얼 엔진은 이 두 문제를 애님 몽타주(Anim Montage)와 애님 노티파이(Anim Notify)로 풉니다. 이 글에서는 공격 몽타주를 만들고, 타격 시점에 노티파이를 넣은 뒤, 캐릭터의 입력으로 몽타주를 재생하고 애님 블루프린트에서 노티파이를 받아 판정 로직을 호출하는 데까지 연결합니다. 그리고 공격이 끝났을 때와 중간에 끊겼을 때 상태를 정리하는 방법을 함께 살펴봅니다.
한 번 재생하는 동작을 담는 몽타주
애님 몽타주는 애니메이션 시퀀스 여러 개를 시간 축에 배치해 하나의 복합 애니메이션으로 만드는 에셋입니다. 한 행동에 필요한 애니메이션을 조합하고, 재생 순서와 블렌딩을 제어하며, 블렌드 스페이스로 재생 중인 이동 동작 위에 겹쳐 재생할 수도 있습니다.
몽타주를 쓰면 공격, 스킬, 피격, 사망처럼 한 번 재생하고 끝나는 동작을 이동 스테이트 머신과 따로 관리할 수 있습니다. 공격 준비, 공격, 공격 후 경직처럼 여러 시퀀스를 이어 하나의 동작으로 만들 수 있고, 상체에만 몽타주를 적용해 하체는 기존의 걷기나 달리기를 유지하게 할 수도 있습니다. 또 다음 절에서 볼 노티파이와 함께 쓰면 애니메이션의 특정 시점에 게임 로직을 실행할 수 있습니다.
공격 몽타주 만들기
공격 애니메이션 시퀀스 하나(이 글에서는 Attack_Swing이라고 부릅니다)로 몽타주를 만들어 보겠습니다. 콘텐츠 브라우저의 빈 곳을 우클릭해 Animation > Animation Montage를 고르고, 몽타주를 적용할 캐릭터의 스켈레톤을 선택한 뒤 이름을 AM_PlayerAttack으로 정합니다. 공식 문서에 따르면 애니메이션 시퀀스를 우클릭해 Create > Create AnimMontage를 골라도 그 시퀀스로 몽타주를 만들 수 있습니다.
AM_PlayerAttack을 더블클릭하면 몽타주 에디터가 열립니다. 에셋 브라우저(Asset Browser)에서 Attack_Swing을 찾아 몽타주의 트랙으로 끌어다 놓으면 몽타주가 이 애니메이션으로 구성됩니다. 여러 시퀀스를 차례로 끌어다 놓아 순서대로 이어 붙일 수도 있습니다.
슬롯 정하기
몽타주는 슬롯(Slot)이라는 이름 붙은 통로를 통해 애님 그래프의 포즈에 끼어듭니다. 기본으로는 DefaultGroup.DefaultSlot을 쓰며, 몸의 특정 부위에 쓸 동작이라면 Anim Slot Manager에서 UpperBody 같은 새 슬롯을 만들어 필요한 슬롯 그룹에 넣을 수 있습니다. 다만 슬롯과 그룹은 이름일 뿐이라서, UpperBody라고 이름 붙였다고 적용되는 뼈가 상체로 제한되지는 않습니다. 실제로 상체에만 적용하려면 애님 그래프에서 그 슬롯의 포즈를 Layered Blend Per Bone 노드로 섞어, 허리 위의 뼈에만 영향을 주도록 설정해야 합니다.
이 글의 기본 실습은 DefaultGroup.DefaultSlot을 그대로 씁니다. 몽타주를 설정했으면 저장합니다.
정해진 순간에 이벤트를 보내는 노티파이
애님 노티파이는 애니메이션 시퀀스나 몽타주의 타임라인에 지정한 지점을 재생이 지나갈 때 이벤트를 발생시키는 기능입니다. 이 이벤트를 애님 블루프린트의 이벤트 그래프나 다른 블루프린트에서 받아 게임플레이 로직을 실행합니다.
노티파이를 쓰는 가장 큰 이유는 타이밍을 맞추기 위해서입니다. 칼을 휘두르는 애니메이션에서 칼날이 실제로 앞을 지나는 프레임에 사운드를 재생하고, 피해를 주고, 이펙트를 띄우면 동작과 결과가 어긋나지 않습니다. 애니메이터가 애니메이션 에디터에서 직접 이벤트 시점을 정할 수 있으므로, 타이밍을 조정할 때마다 프로그래머가 숫자를 고칠 필요도 없어 협업도 쉬워집니다.
AttackHit 노티파이 추가하기
AM_PlayerAttack 몽타주 에디터를 열고, 타임라인 아래 Notifies 영역에서 트랙 추가 버튼으로 노티파이 트랙(Notify Track)을 하나 만듭니다. 그다음 재생 위치를 무기가 실제로 적을 치는 프레임으로 옮기고, 노티파이 트랙을 우클릭해 Add Notify... > New Notify...를 고른 뒤 이름을 AttackHit으로 정합니다.
이렇게 이름만으로 만든 노티파이를 스켈레톤 노티파이(Skeleton Notify)라고 부르며, 그 이름이 스켈레톤 에셋에도 저장됩니다. 그래서 몽타주를 저장할 때 변경된 스켈레톤도 함께 저장해야 합니다.
더 깊이: 노티파이가 늦거나 빠지는 경우
노티파이가 정확히 어느 순간에 실행되는지는 설정에 따라 달라집니다. 애니메이션 노티파이 문서에 따르면 몽타주에 놓은 노티파이는 Montage Tick Type으로 두 방식 가운데 하나를 고릅니다. Queued는 비동기로 처리되어 비용이 적은 대신 실행 시점이 프레임 단위로 조금 어긋날 수 있고, Branching Point는 동기로 처리되어 비용이 더 들지만 프레임 정확도가 가장 높습니다. 문서는 노티파이로 게임플레이를 바꾸는 판단을 내리거나 다른 이벤트를 정해진 순서로 이어 갈 때 Branching Point가 알맞다고 설명합니다.
노티파이가 아예 실행되지 않는 경우도 있습니다. 애니메이션이 다른 애니메이션과 섞이고 있을 때는 블렌드 가중치가 Trigger Weight Threshold를 넘어야 노티파이가 실행되는데, 기본값이 0.00001이라 아주 조금만 섞여도 실행됩니다. 이 값을 높이면 그만큼 충분히 섞인 뒤에만 실행됩니다. 또 Notify Trigger Chance가 1보다 작으면 그 확률로만 실행되고, 데디케이티드 서버에서는 Trigger on Dedicated Server가 켜져 있어야 실행됩니다. 피해를 주는 AttackHit처럼 빠지면 안 되는 노티파이라면 이 값들부터 확인합니다.
애님 블루프린트에서 사용해 보기
이제 캐릭터의 입력으로 AM_PlayerAttack을 재생하고, AttackHit 노티파이를 받아 판정 로직을 실행해 보겠습니다. 먼저 한 번의 재생에서 무엇이 어디로 흘러가는지 그림으로 확인합니다.
PLAY REQUEST · POSE / NOTIFY
한 재생에서 포즈 출력과 Notify 이벤트를 구분합니다
공격 입력에서 Play Montage를 한 번 요청합니다. 재생 중 Slot을 통해 포즈를 구성하는 경로와 AttackHit Skeleton Notify가 AnimBP 이벤트를 부르는 경로는 순차 단계가 아닙니다.
실선은 재생·포즈 경로, 점선은 Notify 이벤트 경로입니다. 완료·중단 정리는 캐릭터의 별도 콜백에서 처리합니다.
Slot과 Notify의 연결 조건
몽타주와 AnimGraph의 Slot 이름을 맞춥니다. 이 예제의 Skeleton Notify는 AnimBP에서 받으며 Play Montage의 Notify Begin/End 출력과 구별합니다.
공격 종료와 피해 권한
완료·중단·재생 실패에서 공격 플래그와 판정을 정리합니다. Notify는 재생·가중치·LOD 등의 조건을 확인하며 네트워크 피해 권한과 중복 방지를 자동 보장하지 않습니다.
위 그림은 공격 입력 한 번이 만드는 두 갈래의 경로를 보여 줍니다. 공격 입력은 Play Montage에 Mesh와 AM_PlayerAttack을 넣어 재생을 한 번 요청합니다. 실선으로 그린 첫 경로에서는 재생 중인 몽타주가 이름이 일치하는 AnimGraph의 DefaultSlot을 거쳐 최종 포즈로 출력됩니다. 점선으로 그린 둘째 경로에서는 AttackHit 스켈레톤 노티파이가 애님 블루프린트의 이벤트를 부르고, 그 이벤트가 판정 로직을 호출합니다.
그림에서 강조하듯 두 경로는 차례로 실행되는 단계가 아니라, 같은 재생에서 나란히 일어나는 일입니다. 슬롯이 연결되려면 몽타주와 AnimGraph의 슬롯 이름이 같아야 하고, 이 예제의 스켈레톤 노티파이는 애님 블루프린트에서 받으므로 Play Montage 노드의 On Notify Begin과 On Notify End 출력과는 다른 경로입니다. 공격이 끝나거나 중간에 끊겼을 때의 정리는 그림 아래에 적힌 대로 캐릭터 쪽의 별도 콜백에서 처리합니다.
애님 그래프에 슬롯 노드 넣기
애님 블루프린트(예: ABP_Mannequin)를 열고 애님 그래프 탭으로 갑니다. Default State Machine의 포즈 출력과 최종 Output Pose 사이에 Slot 'DefaultSlot' 노드를 끼워 넣습니다. 이 노드는 DefaultGroup.DefaultSlot에서 재생되는 몽타주의 포즈를 받아, 몽타주가 재생되는 동안 스테이트 머신의 포즈 대신 내보내거나 섞습니다. 상체에만 적용하고 싶다면 앞에서 말한 대로 Layered Blend Per Bone과 함께 씁니다. 언리얼 엔진 4의 자료에서는 최종 노드를 Final Animation Pose라고 부르지만, 5.8의 애님 그래프에서는 Output Pose입니다.
재생 요청은 입력에서 한 번만
몽타주 재생은 애님 블루프린트의 업데이트 이벤트에서 요청하지 않습니다. Event Blueprint Update Animation은 거의 매 프레임 실행되므로, 그 안에서 IsAttacking이 참인 동안 재생을 요청하면 몽타주를 계속 처음부터 다시 시작하게 될 수 있습니다. 대신 캐릭터의 공격 입력에서 Play Montage를 한 번 호출하고, 재생이 끝나거나 끊겼다는 콜백에서 상태를 정리합니다.
BP_PlayerCharacter 블루프린트를 열고 공격 입력 이벤트를 놓습니다. 빠른 확인을 위해 Left Mouse Button 키 이벤트를 써도 되지만, 5.8의 기본 입력 방식인 Enhanced Input의 입력 액션으로 바꾸면 키 설정을 나중에 고치기 쉽습니다. 캐릭터에 Boolean 변수 IsAttacking을 만들어 기본값을 false로 두고, 입력이 들어오면 IsAttacking이 false인지 확인한 뒤 true로 바꿉니다.
이어서 캐릭터의 이벤트 그래프에 Play Montage를 놓고, In Skeletal Mesh Component에 Mesh를, Montage to Play에 AM_PlayerAttack을 연결합니다. 이 노드의 일반 실행 출력은 재생이 끝나기를 기다리지 않고 바로 다음으로 넘어갑니다. 그래서 공격을 마무리하는 일은 On Completed와 On Interrupted에 각각 연결합니다. 두 출력 모두에서 IsAttacking을 false로 되돌리고 공격 판정을 정리하며, On Interrupted는 다른 몽타주에 밀려 끊긴 경우뿐 아니라 재생을 시작하지 못한 경우에도 실행됩니다.
Play Montage는 비동기로 결과를 알려 주는 노드라서, 재생한 몽타주의 인스턴스를 반환하는 함수가 아닙니다. 애님 인스턴스를 대상으로 하는 Montage Play 노드와는 다른 노드이므로 혼동하지 않습니다. 또 앞에서 만든 스켈레톤 노티파이 AttackHit은 애님 블루프린트의 이벤트로 받습니다. Play Montage의 On Notify Begin과 On Notify End는 몽타주 노티파이(Montage Notify)나 몽타주 노티파이 윈도우(Montage Notify Window)라는 별도의 노티파이를 쓸 때의 경로입니다.
노티파이 이벤트 받기
애님 블루프린트의 이벤트 그래프로 돌아와 빈 곳을 우클릭하고, 추가한 노티파이 이름 AttackHit을 검색해 AnimNotify_AttackHit 이벤트 노드를 추가합니다. 이 이벤트 뒤에 공격이 실제로 적에게 피해를 주는 로직을 연결합니다. 처음에는 Print String으로 "공격 히트 발생!" 같은 문구를 출력해 시점을 확인하고, 그다음 Apply Damage 같은 노드로 바꾸면 됩니다. 연결을 마쳤으면 컴파일하고 저장합니다.
이 이벤트를 "공격마다 반드시 한 번 실행되는 피해 처리"로 가정해서는 안 됩니다. 스켈레톤 노티파이는 블렌드 가중치, LOD, 재생 중단 같은 조건에 따라 처리 여부가 달라질 수 있습니다. 노티파이는 타격 시점을 알려 주는 신호로 쓰고, 피해를 줄 권한과 중복 판정 방지는 따로 설계합니다.
전투 애니메이션 확인하기
게임을 플레이하고 공격 버튼을 눌러 봅니다. 캐릭터가 공격 애니메이션을 재생하고, AttackHit 노티파이가 있는 지점에서 "공격 히트 발생!" 문구가 출력되거나 피해가 적용되면 연결이 맞은 것입니다. 공격 애니메이션이 끝난 뒤 다시 Idle이나 Walk_Run 상태로 자연스럽게 돌아가는지도 확인합니다.
몽타주가 재생되지 않거나 노티파이가 실행되지 않을 때는 입력, 슬롯, 타임라인, 이벤트 수신을 따로 확인합니다. 입력 이벤트에서 Play Montage까지 실행이 도달하는지, 애님 그래프에 몽타주와 같은 이름의 Slot 노드가 Output Pose 앞에 있는지, 노티파이가 몽타주 타임라인 안의 재생되는 구간에 있는지, 애님 블루프린트의 노티파이 이벤트 이름이 맞는지를 차례로 봅니다.
정상적으로 끝난 공격뿐 아니라 중간에 끊긴 공격과 시작하지 못한 공격에서도 공격 플래그와 히트 판정이 정리되어야 합니다. 네트워크 게임이라면 누가 피해를 줄 권한을 가지는지와 같은 공격이 두 번 판정되지 않게 하는 방법도 따로 설계해야 하며, 노티파이가 그 일까지 해 주지는 않습니다. 리플리케이션은 블루프린트에서의 리플리케이션에서 다룹니다.
정리
- 애님 몽타주는 한 번 재생하는 동작을 시퀀스 조합으로 만드는 에셋이고, 슬롯을 통해 애님 그래프의 포즈에 끼어듭니다.
- 몽타주의 슬롯 이름과 애님 그래프의 Slot 노드 이름이 같아야 하며, 슬롯 이름만으로 적용 부위가 제한되지는 않습니다. 부위를 나누려면
Layered Blend Per Bone을 씁니다. - 애님 노티파이는 타임라인의 지정 지점에서 이벤트를 보내며, 스켈레톤 노티파이는 애님 블루프린트의
AnimNotify_이름이벤트로 받습니다. - 몽타주 재생은 입력에서
Play Montage로 한 번 요청하고,On Completed와On Interrupted에서 공격 상태를 정리합니다. - 노티파이는 타격 시점의 신호일 뿐, 반드시 한 번 실행되는 피해 처리나 네트워크 권한을 보장하지 않습니다.
연습 문제
1. 타격 이벤트 없이 끝난 공격
AttackHit 지점에 닿기 전에 공격이 끊겼고, 이 공격에서는 AttackHit 이벤트가 한 번도 오지 않았습니다. IsAttacking을 false로 되돌리는 일을 AttackHit 이벤트에만 두었다면 다음 공격을 다시 시작할 수 있을까요? 이 글의 어느 경로가 이 누락을 막나요?
해설 보기
다시 시작할 수 없습니다. AttackHit이 오지 않았으므로 그 이벤트에만 있는 초기화가 실행되지 않고, IsAttacking이 true로 남아 다음 입력을 계속 막습니다.
이 누락은 캐릭터의 Play Montage에서 On Interrupted에 연결한 정리가 막습니다. 여기서 IsAttacking을 false로 되돌리고 공격 판정을 정리하며, 정상적으로 끝난 경우를 위해 On Completed도 따로 연결합니다. 노티파이는 타격 시점에 관한 신호이고, 완료와 중단의 정리는 재생이 끝나는 시점에 관한 일이므로 두 가지를 나누어 둡니다.
2. 업데이트 이벤트에서 재생 요청하기
동료가 애님 블루프린트의 Event Blueprint Update Animation에서 IsAttacking이 참이면 Montage Play를 호출하도록 만들었습니다. 공격 버튼을 한 번 눌렀을 뿐인데 공격 동작이 처음 몇 프레임만 반복되며 끝나지 않습니다. 왜 이런 일이 생길까요?
해설 보기
업데이트 이벤트는 거의 매 프레임 실행되므로, IsAttacking이 참인 동안 매 프레임 재생을 새로 요청하게 됩니다. 같은 몽타주를 계속 처음부터 다시 시작하니 공격 동작이 앞부분만 반복되고, 재생이 끝까지 가지 못하니 완료 처리도 일어나지 않습니다.
재생 요청은 공격 입력이 들어온 순간에 한 번만 보내야 합니다. 이 글처럼 캐릭터의 입력 이벤트에서 Play Montage를 호출하고, 애님 블루프린트의 업데이트 이벤트는 속도 같은 값을 갱신하는 데만 씁니다.
3. 노티파이는 오는데 동작이 보이지 않음
공격 버튼을 누르면 "공격 히트 발생!" 문구는 정상적으로 출력되는데, 캐릭터는 공격 동작 없이 계속 걷기만 합니다. 어디를 먼저 확인해야 할까요?
해설 보기
애님 그래프의 Slot 노드를 먼저 확인합니다. 노티파이가 온다면 몽타주의 재생 자체는 시작되었을 가능성이 높지만, 그 포즈는 이름이 맞는 Slot 노드를 거쳐야 최종 포즈에 반영됩니다. 그림에서 본 것처럼 노티파이 경로와 포즈 경로는 서로 다른 길이므로, 한쪽이 동작한다고 다른 쪽도 연결되었다고 볼 수 없습니다.
Slot 'DefaultSlot' 노드가 스테이트 머신과 Output Pose 사이에 있는지, 몽타주가 쓰는 슬롯 이름과 Slot 노드의 슬롯 이름이 같은지를 확인합니다.
몽타주를 만드는 방법과 슬롯 설정은 언리얼 엔진 공식 문서의 애니메이션 몽타주에서 확인할 수 있습니다.
다음 글 프로시저럴 애니메이션 구현에서는 미리 만든 애니메이션 대신 IK와 물리 기반 애니메이션으로 지형과 충돌에 실시간으로 반응하는 포즈를 만듭니다.