오늘은 언리얼 엔진에서 제공하는 애니메이션 몽타주와 애니메이션 노티파이 기능을 통해 다단 공격 시스템을 만들것이다
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 함수 부분만 짚도록 하겠다
이제 공격 입력 시 현재 상태를 받아 다음 공격이 가능한지 여부를 확인하고, 가능하다면 다음 공격이 나가는 방식의 코드를 통해
다단 공격을 만들 것이다
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 파일에 추가한 부분은 다음과 같다
PlayAttackMontage()
JumpToAttackMontageSection(int32 NewSection)
GetAttackMontageSectionName
과 함께 사용해 몽타주 이름을 받을 예정(Attack1, Attack2 등)언리얼 엔진에서
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 파일에는
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
에는 람다식을 통해 다음 콤보를 이어나가는 함수를 추가해준다
마지막으로 콤보공격이 잘 동작하는지 확인해보자
현재 상태 그대로 두게 된다면, 공격 모션 중 이동이 가능하여 어색하게 보인다
이를 공격중일때는 이동과 점프가 불가하도록 구현하였다
이동의 경우엔 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();
}