애니메이션 몽타주와 노티파이를 이용한 다단 공격 구현

유영준·2023년 1월 7일
0
post-thumbnail

오늘은 언리얼 엔진에서 제공하는 애니메이션 몽타주와 애니메이션 노티파이 기능을 통해 다단 공격 시스템을 만들것이다

몽타주 설정

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

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

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

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

(몽타주를 만드는 모습)

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

(완료된 모습)


노티파이 설정

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

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

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

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

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

SlotDefaultSlot 을 생성해 BaseActionOutputPose 사이를 연결해준다


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 키워드는 DefaultsOnlyInstanceOnly로 나뉜다
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 함수의 경우 기본적으로 구현이 되어 있기 때문에 이를 재정의 해주었다

_ABCharacter.cpp_

void AABCharacter::Jump()
{
	if (!IsAttacking)
		ACharacter::Jump();
}
profile
토비폭스가 되고픈 게임 개발자

0개의 댓글