본문 바로가기

공부/이득우의 언리얼 프로그래밍

[Study] Part 2 - 캐릭터 콤보 액션 (5/15)

728x90
반응형

 

 

 

  • 정리
    • 애니메이션 몽타주와 콤보 공격의 구현
      1. 애니메이션 몽타주 활용 방법의 이해
      2. 데이터 에셋을 활용한 콤보 로직의 구현
      3. 언리얼 델리게이트를 사용한 이벤트 콜백의 구현

 

 

  • 애니메이션 몽타주
    • 몽타주(Montage) : 이미지 일부를 잘라내 한 화면에서 합성하는 회화 기법
    • 애니메이션 클립을 잘라내고 합성한 후 이를 재생하는 애니메이션 기능
    • 애니메이션 클립을 모아둔 다수의 섹션으로 구성되어 있음
    • 섹션끼리 연동할 수 있으며, 스크립트를 통해 원하는 섹션으로 건너뛸 수 있음

 

 

 

Animation Montage

  • 애니메이션 몽타주를 통해 세션을 만들고 위 그림 오른쪽 아래를 보면 Montage Sections에 화살표로 연결이 되어 있다.
  • 이는 연결이 되어 있다는 뜻이고, 연결을 끊으려면 화살표를 클릭하여 Remove link를 해주면 된다.

 

 

 

	// Combo Action Section
protected:
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = Animation)
	TObjectPtr<class UAnimMontage> ComboActionMontage;

	void ProcessComboCommand();
  • ABCharacterBase.h
#include "Animation/AnimMontage.h"

// ...

void AABCharacterBase::ProcessComboCommand()
{
	// 스켈레탈 메쉬의 GetAnimInstance 함수를 호출하여 AnimInstance에 대한 포인터 가져오기
	UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
	AnimInstance->Montage_Play(ComboActionMontage);
}
  • ABCharacterBase.cpp
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class UInputAction> AttackAction;

	// Attack
	void Attack();
  • ABCharacterPlayer.h
	// Attack 액션에 대해서 (생성자)
	static ConstructorHelpers::FObjectFinder<UInputAction> InputActionAttackRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Attack.IA_Attack'"));
	if (nullptr != InputActionAttackRef.Object)
	{
		AttackAction = InputActionAttackRef.Object;
	}
	
	
void AABCharacterPlayer::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	
	// ...
	
	EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::Attack);		// 입력 Attack액션이 발동되었을 때, Attack함수 바인딩
}

// Attack
void AABCharacterPlayer::Attack()
{
	ProcessComboCommand();
}
  • ABCharacterPlayer.cpp
// ...

static ConstructorHelpers::FClassFinder<APawn> DefaultPawnClassRef(TEXT("/Script/Engine.Blueprint'/Game/ArenaBattle/Blueprint/BP_ABCharacterPlayer.BP_ABCharacterPlayer_C'"));

// ...
  • ABGameMode.cpp

 

 

 

BP>Animation>Combo Action Montage

  • 플레이 캐릭터도 BP로 변경해준다.
  • ABCharacterBase.h에서 Montage를 멤버변수로 설정해 BP상에서 적용할 수 있도록 해준다.
  • ABCharacterBase.cpp에서 스켈레탈 메쉬의 GetAnimInstance()함수를 호출하여 AnimInstance에 대한 포인터 가져와 준다.
    • UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

 

 

 

  • 콤보 공격의 기획
    • 콤보 정보를 저장한 데이터 에셋의 생성 (ArenaBattleCombo)
    • 각 콤보마다 입력을 테스트하는 프레임을 지정 (마지막 프레임 제외)
    • 테스트 프레임 전에 입력이 들어오면 다음 몽타주 섹션으로 이어서 재생
    • 테스트 프레임보다 입력이 늦으면 해당 섹션을 마저 플레이하고 종료

콤보 공격의 기획

 

protected:
	virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData);		// Data세팅 함수

	UPROPERTY(EditAnywhere,Category = CharacterControl, Meta = (AllowPrivateAccess = "true"))
	TMap<ECharacterControlType, class UABCharacterControlData*> CharacterControlManager;

	// Combo Action Section
protected:
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = Animation)
	TObjectPtr<class UAnimMontage> ComboActionMontage;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class UABComboActionData> ComboActionData;

	void ProcessComboCommand();

	void ComboActionBegin();		// 몽타주가 시작될 때 호출
	void ComboActionEnd(class UAnimMontage* TargetMontage, bool IsProperlyEnded);			// 몽타주가 모두 종료가 됐을 때 호출 / 몽타주에 설정된 델리게이트를 통해서 바로 호출될 수 있도록 파라미터 설정
	void SetComboCheckTimer();		// 타이머를 발동 시킴
	void ComboCheck();				// 타이머가 발동했을 때 입력이 들어왔는지 체크하는 함수

	int32 CurrentCombo = 0;		// 현재 몇번째 콤보가 시작되고 있는지 체크를 위한 (0 = 시작안함, 1이상 = 시작)
	FTimerHandle ComboTimerHandle;
	bool HasNextComboCommand;
  • ABCharacterBase.h

 

 

 

  • 애니메이션 몽타주를 관리할 DataAsset을 만들고, Combo Action에 필요한 멤버 변수와 함수들을 만들어 준다.
    • void ComboActionBegin();
      • 몽타주가 시작될 때 호출되는 함수
    • void ComboActionEnd(class UAnimMontage* TargetMontage, bool IsProperlyEnded);
      • 몽타주가 모두 종료가 됐을 때 호출 / 몽타주에 설정된 델리게이트를 통해서 바로 호출될 수 있도록 파라미터 설정
    • void SetComboCheckTimer();
      • 타이머를 발동 시킴
    • void ComboCheck();
      • 타이머가 발동했을 때 콤보 어택에 맞는 입력이 들어왔는지 체크하는 함수

 

 

 

