PART 7 - 강의 3/4
동적 쿨다운 시스템
상황에 따른 쿨다운 조절
01
쿨다운 기본 구조
GAS 쿨다운 메커니즘
기본 쿨다운 Effect
// GAS에서 쿨다운은 Duration GameplayEffect로 구현
UCLASS()
class UGE_AbilityCooldown : public UGameplayEffect
{
public:
UGE_AbilityCooldown()
{
DurationPolicy = EGameplayEffectDurationType::HasDuration;
// 기본 쿨다운 시간 (MMC로 오버라이드 가능)
DurationMagnitude = FScalableFloat(5.0f);
// 쿨다운 태그
InheritableOwnedTagsContainer.AddTag(
FGameplayTag::RequestGameplayTag(FName("Cooldown.Skill.Fireball")));
}
};
// 어빌리티에서 쿨다운 Effect 클래스 지정
UCLASS()
class UGA_Fireball : public UGameplayAbility
{
public:
UGA_Fireball()
{
// 쿨다운 Effect 클래스
CooldownGameplayEffectClass = UGE_FireballCooldown::StaticClass();
}
// 쿨다운 적용 (자동 호출됨)
virtual UGameplayEffect* GetCooldownGameplayEffect() const override
{
return CooldownGameplayEffectClass->GetDefaultObject<UGameplayEffect>();
}
// 쿨다운 태그 (CanActivate 체크용)
virtual const FGameplayTagContainer* GetCooldownTags() const override
{
return &CooldownTags;
}
protected:
UPROPERTY(EditDefaultsOnly)
FGameplayTagContainer CooldownTags;
};
02
동적 쿨다운 계산
MMC로 쿨다운 시간 조절
Cooldown Reduction MMC
// 쿨다운 감소 스탯을 반영한 MMC
UCLASS()
class UMMC_DynamicCooldown : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UMMC_DynamicCooldown()
{
// 쿨다운 감소 어트리뷰트 캡처
CooldownReductionDef.AttributeToCapture =
UMyAttributeSet::GetCooldownReductionAttribute();
CooldownReductionDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Source;
CooldownReductionDef.bSnapshot = false;
RelevantAttributesToCapture.Add(CooldownReductionDef);
}
virtual float CalculateBaseMagnitude_Implementation(
const FGameplayEffectSpec& Spec) const override
{
// 태그에서 기본 쿨다운 가져오기
const FGameplayTagContainer* Tags = Spec.CapturedSourceTags.GetAggregatedTags();
float BaseCooldown = GetBaseCooldownFromTags(Tags);
// 쿨다운 감소 어트리뷰트 가져오기
float CooldownReduction = 0.f;
GetCapturedAttributeMagnitude(CooldownReductionDef, Spec,
FAggregatorEvaluateParameters(), CooldownReduction);
// 쿨다운 감소 적용 (최대 80% 감소 제한)
float ReductionPercent = FMath::Clamp(CooldownReduction / 100.f, 0.f, 0.8f);
float FinalCooldown = BaseCooldown * (1.f - ReductionPercent);
// 최소 쿨다운 1초
return FMath::Max(FinalCooldown, 1.f);
}
protected:
FGameplayEffectAttributeCaptureDefinition CooldownReductionDef;
float GetBaseCooldownFromTags(const FGameplayTagContainer* Tags) const
{
// 태그별 기본 쿨다운 (DataTable에서 가져올 수도 있음)
if (Tags->HasTagExact(FGameplayTag::RequestGameplayTag(FName("Skill.Ultimate"))))
{
return 60.f; // 궁극기 60초
}
if (Tags->HasTagExact(FGameplayTag::RequestGameplayTag(FName("Skill.Elemental"))))
{
return 15.f; // 원소 스킬 15초
}
return 10.f; // 기본 10초
}
};
03
조건부 쿨다운 감소
특정 상황에서 쿨다운 변경
조건부 쿨다운
// 타겟 처치 시 쿨다운 초기화
void UGA_Execute::OnTargetKilled(AActor* KilledActor)
{
// 처형 스킬로 적 처치 시 쿨다운 즉시 초기화
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
// 이 어빌리티의 쿨다운 태그를 가진 Effect 제거
FGameplayTagContainer CooldownTags = *GetCooldownTags();
ASC->RemoveActiveEffectsWithTags(CooldownTags);
// GameplayCue로 피드백
FGameplayCueParameters CueParams;
K2_ExecuteGameplayCue(
FGameplayTag::RequestGameplayTag(FName("GameplayCue.Ability.CooldownReset")),
CueParams);
}
// 크리티컬 히트 시 쿨다운 감소
void UGA_CriticalStrike::OnCriticalHit()
{
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
// 현재 쿨다운 Effect 찾기
FGameplayEffectQuery Query;
Query.EffectTagQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(
*GetCooldownTags());
TArray<FActiveGameplayEffectHandle> ActiveCooldowns =
ASC->GetActiveEffects(Query);
for (FActiveGameplayEffectHandle& Handle : ActiveCooldowns)
{
// 쿨다운 2초 감소
const FActiveGameplayEffect* ActiveEffect =
ASC->GetActiveGameplayEffect(Handle);
if (ActiveEffect)
{
float CurrentRemaining = ActiveEffect->GetTimeRemaining(
ASC->GetWorld()->GetTimeSeconds());
float NewRemaining = FMath::Max(CurrentRemaining - 2.f, 0.f);
// Duration 수정
ASC->ModifyActiveGameplayEffectDuration(Handle,
NewRemaining - CurrentRemaining);
}
}
}
// 특정 버프 활성 시 쿨다운 가속
UCLASS()
class UMMC_HasteBuffCooldown : public UGameplayModMagnitudeCalculation
{
public:
virtual float CalculateBaseMagnitude_Implementation(
const FGameplayEffectSpec& Spec) const override
{
float BaseCooldown = 10.f;
// Haste 버프가 있으면 30% 감소
const UAbilitySystemComponent* SourceASC =
Spec.GetContext().GetInstigatorAbilitySystemComponent();
if (SourceASC && SourceASC->HasMatchingGameplayTag(
FGameplayTag::RequestGameplayTag(FName("Buff.Haste"))))
{
return BaseCooldown * 0.7f;
}
return BaseCooldown;
}
};
쿨다운 UI 동기화
쿨다운이 동적으로 변경될 때 UI도 즉시 업데이트해야 합니다. OnActiveGameplayEffectDurationChange 델리게이트를 사용하세요.
04
충전식 어빌리티
여러 개의 사용 가능 횟수
Charge 기반 어빌리티
// 충전식 어빌리티 (예: 대시 3회)
UCLASS()
class UGA_ChargeDash : public UGameplayAbility
{
GENERATED_BODY()
public:
UGA_ChargeDash()
{
// 최대 충전 횟수
MaxCharges = 3;
// 충전당 쿨다운
ChargeRecoveryTime = 5.0f;
}
virtual bool CanActivateAbility(...) const override
{
// 현재 충전 횟수 확인
return GetCurrentCharges() > 0 && Super::CanActivateAbility(...);
}
virtual void ActivateAbility(...) override
{
// 충전 소모
ConsumeCharge();
// 대시 로직
PerformDash();
EndAbility(...);
}
protected:
UPROPERTY(EditDefaultsOnly)
int32 MaxCharges;
UPROPERTY(EditDefaultsOnly)
float ChargeRecoveryTime;
int32 GetCurrentCharges() const
{
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
if (!ASC) return 0;
// 충전 어트리뷰트에서 현재 값 가져오기
float Charges = 0.f;
ASC->GetGameplayAttributeValue(
UMyAttributeSet::GetDashChargesAttribute(), Charges);
return FMath::FloorToInt(Charges);
}
void ConsumeCharge()
{
// 충전 감소 Effect 적용
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
ASC->ApplyGameplayEffectToSelf(
ConsumeChargeEffect->GetDefaultObject<UGameplayEffect>(),
1.0f, ASC->MakeEffectContext());
// 충전 회복 타이머 시작 (이미 활성화된 게 없다면)
StartChargeRecoveryIfNeeded();
}
};
// 충전 회복 Effect (Periodic)
UCLASS()
class UGE_ChargeRecovery : public UGameplayEffect
{
public:
UGE_ChargeRecovery()
{
DurationPolicy = EGameplayEffectDurationType::Infinite;
// 주기적으로 충전 회복
Period = 5.0f; // 5초마다
// 충전 +1
FGameplayModifierInfo ChargeModifier;
ChargeModifier.Attribute = UMyAttributeSet::GetDashChargesAttribute();
ChargeModifier.ModifierOp = EGameplayModOp::Additive;
ChargeModifier.ModifierMagnitude = FScalableFloat(1.0f);
Modifiers.Add(ChargeModifier);
}
};
05
쿨다운 UI 표시
실시간 쿨다운 정보
쿨다운 조회 및 UI
// 쿨다운 정보 조회
bool UMyBlueprintLibrary::GetCooldownInfo(
UAbilitySystemComponent* ASC,
TSubclassOf<UGameplayAbility> AbilityClass,
float& TimeRemaining,
float& TotalDuration)
{
if (!ASC) return false;
// 어빌리티의 쿨다운 태그 가져오기
UGameplayAbility* AbilityCDO = AbilityClass->GetDefaultObject<UGameplayAbility>();
const FGameplayTagContainer* CooldownTags = AbilityCDO->GetCooldownTags();
if (!CooldownTags || CooldownTags->Num() == 0) return false;
// 활성 쿨다운 Effect 찾기
FGameplayEffectQuery Query;
Query.EffectTagQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(*CooldownTags);
TArray<float> Durations = ASC->GetActiveEffectsDuration(Query);
TArray<TPair<float, float>> DurationAndRemaining =
ASC->GetActiveEffectsTimeRemainingAndDuration(Query);
if (DurationAndRemaining.Num() > 0)
{
TimeRemaining = DurationAndRemaining[0].Key;
TotalDuration = DurationAndRemaining[0].Value;
return true;
}
return false;
}
// UI 위젯에서 사용
void UAbilitySlotWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
float TimeRemaining, TotalDuration;
if (UMyBlueprintLibrary::GetCooldownInfo(
CachedASC, AbilityClass, TimeRemaining, TotalDuration))
{
// 쿨다운 오버레이 표시
CooldownOverlay->SetVisibility(ESlateVisibility::Visible);
CooldownOverlay->GetDynamicMaterial()->SetScalarParameterValue(
TEXT("Progress"), 1.f - (TimeRemaining / TotalDuration));
CooldownText->SetText(FText::AsNumber(FMath::CeilToInt(TimeRemaining)));
}
else
{
CooldownOverlay->SetVisibility(ESlateVisibility::Collapsed);
}
}
SUMMARY
핵심 요약
- 쿨다운 = Duration Effect: 태그로 식별
- MMC로 동적 계산: 쿨다운 감소 스탯 반영
- 조건부 초기화/감소: 처치, 크리티컬 등 이벤트 연동
- 충전식 어빌리티: Attribute로 충전 횟수 관리
- GetActiveEffectsTimeRemaining: UI용 쿨다운 조회
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: 기본 쿨다운 GE
UGameplayAbility::GetCooldownGameplayEffect()를 오버라이드하여 커스텀 쿨다운 GE를 반환하세요. GetCooldownTimeRemaining()으로 남은 쿨다운을 UI에 표시하세요.
실습 2: 동적 쿨다운 계산
Custom MMC를 사용하여 CooldownReduction 어트리뷰트에 따라 쿨다운 시간이 동적으로 변하는 시스템을 구현하세요. 최소 쿨다운 하한선을 설정하세요.
심화 과제
undefined