본문 바로가기

언리얼C++게임개발/08.애니메이션 시스템 활용

애니메이션 시스템 활용 - 애니메이션 몽타주

캐릭터가 공격 입력을 누를때 마다 연속된 모션으로 공격하도록 애니메이션 기능을 구현해 본다.

스테이트 모신을 사용해 구현 가능하지만 설계가 복잡해 진다.

언리얼엔진은 스테이트 머신의 확장없이 특정 상황에서 원하는 애니메이션을 발동시키는 애니메이션 몽타주라는 기능을 제공한다. 이를 이용해 콤보공격을 구현하겠다.

애니메이션 폴더위를 우클릭하고 Animation Montage를 선택하고 원하는 스켈레톤을 선택하거나

만들고 싶은 스켈레톤을 열어 Create Asset 에서 선택해도 된다.

에디터가 열리면 기본으로 Default라는 이름의 섹션이 있다. Detail에서 이름을 Attack1으로 변경하

오른쪽 Asset Browser에서 WarriorAttack1 2 3 4를 끌어다 놓는다.

자동으로 엇갈리면서 배치된다. 플레이해보면 4개가 연달아 플레이된다.

배치한 애니메이션을 선택해 디테일에서 공격이 끝나는 지점을 설정해준다 약 0.56, 0.67, 0.67, 1.5 정도이다. 애니메이션의 길이도 자동 조절된다.

이건 연습용이므로 아래를 바로 해도 된다.

 

 

ABAnimInstance.h

PlayAttackMontage()함수와 private: 마지막에 UAnimMontage* AttackMontage;를 추가한다.

VisibleDefaultOnly는 클래스의 기본값을 담당하는 블루프린트 편집화면에서만 보인다.

public:
	....
	void PlayAttackMontage();

private:
	....
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	UAnimMontage* AttackMontage;

 ABAnimInstance.cpp

방금 저장한 몽타지를 로딩해  AttackMontage에 연결해준다. 블루프린트에서 연결해줘도 무방하다. 여기서는 아직 블루프린트가 없지만. 액터 같으면 레벨에 없으면 연결이 안되는데 이건 액터가 아니라 되는것 같다. CDO인지도 모르겠다.

로딩된 AttackMontage는 Montage_Play()함수로 플레이 할 수 있다.2번째 매개변수는 재생속도이다.

static ConstructorHelpers::FObjectFinder<UAnimMontage> ATTACK_MONTAGE(
	TEXT("/Game/Book/Animations/SK_Mannequin_Skeleton_Montage.SK_Mannequin_Skeleton_Montage"));
if (ATTACK_MONTAGE.Succeeded())
{
    AttackMontage = ATTACK_MONTAGE.Object;
}

void UABAnimInstance::PlayAttackMontage()
{
	if(!Montage_IspPlaying(AttackMontage))
		Montage_Play(AttackMontage, 1.0f);
}

재생 명령을 줘도 블루프린트에서 이를 재생하려면 DefaultSlot 노드를 추가해줘야한다. Montage는 이 슬롯에서 재생된다.

Attack를 위한 InputAction을 프로젝트프리셋 Input에서 하나 만들어주고 

AABCharacter.cpp

Attack액션와 Attack()함수를 BindAction해준다

void AABCharacter::AABCharacter()
{
...
	Isattacking = false;
}

void AABCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
    .....
	PlayerInputComponent->BindAction(TEXT("Attack"), EInputEvent::IE_Pressed, 
    		this, &AABCharacter::Attack);
}
            
            
void AABCharacter::Attack()
{
	auto AnimInstance = Cast<ABAnimInstance>(GetMesh()->GetAnimInstance());
    if(AnimInstance)
    	AnimInstance->PlayAttackMontage();
}

빌드후 플레이하면 잘 실행된다.

 

몽타주 설정

2023-01-12 공격중 이동불가 설정

먼저 애니메이션 몽타주를 만들어주자

워리어 캐릭터 애니메이션을 들어간 후 좌측 상단 Create Asset  Anim Montage 를 눌러 몽타주를 만들어준다

완료했다면, Warrior Attack 1~4를 넣어주고, 각각의 애니메이션마다 우클릭 후 New Montage Section으로 섹션을 나눠준다

(몽타주를 만드는 모습)

이후 오른쪽 하단에 있는 몽타주색션에서 연결되어 있는 링크를 모두 해제시켜준다

(완료된 모습)


노티파이 설정

몽타주를 다 나눴다면, 이제 노티파이를 통해 각 모션의 공격이 중앙에 위치하는 지점과
Idle 자세로 돌아가는 지점의 노티파이를 남겨둘 것이다

