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