내일배움캠프-언리얼

TIL - 79

이시보 2026. 5. 26. 21:51

스킬 데이터 테이블 설계 — 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연) → HitSequence 3개
  • 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: [] }
]

데이터 외 영역에서 잡아야 할 약속

  1. Status GE 정책Has Duration 5s + Stacking Limit 1 + Refresh on Application. GE_StatusBase로 통일
  2. World 액터 BP 규약 — 콜리전 + VFX + 자체 지속시간(self-destroy). 데미지는 외부에서 인플릭트
  3. 노티파이 컨벤션AnimNotify_BurstHit(int32 HitIndex)HitSequence[i] 발동
  4. Projectile 인터페이스Initialize(FBurstHitInstance HitData, ...) 같은 패턴으로 투사체에 정보 전달
  5. DamageEffect의 SetByCaller 키 — 데미지 값 전달용 태그(예: Data.Damage) 약속
  6. 무기 데이터의 PartName 컨벤션 — 검은 "Sword", 방패는 "Shield"로 통일해야 HitSource와 매핑 가능
  7. 반응 시스템DT_ElementReaction 등 별도 데이터 테이블 + GE 적용 이벤트 리스너로 화상↔추위 반응 구현

'내일배움캠프-언리얼' 카테고리의 다른 글

TIL - 81  (0) 2026.05.29
TIL - 80  (0) 2026.05.27
TIL - 78  (0) 2026.05.21
TIL - 77  (0) 2026.05.20
TIL - 76  (0) 2026.05.19