Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

공부용 이모저모

UE4 - 애니메이션 몽타주 본문

UE4 - C++

UE4 - 애니메이션 몽타주

불타는버스 2021. 9. 9. 15:56

이득우의 언리얼 C++ - 챕터 8

 

애니메이션 몽타주

캐릭터에 공격을 넣기위해 연속공격을 누르고 공격하는 애니메이션을 만든다.

 

애셋 생성 - 애님 몽타주
경로는 여기에 넣기로 했다

몽타주는 섹션 단위로 애니메이션을 관리한다.

몽타주 : 촬영 화면이나 인쇄된 종이를 떼어붙혀 새로운 장면을 만드는 마술기법

언리얼에서는 애니메이션들을 자르고 붙이는 관리용으로 쓰인다.

 

애니메이션 몽타주 파일은 이렇게 생겼다
몽타주 기본화면. Default 섹션이 자동으로 추가되어 있다.
Default를 클릭하면 디테일에 표기된다.Attack1로 이름 변경
공격 애니메이션 4개를 순서대로 넣으면 연속된 동작으로 실행된다.

모션이 끝난 동작이랑 같이 나오기 때문에 어색하게 이어진다.

디테일란에서 애니메이션 수치를 수정 텡리할 수 있다.

애니메이션을 누르고 끝시간을 조정
이어지는 애니메이션이라면 문제없이 이어진다.하지만 보간은 기본적으로 해주진 않는다.

드래그로 애니메이션 간 순서를 조정 할 수 있다.

프로젝트 세팅 -> 입력 -> 엑션매핑에 Attack을 추가하자.왼쪽 마우스 버튼으로 넣자.
몽타주의 레퍼런스 복사

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 파트.

몽타주를 받아오고, 성공시 만들어놓은 변수에 대입시킨다.

 

컴파일 시 애님블루프린트에 AttackMontage가 대입되어있는걸 확인 할 수 있다.
애님그래프에서 마우스 오른쪽 -> DefaultSlot 생성
defaultSlot을 최종애니메이션 직전에 거치도록 수정한다.

#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이 추가된다.

 

공격버튼을 누르면 IsAttacking이 활성되고, 몽타주가 끝나면 IsAttacking이 false 처리되는걸 확인 할 수 있다.

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으로 받아오지 않아도 되므로 좀 더 비용을 줄일 수 있다.

 

애니메이션 노티파이

애니메이션이 재생되는 특정 타이밍에 애님 인스턴스에게 신호를 보내는 기능.

애니메이션/몽타주 둘다 가능하다.

 

노티파이 트랙이 있는 줄에서 마우스 오른쪽을 클릭하면 노티파이추가가 뜬다. 새 노티파이 생성 후 이름은 AttackHitCheck로
공격의 히트박스가 나올 타이밍을 정한다.
복사 후 붙혀넣기로 여러개를 만들 수 있다.
맨위 라인에 마우스 오른쪽 클릭 - 새 몽타주 섹션

새 몽타주 섹션의 이름은 Attack2,Attack3,Attack4이다.

각 애니메이션의 시작점으로 드래그 시킨다.하지만 이렇게해도

게임을 실행시켜보면 애니메이션이 연속적으로 나온다.

몽타주 섹션으로 가서 링크를 전부 제거한다.

전부 독립시킬경우 게임에서는 1타만 사용하고 끝난다.

트랙 삽입
NextAttackCheck를 각 구간에 집어넣는다.

NextAttackCheck는 다음 공격으로 넘어갈 타이밍을 정해주는 역할.

마지막 타는 연타할 구간이 없으므로 4콤보일시 3개만 필요하다.

 

AttackHitCheck는 공격 판정이 나올 타이밍이다.

반드시 NextAttackCheck보다 먼저 있어야만 데미지 판정이 무시되지 않고 다음으로 넘어갈 수 있다.

 

틱 타입은 Branching Point로하자

각 노티파이 클릭 후 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;

기존의 Ground->JumpStart의 기준을 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로 취급(아직 땅에 있으니까)되어
애니메이션이 동작하지 않는 상황등이 생긴다.아쉬운 부분이다.