#include "ABComboActionData.h"								// 에셋에 저장되어 있는 각 프레임 정보, 인덱스가 유효한지 검사를 위한

void AABCharacterBase::ProcessComboCommand()
{
	if (CurrentCombo == 0)
	{
		// 콤보가 시작되면 리턴
		ComboActionBegin();
		return;
	}

	// ComboTimerHandle가 유효한가? 실행중인가?
	if (!ComboTimerHandle.IsValid())
	{
		HasNextComboCommand = false;
	}
	else
	{
		HasNextComboCommand = true;
	}
}

// 콤보 시작
void AABCharacterBase::ComboActionBegin()
{
	// Combo Status
	CurrentCombo = 1;

	// Movement Setting
	GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);		// Move_None으로 설정하면 움직임이 없도록 설정할 수 있음

	// Animation Setting
	const float AttackSpeedRate = 1.0f;

	// 스켈레탈 메쉬의 GetAnimInstance 함수를 호출하여 AnimInstance에 대한 포인터 가져오기
	UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
	AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate);		// 몽타주 재생

	FOnMontageEnded EndDelegate;
	EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
	AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage);		// 바인딩된 함수 EndDelegate와 ComboActionMontage를 넘겨주어 바인딩 된 AABCharacterBase::ComboActionEnd 함수가 실행되도록

	// 콤보 타이머 무효화후, 콤보 타이머 시작
	ComboTimerHandle.Invalidate();
	SetComboCheckTimer();
}

// 콤보 끝
void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
	// Assertion 함수를 사용해서 몽타주가 끝날땐 CurrentCombo가 절때 0이 될 수 없으니, 검증 + 0이 나오면 에러
	ensure(CurrentCombo != 0);
	CurrentCombo = 0;
	GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);			// 다시 움직임 가능하게 변경
}

// 콤보 타이머를 발동시키는 함수
void AABCharacterBase::SetComboCheckTimer()
{
	int32 ComboIndex = CurrentCombo - 1;
	ensure(ComboActionData->EffectiveFrameCount.IsValidIndex(ComboIndex));

	// 콤보가 가능한 타이밍의 시간 정의 로직
	const float AttackSpeedRate = 1.0f;
	float ComboEffectiveTime = (ComboActionData->EffectiveFrameCount[ComboIndex] / ComboActionData->FrameRate) / AttackSpeedRate;

	if (ComboEffectiveTime > 0.0f)
	{
		GetWorld()->GetTimerManager().SetTimer(ComboTimerHandle, this, &AABCharacterBase::ComboCheck, ComboEffectiveTime, false);
	}
}

// 타이머가 발동했을 때 입력이 들어왔는지 체크하는 함수
void AABCharacterBase::ComboCheck()
{
	// 타이머 초기화
	ComboTimerHandle.Invalidate();
	// 입력이 들어왔다면
	if (HasNextComboCommand)
	{
		// AnimInstance 가져오기
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

		CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, ComboActionData->MaxComboCount);

		// 재생할 세션 이름 정의후 해당 애니메이션 몽타주로 점프
		FName NextSection = *FString::Printf(TEXT("%s%d"), *ComboActionData->MontageSectionPrefix, CurrentCombo);
		AnimInstance->Montage_JumpToSection(NextSection, ComboActionMontage);

		// 다시 콤보 타이머 발동
		SetComboCheckTimer();
		HasNextComboCommand = false;
	}
}
  • void AABCharacterBase::ComboActionBegin() 를 통해 콤보가 시작이 되면, 캐릭터의 움직임을 멈추고, 플레이할 애니메이션 몽타주를 가져와 플레이해주며, 콤보가 끝날때의 이벤트를 받는 델리게이트를 콤보가 끝나면 발동할 함수를 바인딩해주면서 설정한다.
  • 설정을 해주고 나면, ComboTimerHandle.Invalidate();를 통해 타이머를 무효화시켜준 뒤, void AABCharacterBase::SetComboCheckTimer()를 통해 콤보에 대한 타이머를 발동시켜준다.
  • 해당 과정에서 타이머가 끝나기전에 입력이 들어왔는지void AABCharacterBase::ComboCheck()에서 처리해 주며, 콤보 액션이 될 수 있도록 시간안에 입력이 들어왔다면 다음 콤보 애니메이션으로 몽타주로 점프하고 void AABCharacterBase::SetComboCheckTimer()다시 콤보 타이머를 발동시켜준다.

 

 

 

타이머가 끝나기 전에 공격 LMB를 입력하면 콤보 애니메이션이 나간다.

 

 

 

 

 

해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
 

이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 | 이득우 - 인프런

청강문화산업대학교에서 언리얼 엔진, 게임 수학, UEFN 게임제작을 가르치고 있습니다. - 이득우의 언리얼 C++ 프로그래밍, 넥슨 코리아 공식 교육 교재 선정 2023 - 스마일게이트 언리얼 프로그래

www.inflearn.com

 

 

 

 

 

 

728x90