본문 바로가기

Unreal Project

SkillCooldownSystemComponent

스킬의 쿨타임을 관리하는 컴포넌트입니다. 쿨다운이 진행중인 스킬들을 Map<SkillTag, CooldownData>형태로 갖고있고, Tick()함수를 통해 해당 스킬들의 쿨타임을 줄이는 방식을 활용했습니다.

 

 

 

유저가 단축키를 누르면 SkillSystemComponent에서 (1) 을 통해 해당 스킬의 쿨타임을 확인합니다.

만약 쿨타임이 진행중이지 않으면 (2) 를 통해 스킬을 활성화 하고 해당 스킬의 쿨타임이 시작됩니다.

 


void USHCooldownSystemComponent::StartCooldown(const FGameplayTag& InSkillTag, float Duration)
{
	if (!ActiveCooldowns.Contains(InSkillTag))
	{
		ActiveCooldowns.Add(InSkillTag, FCooldownData(Duration));
	}

	ActiveCooldowns[InSkillTag].CooldownDuration = Duration;
	ActiveCooldowns[InSkillTag].TimeRemaining = Duration;
}

SkillSystemComponent에 의해서 스킬이 활성화 되고 StartCooldown함수를 호출하게 되면 ActiveCooldowns에 있는지 확인 후 리스트에 없으면 ActiveCooldowns에 추가를 해줍니다.

쿨타임이 진행중인 스킬은 들어올 수 없지만 만약을 대비해 쿨다운이 진행중인 스킬이 들어오게 되면 초기화 되도록 설정했습니다.

 


void USHCooldownSystemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	TArray<FGameplayTag> CooldownsToRemove;

	for (auto& Elem : ActiveCooldowns)
	{
		FCooldownData& CooldownData = Elem.Value;

		if (CooldownData.TimeRemaining > 0.f)
		{
			CooldownData.TimeRemaining -= DeltaTime;
			if (CooldownData.TimeRemaining <= 0.f)
			{
				CooldownData.TimeRemaining = 0.f;
				CooldownsToRemove.Add(Elem.Key);
			}
		}
	}

	if (ActiveCooldowns.Num() == 0)
		return;

	for (const FGameplayTag& SkillTag : CooldownsToRemove)
	{
		ActiveCooldowns.Remove(SkillTag);
	}

	if (OnTick.IsBound() == true)
		OnTick.Broadcast();
}

 

CooldownSystem은 Tick()함수를 돌며 쿨다운이 진행중인 스킬들의 남은 쿨다임 시간을 계산합니다. 인자로 넘어오는 DeltaTime은 직전에 호출된 Tick()함수와 현재 호출된 Tick()함수사이의 경과 시간을 나타냅니다. 따라서 ActiveCooldowns을 반복문을 통해 돌면서 각 스킬의 남은 쿨타임에서 DeltaTime만큼 감소 시켜줍니다. TimeRemaining <= 0이 되면 해당 스킬은 쿨타임이 다 돌았기 때문에 List에서 제거해 줍니다.

 

OnTick delegator로 HUD의 SkillBotBarWidget에 스킬들의 쿨타임 데이터을 넘겨주고, SkillHotBarWidget은 해당 데이터를 통해 ProgressBar의 Persent를 설정합니다.

 


전체 코드

***************************
SHCooldownSystemComponent.h
***************************

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SHCooldownSystemComponent.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDOnTick);

USTRUCT(BlueprintType)
struct SHPROJECT_API FCooldownData
{
	GENERATED_BODY()

public:
	FCooldownData()
		: CooldownDuration(0.f), TimeRemaining(0.f) {}

	FCooldownData(float InCooldownDuration)
		: CooldownDuration(InCooldownDuration), TimeRemaining(0.f) {}

public:
	UPROPERTY(BlueprintReadOnly)
	float CooldownDuration;

	UPROPERTY(BlueprintReadOnly)
	float TimeRemaining;
};

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SHPROJECT_API USHCooldownSystemComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	USHCooldownSystemComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

public:
	void StartCooldown(const FGameplayTag& InSkillTag, float Duration);
	bool IsOnCooldown(const FGameplayTag& InSkillTag) const;
	float GetCooldownRemainingBySkillTag(const FGameplayTag& InSkillTag) const;
	float GetCooldownRemainingPercentBySkillTag(const FGameplayTag& InSkillTag) const;

public:
	FDOnTick OnTick;
private:
	UPROPERTY()
	TMap<FGameplayTag, FCooldownData> ActiveCooldowns;
	
};

 

*****************************
SHCooldownSystemComponent.cpp
*****************************

// Fill out your copyright notice in the Description page of Project Settings.


#include "SkillSystem/SHCooldownSystemComponent.h"

// Sets default values for this component's properties
USHCooldownSystemComponent::USHCooldownSystemComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	// ...
}


// Called when the game starts
void USHCooldownSystemComponent::BeginPlay()
{
	Super::BeginPlay();

	// ...
	
}


// Called every frame
void USHCooldownSystemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	TArray<FGameplayTag> CooldownsToRemove;

	for (auto& Elem : ActiveCooldowns)
	{
		FCooldownData& CooldownData = Elem.Value;

		if (CooldownData.TimeRemaining > 0.f)
		{
			CooldownData.TimeRemaining -= DeltaTime;
			if (CooldownData.TimeRemaining <= 0.f)
			{
				CooldownData.TimeRemaining = 0.f;
				CooldownsToRemove.Add(Elem.Key);
			}
		}
	}

	if (ActiveCooldowns.Num() == 0)
		return;

	for (const FGameplayTag& SkillTag : CooldownsToRemove)
	{
		ActiveCooldowns.Remove(SkillTag);
	}

	if (OnTick.IsBound() == true)
		OnTick.Broadcast();
}

void USHCooldownSystemComponent::StartCooldown(const FGameplayTag& InSkillTag, float Duration)
{
	if (!ActiveCooldowns.Contains(InSkillTag))
	{
		ActiveCooldowns.Add(InSkillTag, FCooldownData(Duration));
	}

	ActiveCooldowns[InSkillTag].CooldownDuration = Duration;
	ActiveCooldowns[InSkillTag].TimeRemaining = Duration;
}

bool USHCooldownSystemComponent::IsOnCooldown(const FGameplayTag& InSkillTag) const
{
	if (GetCooldownRemainingBySkillTag(InSkillTag) > 0.f)
		return true;

	return false;
}

float USHCooldownSystemComponent::GetCooldownRemainingBySkillTag(const FGameplayTag& InSkillTag) const
{
	if (const FCooldownData* CooldownData = ActiveCooldowns.Find(InSkillTag))
	{
		return CooldownData->TimeRemaining;
	}

	return 0.0f;
}

float USHCooldownSystemComponent::GetCooldownRemainingPercentBySkillTag(const FGameplayTag& InSkillTag) const
{
	if (const FCooldownData* CooldownData = ActiveCooldowns.Find(InSkillTag))
	{
		return CooldownData->TimeRemaining / CooldownData->CooldownDuration;
	}

	return 0.f;
}