UE5 전투 시스템 설계 패턴
전투 시스템 아키텍처
섹션 제목: “전투 시스템 아키텍처”입력 → Ability (GAS) → 애니메이션 → HitBox 활성화 ↓ 충돌 감지 → DamageEvent ↓ Target ASC → GameplayEffect (데미지) ↓ Attribute (Health 감소) → 사망 처리히트박스 컴포넌트
섹션 제목: “히트박스 컴포넌트”UCLASS()class UHitboxComponent : public UCapsuleComponent { GENERATED_BODY()
bool bActive = false; TSet<AActor*> HitActors; // 이미 히트한 액터 (한 공격에 중복 방지)
public: UFUNCTION() void OnOverlapBegin(UPrimitiveComponent* Comp, AActor* Other, ...);
void Activate() { bActive = true; HitActors.Empty(); SetCollisionEnabled(ECollisionEnabled::QueryOnly); }
void Deactivate() { bActive = false; SetCollisionEnabled(ECollisionEnabled::NoCollision); }};
void UHitboxComponent::OnOverlapBegin(UPrimitiveComponent* Comp, AActor* Other, ...) { if (!bActive || HitActors.Contains(Other)) return; HitActors.Add(Other);
// GAS를 통한 데미지 적용 FGameplayEventData EventData; EventData.Instigator = GetOwner(); EventData.Target = Other; EventData.EventMagnitude = DamageAmount;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor( Other, FGameplayTag::RequestGameplayTag("Event.HitReact"), EventData);}데미지 GameplayEffect
섹션 제목: “데미지 GameplayEffect”// GE_Damage.cpp — 즉시 체력 감소UCLASS()class UGE_Damage : public UGameplayEffect { GENERATED_BODY() UGE_Damage() { DurationPolicy = EGameplayEffectDurationType::Instant; // Modifier: Health -= Magnitude FGameplayModifierInfo Modifier; Modifier.Attribute = UMyAttributeSet::GetHealthAttribute(); Modifier.ModifierOp = EGameplayModOp::Additive; Modifier.ModifierMagnitude = FScalableFloat(-1.f); // Execution 또는 SetByCaller로 실제 값 결정 Modifiers.Add(Modifier); }};
// 적용 코드void ApplyDamage(UAbilitySystemComponent* TargetASC, float DamageAmount) { FGameplayEffectSpecHandle Spec = SourceASC->MakeOutgoingSpec( UGE_Damage::StaticClass(), Level, SourceASC->MakeEffectContext()); Spec.Data->SetSetByCallerMagnitude( FGameplayTag::RequestGameplayTag("Data.Damage"), DamageAmount); TargetASC->ApplyGameplayEffectSpecToSelf(*Spec.Data);}콤보 시스템
섹션 제목: “콤보 시스템”UCLASS()class UComboComponent : public UActorComponent { GENERATED_BODY()
int32 ComboIndex = 0; bool bCanCombo = false; FTimerHandle ComboResetTimer;
TArray<TSubclassOf<UGameplayAbility>> ComboAbilities;
public: void OnAttackInput() { if (ComboIndex == 0 || bCanCombo) { ExecuteCombo(); } }
void ExecuteCombo() { if (ComboIndex >= ComboAbilities.Num()) ComboIndex = 0;
ASC->TryActivateAbilityByClass(ComboAbilities[ComboIndex]); ComboIndex++; bCanCombo = false;
// 콤보 윈도우 종료 타이머 GetWorld()->GetTimerManager().SetTimer( ComboResetTimer, this, &UComboComponent::ResetCombo, 1.5f); }
// AnimNotify에서 호출: 다음 입력 허용 구간 void OpenComboWindow() { bCanCombo = true; } void CloseComboWindow() { bCanCombo = false; }
void ResetCombo() { ComboIndex = 0; bCanCombo = false; }};막기/회피 시스템
섹션 제목: “막기/회피 시스템”// 막기 상태 태그로 데미지 감소void UDamageExecutionCalculation::Execute_Implementation( const FGameplayEffectCustomExecutionParameters& Params, FGameplayEffectCustomExecutionOutput& OutExecOutput) const {
float Damage = 0.f; Params.AttemptCalculateCapturedAttributeMagnitude(DamageDef, EvalParams, Damage);
// 막기 태그가 있으면 데미지 80% 감소 FGameplayTagContainer TargetTags; Params.GetTargetAbilitySystemComponent()->GetOwnedGameplayTags(TargetTags);
if (TargetTags.HasTag(FGameplayTag::RequestGameplayTag("State.Blocking"))) Damage *= 0.2f;
OutExecOutput.AddOutputModifier( FGameplayModifierEvaluatedData(HealthAttr, EGameplayModOp::Additive, -Damage));}- HitBox는 컴포넌트로 분리, 활성화 구간 제어 + 중복 히트 방지
- 데미지는 GAS
GameplayEffect+SetByCaller로 유연하게 전달 - 콤보는 입력 큐잉 없이 “콤보 윈도우” 플래그로 단순하게 구현
- 막기/회피는 Gameplay Tag로 상태 표현 → Execution에서 감소
- 모든 전투 로직을 GAS Ability 안으로 집중시켜 재사용성 확보