본문 바로가기
언리얼 최고/언리얼 c++

이득우의 언리얼 C++ 정리 - 8 (애니메이션 시스템 활용)

by Lee_story_.. 2022. 8. 30.
728x90

 

 

*  아래의 책의 내용을 정리한 글입니다!  *

 

 

ABCharacter.h에 추가

private:
	void Attack();

 

ABCharacter.cpp 에 추가

void AABCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent){
//~~


PlayerInputComponent->BindAction(TEXT("Attack"), EInputEvent::IE_Pressed, this, &AABCharacter::Attack);// 공격
}
//~~


void Attack() {
	ABLOG_S(Warning);
}

 

 

 

 

클릭시 로그가 찍힌다면 성공!

 

이제 애니메이션을 넣어줍시다.

 

ABAnimInstance.h 에 추가

void PlayAttackMontage();

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

 

 

ABAnimInstance.cpp 에 수정 및 추가!

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

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


//~~

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

 

 

 

애니메이션 블루프린트를 아래처럼 바꿔주고

 

 

 

ABCharacter.cpp 수정해주면

    
void Attack() {
	auto AnimInstance = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
    if (nullptr == AnimInstance) return;
    
    AnimInstance->PlayAttackMontage();
}

 

잘 움직이네요 ㅎ

 

 

 

 

 

델리게이트?

위에서 애니메이션을플레이 해보았을때 움직이는 상태에선 플레이 되지 않도록 만들었습니다.

여기서는 계속플레이중인지를 매번 체크하는 방식으로 확인하는데

델리게이트는 플레이가 끝난순간 폰에게 애니메이션이 끝났다고 알려주는 방식으로 좀 더 효율적인 방법입니다!

 

좀 더 넓게 본다면 하나의 작업을 여러 객체가 나눠서 처리할수있도록 하기위한 프로그래밍 설계방법입니다.

 

언리얼에서도 자주사용되며 여러 프로그램에서도 이와같은 방식을 많이 사용합니다.

 

하지만 c++에서는 델리케이트 시스템을 기본적으로 제공하고 있지 않기에 

언리얼에서는 별도의 프레임워크를 사용합니다.

 

 

코드로도 만들어 봅시다.

 

일단 오류코드를 잡아주기위한

 ArenaBattle.h

 

#define ABCHECK(Expr, ...) { if (!(Expr)) { ABLOG(Error, TEXT("ASSERTION : %s"), TEXT("'"#Expr"'")); return __VA_ARGS__;} }

 

 

 

ABCharacter.h

public:
virtual void PostInitializeComponents() override;


private:
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		bool IsAttacking;
        
        UPROPERTY()
		class UABAnimInstance* ABAnim;

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

 

 

ABCharacter.cpp

AABCharacter::AABCharacter()
{
    //~~
    IsAttacking = false;
    
//~~

void AABCharacter::PostInitializeComponents()
{
    Super::PostInitializeComponents();
    
    ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
    ABCHECK(nullptr != ABAnim);
    
    ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
}
//~~

void AABCharacter::Attack()
{
    if (IsAttacking) return;
    
    //~~
    
    IsAttacking = true;
}

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

 

 

다음은 노티파이!

 

특정 타이밍에 애님인스턴스에게 신호를 보내는 함수를 만들수있게 해주는 기능!

 

 

 

 

이처럼 추가후

 

ABAnimInstance.h

private:
	UFUNCTION()
		void AnimNotify_AttackHitCheck();

 

 

ABAnimInstance.cpp

void UABAnimInstance::AnimNotify_AttackHitCheck() {
	ABLOG_S(Warning);
}

 

그리고 시작하면 각 행동마다 로그가 찍히게 됩니다!

 

 

이 기능을 이용해서 콤보공격을 만들어 봅시다!

 

 

아까 위에서 만든 몽타주 상태에서

 

링크 제거를 해 별개의 애님으로 만들어 줍시다.

 

 

여기서 엄청 해매서... 

 

ABCharacter.h

class ARENABATTLE_API AABCharacter : public ACharacter
{


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.cpp

AABCharacter::AABCharacter()
{

    MaxCombo = 4;
    AttackEndComboState();
}



oid AABCharacter::Attack()// 공격!
{
	if (IsAttacking)
	{
		ABCHECK(FMath::IsWithinInclusive<int32>(CurrentCombo, 1, MaxCombo));
		if (CanNextCombo)
		{
			IsComboInputOn = true;
		}
	}
	else
	{
		ABCHECK(CurrentCombo == 0);
		AttackStartComboState();
		ABAnim->PlayAttackMontage();
		ABAnim->JumpToAttackMontageSection(CurrentCombo);
		IsAttacking = true;
	}
}

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



void AABCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
	ABCHECK(nullptr != ABAnim);

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


	ABAnim->OnNextAttackCheck.AddLambda([this]() -> void {
		ABLOG(Warning, TEXT("OnNextAttackCheck"));
		CanNextCombo = false;

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

		});
}



void AABCharacter::AttackStartComboState() // 애님 분할!
{
	CanNextCombo = true;
	IsComboInputOn = false;
	ABCHECK(FMath::IsWithinInclusive<int32>(CurrentCombo, 0, MaxCombo - 1));
	CurrentCombo = FMath::Clamp<int32>(CurrentCombo + 1, 1, MaxCombo);
}

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

 

 

 

ABAnimInstance.h

DECLARE_MULTICAST_DELEGATE(FOnNextAttackCheckDelegate);
DECLARE_MULTICAST_DELEGATE(FOnAttackHitCheckDelegate);

class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{

//+
public:
    void JumpToAttackMontageSection(int32 NewSection);
    
public:
    FOnNextAttackCheckDelegate OnNextAttackCheck;
    FOnAttackHitCheckDelegate OnAttackHitCheck;

private:
    
    UFUCNTION()
    void AnimNotify_AttackHitCheck();
    
    UFUNCTION()
    void AnimNotify_NextAttackCheck();
    
    FName GetAttackMontageSectionName(int32 Section);

}

 

 

ABAnimInstance.cpp

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

void UABAnimInstance::AnimNotify_AttackHitCheck()
{
    OnAttackHitCheck.Broadcast();
}

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

FName UABAnimInstance::GetAttackMontageSectionName(int32 Section)
{
    ABCHECK(FMath::IsWithinInclusive<int32>(Section, 1, 4), NAME_None);
    return FName(*FString::Printf(TEXT("Attack%d"), Section));
}

 

 

여기까지하면 클릭에 따라 콤보공격이 가능해집니다!

 

 

 

 

 

 

 

틀린점이 있다면 댓 달아주세요!

댓글