본문 바로가기
Unreal 게임 개발/Unreal Tool 활용

Uneal DataAsset으로 Combo System 만들기

by daisy0461 2025. 3. 24.
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ABComboActionData.generated.h"

/**
 *
 */
UCLASS()
class PRACTICE_API UABComboActionData : public UPrimaryDataAsset
{
	GENERATED_BODY()

public:
	UABComboActionData();

	UPROPERTY(EditAnywhere, Category = Name)
	FString MontageSectionNamePrefix;

	UPROPERTY(EditAnywhere, Category = Name)
	uint8 MaxComboCount;

	UPROPERTY(EditAnywhere, Category = Name)
	float FrameRate;

	UPROPERTY(EditAnywhere, Category = ComboData)
	TArray<float> EffectiveFrameCount;
};

다음과 같이 ComboAttack에 사용할 변수를 Data Asset 헤더파일에 정의한다.

MontagePrefix엔

이렇게 Section Name에 Prefix를 적어준다.

MaxComboCount에는 Section의 개수를 적어준다.

FremeRate는 목표하는 Frame Rate를 적어준다. (30으로 했음)

EffectiveFrameCount는 이후에 나오는 코드를 살펴보면 알 수 있을 것이다.

 

void AABCharacterPlayer::Attack()
{
	ProcessComboCommand();
}

 

Attack을 실행(마우스 왼쪽을 클릭하면) 실행하는 함수이다.

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

	if (!ComboTimerHandle.IsValid())
	{
		HasNextComboCommand = false;
	}
	else
	{
		HasNextComboCommand = true;
	}
}

CurrentCombo의 의미는 지금 몇번째 Section을 재생하고 있냐는 것이다.

0은 아직 Combo를 실행하지 않았다는 의미와 동일하기에 ComboActionBeign()으로 Combo를 실행시켜주며

이후 나오겠지만 HasNextComboCommand는 EffectiveFrameCount안에 Attack이 다시 눌려졌는가를 의미한다.

 

void AABCharacterBase::ComboActionBegin()
{
	// Combo Status
	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);

	ComboTimerHandle.Invalidate();
	SetComboCheckTimer();
}

Begin을 보면 CurrentCombo = 1로 1번째 Section을 재생한다는 의미가 된다.

그리고 Move를 못하게 막고 Animation을 재생한다.

EndDelegate는 Montage가 끝날 때 실행되는 함수를 Delegate로 묶어서 ComboActionEnd을 실행하도록 한다.

그리고 원래 실행중이었던 Timer와 충돌이 발생할 수 있끼에 ComboTimerHandle을 유효하지 않게 만들고

SetComboCheckTimer()를 사용한다.

 

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);
	}
}

Index를 확인하는 것이기 때문에 -1을 해서 계산을 하고
ComboEffectiveTime의 수식이 의미하는 바가 일정한 Frame(EffectiveFrameCount)안에 한번 더 입력이 들어왔는지를 확인하기 위해 사용하는 float변수이다.

아래를 보면 ComboTimerHandle로 Timer를 시작하고 ComboCheck를 ComboEffectiveTime이후에 실행한다.

 

void AABCharacterBase::ComboCheck()
{
	ComboTimerHandle.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;
	}
}

ComboCheck는 유효한 ComboTimerHandle를 비유효하게 하고

ProcessComboCommand에 의해 HasNextComboCommand가 true로 변환이 되었다면 다음 Section으로 넘어가는 형식이다.