2가지 노티파이 트랙을 만들고, 하나는 AttackHitCheck 노티파이를, 하나는 NextAttackCheck 노티파이를 만든다

이때 노티파이의 Tick 타입을 더욱 빠른 반응속도를 가진 Branching Point로 바꿔준다

AttackHitCheck 노티파이를 통해 다음에 할 충돌처리를 할 예정이고,
NextAttackCheck 이전에 공격을 누른다면 다음 공격 모션이 나가도록 구현을 할 예정이다

마지막으로, 공격 모션이 어디서든 나가게 하기 위해서, 애니메이션 블루 프린트에서 슬롯을 추가하여 연결해주도록 한다

SlotDefaultSlot 을 생성해 BaseAction과 OutputPose 사이를 연결해준다


Attack 구현

다음으로는 Attack 함수를 인풋에 설정해주고, 캐릭터에 연결하는 과정이 필요하다
이 과정은 그동안 많이 했기 때문에 잠시 후 Attack 함수 부분만 짚도록 하겠다

이제 공격 입력 시 현재 상태를 받아 다음 공격이 가능한지 여부를 확인하고, 가능하다면 다음 공격이 나가는 방식의 코드를 통해
다단 공격을 만들 것이다

ABAnimInstance.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "Animation/AnimInstance.h"
#include "ABAnimInstance.generated.h"

//여러개를 엮을 수 있는 델리게이트 선언
DECLARE_MULTICAST_DELEGATE(FOnNextAttackCheckDelegate);
DECLARE_MULTICAST_DELEGATE(FOnAttackHitCheckDelegate);

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	
public:

...

	void PlayAttackMontage();
	void JumpToAttackMontageSection(int32 NewSection);

	FOnNextAttackCheckDelegate OnNextAttackCheck;
	FOnAttackHitCheckDelegate  OnAttackHitCheck;

private:

...

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	UAnimMontage* AttackMontage;

	UFUNCTION()
	void AnimNotify_AttackHitCheck();

	UFUNCTION()
	void AnimNotify_NextAttackCheck();

	FName GetAttackMontageSectionName(int32 Section);
};

ABAnimInstance.h 파일에 추가한 부분은 다음과 같다

  • 델리게이트 2가지(OnNextAttackCheck, OnAttackHitCheck)
  • 몽타주를 실행시킬 PlayAttackMontage()
  • 다음 몽타주 색션을 실행시킬 JumpToAttackMontageSection(int32 NewSection)
    (밑에 GetAttackMontageSectionName과 함께 사용해 몽타주 이름을 받을 예정(Attack1, Attack2 등)
  • AnimMontage 변수
  • Notify 를 실행시키는 함수 AnimNotify_ 시리즈

언리얼 엔진에서 Anywhere 키워드는 DefaultsOnly와 InstanceOnly로 나뉜다
DefaultsOnly 는 블루프린트 편집화면에서만 보여지고
InstanceOnly 는 뷰포트에서만 보여진다

  • 블루프린트와 연동이 되는 델리게이트는 다이내믹 델리게이트라 명하고 UFUNCTION 키워드를 사용한다

ABAnimInstance

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABAnimInstance.h"

UABAnimInstance::UABAnimInstance()
{
	CurrentPawnSpeed = 0.0f;
	IsInAir = false;

	static ConstructorHelpers::FObjectFinder <UAnimMontage> ATTACK_MONTAGE
	(TEXT("/Game/InfinityBladeWarriors/Character/Animations/Warrior_Montage.Warrior_Montage"));

	if (ATTACK_MONTAGE.Succeeded())
		AttackMontage = ATTACK_MONTAGE.Object;
}

...

void UABAnimInstance::PlayAttackMontage()
{
	Montage_Play(AttackMontage, 1.0f);
}

void UABAnimInstance::JumpToAttackMontageSection(int32 NewSection)
{
	Montage_JumpToSection(GetAttackMontageSectionName(NewSection), AttackMontage);
}

void UABAnimInstance::AnimNotify_AttackHitCheck()
{
	//Broadcast = 델리게이트 안에 있는 모든 함수 실행
	OnAttackHitCheck.Broadcast();
}

void UABAnimInstance::AnimNotify_NextAttackCheck()
{
	OnNextAttackCheck.Broadcast();
}

FName UABAnimInstance::GetAttackMontageSectionName(int32 Section)
{
	return FName(*FString::Printf(TEXT("Attack%d"), Section));
}

UE5에서는 Broadcast 를 통해 델리게이트 안에 있는 모든 함수를 실행 가능하다

ABCharacter.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "GameFramework/Character.h"
#include "ABCharacter.generated.h"

UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{

...

	void Attack();

	UFUNCTION()
	void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);

	void AttackStartComboState();
	void AttackEndComboState();

	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	bool IsAttacking;

	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;
	
	UPROPERTY()
	class UABAnimInstance* ABAnim;

};

