공부용 이모저모
UE4 - 애니메이션 몽타주 본문
이득우의 언리얼 C++ - 챕터 8
애니메이션 몽타주
캐릭터에 공격을 넣기위해 연속공격을 누르고 공격하는 애니메이션을 만든다.
몽타주는 섹션 단위로 애니메이션을 관리한다.
몽타주 : 촬영 화면이나 인쇄된 종이를 떼어붙혀 새로운 장면을 만드는 마술기법
언리얼에서는 애니메이션들을 자르고 붙이는 관리용으로 쓰인다.
모션이 끝난 동작이랑 같이 나오기 때문에 어색하게 이어진다.
디테일란에서 애니메이션 수치를 수정 텡리할 수 있다.
드래그로 애니메이션 간 순서를 조정 할 수 있다.
ABAnimInstance.h
public:
void PlayAttackMontage();//몽타주 실행
private:
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
UAnimMontage* AttackMontage;
AnimInstance의 헤더에 PlayAttackMontage 함수와,
AttackMontage 몽타주 변수를 만든다.
UABAnimInstance::UABAnimInstance()
{
...
static ConstructorHelpers::FObjectFinder<UAnimMontage> ATTACK_MONTAGE(TEXT("/Game/Tekken/Character/Animation/newHwoarang_Skeleton_Montage.newHwoarang_Skeleton_Montage"));
if (ATTACK_MONTAGE.Succeeded())
{
AttackMontage = ATTACK_MONTAGE.Object;
}
}
//델리게이트에선 공격의 시작,종료일때만 감지하므로 따로 반복실행을 걱정 할 필요는 없다.
void UABAnimInstance::PlayAttackMontage()
{
Montage_Play(AttackMontage, 1.0f);
}
AnimInstance.cpp 파트.
몽타주를 받아오고, 성공시 만들어놓은 변수에 대입시킨다.
#include "ABCharacter.h"
#include "ABAnimInstance.h"
void AABCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
//프로젝트 세팅에 있는 함수들을 해당 함수로 이어주는 함수포인터 역할이다
//축매핑(BindAxis)
...
//액션 매핑(BindAction)
...
PlayerInputComponent->BindAction(TEXT("Attack"), EInputEvent::IE_Pressed, this, &AABCharacter::Attack);
}
void AABCharacter::Attack()
{
auto AnimInstance = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
// 실패시 리턴
if (nullptr == AnimInstance)
return;
AnimInstance->PlayAttackMontage();
}
ABCharacter 추가 코드. 헤더에는 void Attack();만 선언해준다
델리게이트
지금 방식은 버튼 한번누르면 마지막 애니메이션까지 다 나온다.
이렇게 하지말고 1타 입력후 재입력 시점을 받아서 2타를 입력받게 하는것이
일반적인 콤보공격의 모양새이다.
프로그래밍 적 관점 - 특정 개체가 해야할 일을 다른 개체에게 대신 진행시키게 해주는 역할
언리얼 적 관점 - A객체가 B객체에 명령을 내릴 때, B객체에 자신을 등록하고 B 작업이 끝날때 A에게 그 신호를 보내줆
public:
virtual void Tick(float DeltaTime) override;
virtual void PostInitializeComponents() override;
...
private:
...
//언리얼 함수
UFUNCTION()
void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);
private:
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
bool IsAttacking;
ABCharacter.h 추가사항. Tick 함수 밑에 추가한다.
UFUNCTION - 언리얼 전용 함수.
UPROPERTY - 언리얼 전용 변수.
AABCharacter::AABCharacter()
{
...
IsAttacking = false;
}
void AABCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
auto AnimInstance = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
//OnMontageEnded는 AnimInstance 기본 변수이다.몽타주가 끝났을 때 AttackMontageEnded 함수를 호출시킨다.
AnimInstance->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
}
void AABCharacter::Attack()
{
//어택중이 켜져있다면 리턴
if (IsAttacking) return;
...
IsAttacking = true;
}
void AABCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterruped)
{
IsAttacking = false;
}
ABCharacter.cpp 추가사항.
빌드 시, 플레이어에 IsAttacking이 추가된다.
ABCharacter.h에 다음 변수를 추가한다
UPROPERTY()
class UABAnimInstance* ABAnim;
void AABCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
//OnMontageEnded는 AnimInstance 기본 변수이다.몽타주가 끝났을 때 AttackMontageEnded 함수를 호출시킨다.
ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
}
void AABCharacter::Attack()
{
//어택중이 켜져있다면 리턴
if (IsAttacking) return;
ABAnim->PlayAttackMontage();
IsAttacking = true;
}
기존에 auto 변수로 만든 변수 대신 ABAnim 변수에 값을 집어넣도록 수정.
Attack에서 매번 Get으로 받아오지 않아도 되므로 좀 더 비용을 줄일 수 있다.
애니메이션 노티파이
애니메이션이 재생되는 특정 타이밍에 애님 인스턴스에게 신호를 보내는 기능.
애니메이션/몽타주 둘다 가능하다.
새 몽타주 섹션의 이름은 Attack2,Attack3,Attack4이다.
각 애니메이션의 시작점으로 드래그 시킨다.하지만 이렇게해도
게임을 실행시켜보면 애니메이션이 연속적으로 나온다.
전부 독립시킬경우 게임에서는 1타만 사용하고 끝난다.
NextAttackCheck는 다음 공격으로 넘어갈 타이밍을 정해주는 역할.
마지막 타는 연타할 구간이 없으므로 4콤보일시 3개만 필요하다.
AttackHitCheck는 공격 판정이 나올 타이밍이다.
반드시 NextAttackCheck보다 먼저 있어야만 데미지 판정이 무시되지 않고 다음으로 넘어갈 수 있다.
각 노티파이 클릭 후 Motion Tick Type을 Branching Point로 할것.
Branching Point - 해당 프레임에 즉각적으로 반응한다.
Queued - 타이밍에 민감하지 않은 사운드,이펙트 등에 쓰인다.
#include "ArenaBattle.h"
#include "Animation/AnimInstance.h"
#include "ABAnimInstance.generated.h"
//다음 공격 체크 딜리게이트
DECLARE_MULTICAST_DELEGATE(FOnNextAttackCheckDelegate);
//공격 체크용 딜리게이트
DECLARE_MULTICAST_DELEGATE(FOnAttackHitCheckDelegate);
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
...
void JumpToAttackMontageSection(int32 NewSection); //다음 어택 몽타주로 이동
public:
FOnNextAttackCheckDelegate OnNextAttackCheck;
FOnAttackHitCheckDelegate OnAttackHitCheck;
private:
UFUNCTION()
void AnimNotify_AttackHitCheck();
UFUNCTION()
void AnimNotify_NextAttackCheck();
FName GetAttackMontageSectionName(int32 Section);
...
}
ABAnimInstance.h 추가사항. Delegate를 선언후 변수로 선언한다.
(중요)AnimNotify_@@@@() 함수명은,
@@@@부분이 애님 노티파이의 이름이다.이건 언리얼에서 정한 규칙이고,
이렇게 이름을 지었기 때문에 애니메이션에서 해당 노티파이의 정보를 받아오는 것이다.
void UABAnimInstance::JumpToAttackMontageSection(int32 NewSection)
{
//AnimInstance 기본 함수. 받은 텍스트에 해당하는 섹션을 찾아 실행시켜준다.
Montage_JumpToSection(GetAttackMontageSectionName(NewSection), AttackMontage);
}
void UABAnimInstance::AnimNotify_AttackHitCheck()
{
OnAttackHitCheck.Broadcast();
}
void UABAnimInstance::AnimNotify_NextAttackCheck()
{
OnNextAttackCheck.Broadcast();
}
FName UABAnimInstance::GetAttackMontageSectionName(int32 Section)
{
// Attack1 등의 텍스트로 변환
return FName(*FString::Printf(TEXT("Attack%d"), Section));
}
ABAnimInstance.cpp 추가사항
BroadCast는 Delegate에 등록된 함수를 호출한다.
이제 BroadCast로 부를 함수를 ABCharacter에서 입력한다
private:
...
void AttackStartComboState(); //공격이 시작 될 때 불러주는 함수
void AttackEndComboState(); //공격이 종료 될 때 불러주는 함수
private:
...
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
bool CanNextCombo; // 다음 콤보로 진행이 가능한가?
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
bool IsComboInputOn; // 콤보가 입력되었는가?
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
int32 CurrentCombo; // 현재 진행중인 콤보의 번호
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
int32 MaxCombo; // 최대치 콤보 개수
...
}
ABCharacter.h 추가사항
AABCharacter::AABCharacter()
{
//콤보는 최대 4회까지
MaxCombo = 4;
AttackEndComboState();
}
void AABCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
//OnMontageEnded는 AnimInstance 기본 변수이다.몽타주가 끝났을 때 AttackMontageEnded 함수를 호출시킨다.
ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
//람다식
//NextAttackCheck가 불러질때 해당 함수를 실행시킨다
ABAnim->OnNextAttackCheck.AddLambda([this]() -> void {
CanNextCombo = false;
//불러지기 전에 버튼이 눌렸다면
if (IsComboInputOn)
{
//다음 콤보로의 준비
AttackStartComboState();
ABAnim->JumpToAttackMontageSection(CurrentCombo);
}
});
}
void AABCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterruped)
{
IsAttacking = false;
AttackEndComboState();
}
void AABCharacter::Attack()
{
//1타가 나간 이후부터 Attack 명령이 내려진다면
if (IsAttacking)
{
//다음 콤보가 가능할때 입력 true
if (CanNextCombo)
IsComboInputOn = true;
}
else
{
//첫 공격에 한해서만 작동
AttackStartComboState();
ABAnim->PlayAttackMontage();
ABAnim->JumpToAttackMontageSection(CurrentCombo);
IsAttacking = true;
}
}
// 새 콤보가 시작 될 때
void AABCharacter::AttackStartComboState()
{
CanNextCombo = true;
IsComboInputOn = false;
//1부터 MaxCombo사이의 값으로 조정하여 집어넣어준다.
CurrentCombo = FMath::Clamp<int32>(CurrentCombo + 1, 1, MaxCombo);
}
//콤보가 끝날때,혹은 초기화
void AABCharacter::AttackEndComboState()
{
IsComboInputOn = false;
CanNextCombo = false;
CurrentCombo = 0;
}
ABCharacter.CPP 추가사항
점프 모션 갱신
ABAnimInstance.h에 다음 변수 추가
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Pawn, Meta = (AllowPrivateAccess = true))
bool IsJumpStart;
void AABCharacter::JumpStart()
{
UABAnimInstance* animins = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
if (animins)
{
animins->JumpStart(true);
}
//ACharacter::Jump();
}
기존 점프 스타트 함수를 GetAnimInstance의 JumpStart 변수를 실행시키는거로 재조정한다.
void JumpStart(bool bStart) { IsJumpStart = bStart; };
ABAnimInstance.h
//점프시작 체크용 딜리게이트
DECLARE_MULTICAST_DELEGATE(FOnJumpStartCheckDelegate);
FOnAttackHitCheckDelegate OnAttackHitCheck;
UFUNCTION()
void AnimNotify_JumpStartCheck();
AbAnimInstance.cpp
void UABAnimInstance::AnimNotify_JumpStartCheck()
{
OnJumpStartCheck.Broadcast();
}
ABCharacter.cpp
void AABCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
....
//점프시작
ABAnim->OnJumpStartCheck.AddLambda([this]() -> void {
//신호가 들어오면 점프를 발동시키고 bool값을 꺼준다.
ACharacter::Jump();
ABAnim->JumpStart(false);
});
}
코드에 적진 않았지만, 점프력을 철권캐릭터에 맞게 500정도로 낮춰보았다.
주의! 코드상에서 처리하는것이 아니라 BroadCast로 받아오는 것이어서 그런지
즉각적으로 반응하고 코드를 실행하지 않는다. 그래서 너무 늦게 BroadCast를 발동시키면
애니메이션이 꼬일수 있다.
예를 들면 이미 점프 판정이 나올시점에, 점프가 끝나는 애니메이션의 시작인
IsInAir이 false로 취급(아직 땅에 있으니까)되어애니메이션이 동작하지 않는 상황등이 생긴다.아쉬운 부분이다.
'UE4 - C++' 카테고리의 다른 글
UE4 - 충돌 설정과 데미지 전달 (0) | 2021.09.30 |
---|---|
UE4 - 잔상 효과 만들기(SPAWN/TIME LINE/POSEABLEMESH) (0) | 2021.09.24 |
UE4 - 점프기능 구현 (0) | 2021.08.31 |
UE4 - 일인칭 슈팅 C++ 튜토리얼 (0) | 2021.07.23 |
UE4 - 플레이어 입력 및 폰 (0) | 2021.07.21 |