스킬 데이터 테이블 설계 — FSkillCombination 확장
기존 구조체의 한계
USTRUCT(BlueprintType)
struct FSkillCombination : public FTableRowBase
{
GENERATED_BODY()
TMap<FGameplayTag, int32> ElementPattern;
UAnimMontage* AttackMontage;
TSubclassOf<UGameplayEffect> PrimaryEffect;
TSubclassOf<UGameplayEffect> SecondaryEffect;
float DamageMultiplier;
float EffectDuration;
float AoeRadius;
};
- 멀티히트 표현 불가 — WWW는 60% × 5회, AAA는 100% × 3회, FFW는 120% + 60%. 단일 float로는 못 담음
- 공격 형태가 너무 다름 — 내려베기 / 지면 분출 / 투사체(검기) / 돌진 / AoE 다섯 종류
- 검·방패 출처 구분 불가 — FFW는 1타가 검, 2타가 방패. 한 스킬 안에서 손이 바뀜
- 상태 부여 의미가 모호 —
PrimaryEffect/SecondaryEffect가 데미지인지 상태인지 불명확. WWF는 한 타격에 추위+화상 두 개 순차 부여가 필요 EffectDuration은 사실상 무용 — 모든 상태가 5초 고정 → GE 자체에 박는 게 맞음AoeRadius만으로는 부족 — Release(FWA)에만 의미 있고 나머지엔 다른 정보가 필요
최종 구조
1) 두 개의 enum
UENUM(BlueprintType)
enum class EBurstAttackType : uint8
{
Cleave UMETA(DisplayName = "단일 강타 (내려베기)"),
GroundEruption UMETA(DisplayName = "지면 분출 (물기둥/열탕)"),
Projectile UMETA(DisplayName = "투사체 (검기)"),
Dash UMETA(DisplayName = "돌진"),
AreaOfEffect UMETA(DisplayName = "주변 AoE")
};
UENUM(BlueprintType)
enum class EBurstHitSource : uint8
{
Sword, // Weapon_R 계열
Shield, // 방패 어태치 소켓
Body, // 캐릭터 본체 (돌진/AoE)
World // 월드 액터 (지면 분출)
};
2) 타격 단위 구조체 FBurstHitInstance
각 타격이 들고 있어야 할 정보를 한 묶음으로:
USTRUCT(BlueprintType)
struct FBurstHitInstance
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float DamageMultiplier = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
EBurstHitSource HitSource = EBurstHitSource::Sword;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FName SocketOverride = NAME_None;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSoftObjectPtr<UNiagaraSystem> HitVFX;
// 이 타격에서 순차 부여할 상태 GE들 (한 타격 안 다중 상태도 OK)
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<TSubclassOf<UGameplayEffect>> StatusEffects;
};
핵심은 StatusEffects를 스킬 레벨이 아니라 타격(HitInstance) 레벨에 둔 것
3) 재구성된 FSkillCombination
USTRUCT(BlueprintType)
struct FSkillCombination : public FTableRowBase
{
GENERATED_BODY()
// 메타 (UI / 식별)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Meta")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Meta", meta = (MultiLine = true))
FText Description;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Meta")
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Meta", meta = (Categories = "Ability"))
FGameplayTag SkillTag;
// 조합 / 모션
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Element")
TMap<FGameplayTag, int32> ElementPattern;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Motion")
TObjectPtr<UAnimMontage> AttackMontage;
// 공격 형태 (AttackType에 따라 분기, EditCondition으로 가시성 제어)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Attack")
EBurstAttackType AttackType = EBurstAttackType::Cleave;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Attack|Projectile",
meta = (EditCondition = "AttackType == EBurstAttackType::Projectile"))
TSubclassOf<AActor> ProjectileClass;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Attack|Dash",
meta = (EditCondition = "AttackType == EBurstAttackType::Dash", ClampMin = "0.0"))
float DashDistance = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Attack|AoE",
meta = (EditCondition = "AttackType == EBurstAttackType::AreaOfEffect", ClampMin = "0.0"))
float AoeRadius = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Attack|World",
meta = (EditCondition = "AttackType == EBurstAttackType::GroundEruption", ClampMin = "0.0"))
float WorldSpawnDistance = 300.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Attack|World",
meta = (EditCondition = "AttackType == EBurstAttackType::GroundEruption"))
TSubclassOf<AActor> WorldSpawnActorClass;
// 데미지 / 상태 (타격별 시퀀스)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Damage")
TSubclassOf<UGameplayEffect> DamageEffect;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|Damage")
TArray<FBurstHitInstance> HitSequence;
// 연출
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|FX")
TSoftObjectPtr<UNiagaraSystem> CastVFX;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|FX")
TSoftObjectPtr<UNiagaraSystem> HitVFX;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill|FX")
TSoftObjectPtr<USoundBase> CastSound;
};
핵심 설계 결정
① AttackType enum + EditCondition으로 분기
enum으로 타입을 정한 뒤 EditCondition으로 부속 필드의 가시성을 제어
② 멀티히트를 HitSequence 배열로 — float → 구조체 배열로 승격
배열 길이가 곧 히트 수, 각 인덱스가 한 번의 타격을 표현
- AAA(검기 3연) →
HitSequence3개 - FFW(검+방패) → 2개
- WWW(물기둥 안 5히트) → 5개
- FFF(내려베기) → 1개
③ HitSource로 검·방패·몸·월드 출처 구분
- Sword/Shield — 무기 메시의 소켓 기준 (
PartName컨벤션으로 매핑) - Body — 돌진/AoE처럼 캐릭터 본체에서 발산
- World — 물기둥/열탕처럼 월드 좌표에 액터 스폰
④ StatusEffects를 HitInstance에 넣은 이유
한 타격 안에서 여러 상태를 순서대로 부여할 수도, 멀티히트 중 일부 타격에만 부여할 수도 있음
스킬 데이터 매핑 예시
FFW (증기 폭발) — 1타 검 / 2타 방패
AttackType : Cleave
HitSequence : [
{ 1.2, Sword, StatusEffects: [GE_Burn] },
{ 0.6, Shield, StatusEffects: [GE_Cold] }
]
WWW (물기둥 분출) — 단일 액터, 5회 히트
AttackType : GroundEruption
WorldSpawnDistance : 300
WorldSpawnActorClass : BP_WaterPillar
HitSequence : [
{ 0.6, World, StatusEffects: [GE_Cold] },
{ 0.6, World, StatusEffects: [GE_Cold] },
{ 0.6, World, StatusEffects: [GE_Cold] },
{ 0.6, World, StatusEffects: [GE_Cold] },
{ 0.6, World, StatusEffects: [GE_Cold] }
]
스택 없음 + Refresh 정책이라 매번 부여해도 지속시간만 갱신됩니다.
WWF (열탕 분출) — 단일 타격에서 추위→화상 순차
AttackType : GroundEruption
WorldSpawnActorClass : BP_HotWater
HitSequence : [
{ 1.8, World, StatusEffects: [GE_Cold, GE_Burn] }
]
추위 → 화상 순차 부여로 반응 자동 트리거 (이동속도 40% 감소 + 추위 삭제).
AAA (검기 3연) — 투사체
AttackType : Projectile
ProjectileClass : BP_Projectile_SwordSlash
HitSequence : [
{ 1.0, Sword, StatusEffects: [] },
{ 1.0, Sword, StatusEffects: [] },
{ 1.0, Sword, StatusEffects: [] }
]
데이터 외 영역에서 잡아야 할 약속
- Status GE 정책 —
Has Duration 5s+Stacking Limit 1+Refresh on Application.GE_StatusBase로 통일 - World 액터 BP 규약 — 콜리전 + VFX + 자체 지속시간(self-destroy). 데미지는 외부에서 인플릭트
- 노티파이 컨벤션 —
AnimNotify_BurstHit(int32 HitIndex)로HitSequence[i]발동 - Projectile 인터페이스 —
Initialize(FBurstHitInstance HitData, ...)같은 패턴으로 투사체에 정보 전달 DamageEffect의 SetByCaller 키 — 데미지 값 전달용 태그(예:Data.Damage) 약속- 무기 데이터의
PartName컨벤션 — 검은"Sword", 방패는"Shield"로 통일해야HitSource와 매핑 가능 - 반응 시스템 —
DT_ElementReaction등 별도 데이터 테이블 + GE 적용 이벤트 리스너로 화상↔추위 반응 구현