다양한 기록

[UE Game Framework] #5 Character Combo Action 본문

언리얼 엔진/Unreal Game Framework

[UE Game Framework] #5 Character Combo Action

라구넹 2025. 1. 4. 20:23

애니메이션 몽타주

- 몽타주(Montage) : 이미지 일부를 잘라내 한 화면에서 합성하는 회화 기법

- 애니메이션 클립을 잘라내고 합성한 후 이를 재생하는 애니메이션 기능

- 애니메이션 클립을 모아둔 다수의 섹션으로 구성

- 섹션은 연동 가능, 스크립트를 통해 원하는 섹션으로 건너뛸 수 있음

 

애니메이션 집어넣기

 

섹션 만들기

옆에 화살표 있으면 두 섹션이 연결되어 있다는 의미임

개별적으로 관리하고 싶으면 링크 제거하면 됨

수동으로 연결하려면 AnimInstance 함수 사용

 

1. 공격 입력이 들어올 때 첫번째 몽타주 섹션이 재생되도록 하기

인풋 액션 만들고 매핑 컨텍스트에 추가해줌 (숄더랑 쿼터 둘 다)

 

캐릭터 플레이어에서 액션이랑 함수 추가

액션은 레퍼런스 찾아서 넣어주면 됨

 

베이스에서 ComboActionMontage가 필요한데, 레퍼런스 찾는 거 솔직히 비효율적임

블루프린트에서 설정

 

블루프린트 들어가면 이게 있는데

 

이렇게 설정해주면 됨 (이래서 블루프린트에 리드 라이트 준 것)

 

그리고 애니메이션 블루프린트에 몽타주를 재생할 수 있는 슬롯을 주어야 함

이러면 몽타주 애니메이션이 기존 애니메이션을 덮어써서 나가게 함

몽타주가 플레이되지 않으면 기존 애니메이션이 나감

 

그리고 블루프린트에서 변경이 생겼으니 게임 모드에서 레퍼런스 바꿔줘야 함

 

공격 잘 함

* 슬롯 따로 설정해준 거 없는데, 디폴트라 괜찮은듯

 

콤보 공격 기획

- 콤보 정보를 저장한 데이터 애셋 생성 (ArenaBattleCombo)

- 각 콤보마다 입력을 테스트하는 프레임 지정

- 테스트 프레임 전에 입력이 들어오면 다음 몽타주 섹션으로 이어서 재생

- 테스트 프레임보다 입력이 늦으면 해당 섹션을 마저 플레이하고 종료

ABComboActionData 클래스 추가하고 데이터 애셋으로 만듦

총 네가지 속성

몽타주 섹션 이름 지정, 콤보 개수, 프레임 기준 재생 속도, 입력이 사전에 입력되었는지 감지하는 프레임 지정

베이스에서 필요한 거 넣어주고

void AABCharacterBase::ProcessComboCommand()
{
	if ( CurrentCombo == 0 )
	{
		ComboActionBegin();
	}
}

void AABCharacterBase::ComboActionBegin()
{
	// Combo Started
	CurrentCombo = 1;

	// Movement Setting
	GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);

	// Animation Setting
	const float AttackSpeedRate = 1.0f;

	UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
	AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate);
	
	FOnMontageEnded EndDelegate;
	EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
	AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage);
}

void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
	ensure(CurrentCombo != 0);
	CurrentCombo = 0;
	GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}

 

일단 지금까진 공격 중 이동 못하게 막은 정도

 

콤보 액션 데이터 설정

 

타이머 추가

타이머를 발동 시킬 함수, 타이머가 발동되면 입력이 들어왔는지 안들어왔는지를 체크

 

void AABCharacterBase::ProcessComboCommand()
{
	if ( CurrentCombo == 0 )
	{
		ComboActionBegin();
		return;
	}

	if ( !ComboTimerHande.IsValid() )	// 타이밍을 놓쳤거나, 더 이상 진행 못하거나 
	{
		HasNextComboCommand = false;
	}
	else
	{
		HasNextComboCommand = true;
	}
}

void AABCharacterBase::ComboActionBegin()
{
	// Combo Started
	CurrentCombo = 1;

	// Movement Setting
	GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);

	// Animation Setting
	const float AttackSpeedRate = 1.0f;

	UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
	AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate);
	
	FOnMontageEnded EndDelegate;
	EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
	AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage);

	ComboTimerHande.Invalidate();
	SetComboCheckTimer();
}

void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
	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(
			ComboTimerHande, this, &AABCharacterBase::ComboCheck, ComboEffectiveTime, false);
	}
}

void AABCharacterBase::ComboCheck()
{
	ComboTimerHande.Invalidate();
	if ( HasNextComboCommand )
	{
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
		CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, ComboActionData->MaxComboCount);

		FName NextSection = *FString::Printf(TEXT("%s%d"), *ComboActionData->MontageSectionNamePrefix, CurrentCombo);
		AnimInstance->Montage_JumpToSection(NextSection, ComboActionMontage);

		SetComboCheckTimer();
		HasNextComboCommand = false;
	}
}

여기서 EndDelegate 걸어서 콤보 안이어지면 끝내버림

 

타이머 끝나면 ComboCheck 불리는데, 입력 들어온 게 있으면 다음 몽타주 섹션으로 점프

* 입력 들어오면 바로 넘어가는게 아님, 선입력 생각하면 됨

 

 

콤보 안이어져야 몽타주가 끝나니까, 그때 End 불림