ABCharacter.h 파일에는

  • Attack 함수
  • 몽타주 종료 시점을 체크하는 OnAttackMontageEnded 함수
  • 공격 시작 시점의 상태를 설정해주는 함수 AttackStartComboState()
  • 공격 종료 시점의 상태를 설정해주는 함수 AttackEndComboState()
  • 공격중인지를 확인하는 변수 IsAttacking
  • 다음 단계 공격이 가능한지 확인하는 변수 CansNextCombo
  • 다음 단계 공격이 입력되었는지 확인하는 변수 IsComboInputOn
  • 현재 콤보 수를 나타내는 변수 CurrentCombo
  • 최대 콤보수를 받는 변수 MaxCombo
  • 애님 인스턴스 변수 ABAnim
    을 추가해준다

ABCharacter.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABCharacter.h"
#include "ABAnimInstance.h"

// Sets default values
AABCharacter::AABCharacter()
{
...

	MaxCombo = 4;
	AttackEndComboState();
}

...

void AABCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());

	ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);

	ABAnim->OnNextAttackCheck.AddLambda([this]() -> void
		{
			CanNextCombo = false;

			if (IsComboInputOn)
			{
				AttackStartComboState();
				ABAnim->JumpToAttackMontageSection(CurrentCombo);
			}
		});
}

...

void AABCharacter::Attack()
{
	if (IsAttacking)
	{
		if (CanNextCombo)
		{
			IsComboInputOn = true;
		}
	}
	else
	{
		AttackStartComboState();
		ABAnim->PlayAttackMontage();
		ABAnim->JumpToAttackMontageSection(CurrentCombo);
		IsAttacking = true;
	}
}

void AABCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
	IsAttacking = false;
	AttackEndComboState();
}

void AABCharacter::AttackStartComboState()
{
	CanNextCombo   = true;
	IsComboInputOn = false;
	CurrentCombo   = FMath::Clamp<int32>(CurrentCombo + 1, 1, MaxCombo);
}

void AABCharacter::AttackEndComboState()
{
	IsComboInputOn = false;
	CanNextCombo   = false;
	CurrentCombo   = 0;
}

생성자를 통해 MaxCombo를 4로, 공격이 끝난상태로 초기화 해준다
OnNextAttackCheck에는 람다식을 통해 다음 콤보를 이어나가는 함수를 추가해준다


마지막으로 콤보공격이 잘 동작하는지 확인해보자


2023-01-12 공격중 이동불가 설정

현재 상태 그대로 두게 된다면, 공격 모션 중 이동이 가능하여 어색하게 보인다

이를 공격중일때는 이동과 점프가 불가하도록 구현하였다

이동의 경우엔 LeftRight 함수와 UpDown 함수 안에 IsAttacking 변수를 통해 공격중이면 움직이지 못하게 추가를 해주었고

ABCharacter.cpp

void AABCharacter::UpDown(float NewAxisValue)
{
	if (!IsAttacking)
	{
		switch (CurrentControlMode)
		{
		case EControlMode::TPS:
			AddMovementInput(FRotationMatrix(FRotator(0.0f, GetControlRotation().Yaw, 0.0f)).GetUnitAxis(EAxis::X), NewAxisValue);
			break;
		case EControlMode::QUARTERVIEW:
			DirectionToMove.X = NewAxisValue;
			break;
		}
	}
}

void AABCharacter::LeftRight(float NewAxisValue)
{
	if (!IsAttacking)
	{
		switch (CurrentControlMode)
		{
		case EControlMode::TPS:
			AddMovementInput(FRotationMatrix(FRotator(0.0f, GetControlRotation().Yaw, 0.0f)).GetUnitAxis(EAxis::Y), NewAxisValue);
			break;
		case EControlMode::QUARTERVIEW:
			DirectionToMove.Y = NewAxisValue;
			break;
		}
	}
}

Jump 함수의 경우 기본적으로 구현이 되어 있기 때문에 이를 재정의 해주었다

'언리얼C++게임개발 > 08.애니메이션 시스템 활용' 카테고리의 다른 글

콤보공격  (1) 2024.02.14
애니메이션 노티파이  (1) 2024.02.06
델리게이트  (0) 2023.11.26