[Unreal Engine 5] GAS Ranger ProjectileShot 심화 — 스킬 수치 DataTable 연동

2026. 6. 10. 22:42·프로그래밍/개발일지
[UE5/GAS] Skill Stat DataTable 기반 구조 추가 및 ProjectileShot 연동 TIL

작업 개요

스킬별 기본 수치를 코드에 하드코딩하지 않고 DataTable로 외부화하는 기반 구조를 추가했다. FNSAbilityBaseStatRow와 UNSCombatStatComponent를 새로 추가해, Ability에서 AbilityTag + StatTag 기준으로 수치를 조회할 수 있는 흐름을 만들었다.

이번 작업의 최종 검증 대상은 GA_RangerProjectileShot의 ExplosionRadius였다. DataTable 값 변경이 실제 Projectile 스플래시 반경에 반영되는 것을 로그로 확인했다.

구현 범위

CombatStat GameplayTag 추가
FNSAbilityBaseStatRow 구조체
UNSCombatStatComponent 추가
AbilityTag+StatTag 캐시 구조
PlayerState 조회 흐름 연결
ASC CombatStat 래퍼 함수
GA_SkillBase 편의 함수 추가
ProjectileShot ExplosionRadius 연동
DataTable 연동 로그 검증
증강 역할 분리 설계 정리
Damage 구조 후속 설계 정리
StackGE vs CombatStatModifier 분리

FNSAbilityBaseStatRow 구조

스킬별 기본 수치를 Row 단위로 관리하기 위해 FTableRowBase를 상속하는 구조체를 추가했다. AbilityTag와 StatTag 조합이 캐시 키가 된다.

C++ · FNSAbilityBaseStatRow
USTRUCT(BlueprintType)
struct FNSAbilityBaseStatRow : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere)
    FGameplayTag AbilityTag;      // ex) Ability.Ranger.ProjectileShot

    UPROPERTY(EditAnywhere)
    FGameplayTag StatTag;         // ex) CombatStat.ExplosionRadius

    UPROPERTY(EditAnywhere)
    float BaseValue = 0.f;

    UPROPERTY(EditAnywhere)
    bool bModifiable = true;      // 증강 / 파츠로 변경 가능 여부
};
스킬 수치 데이터 계층 구조 Skill Definition DataAsset 정체성 · UI · 아이콘 · AbilityClass · 설명 · 에셋 참조 이번 작업 AbilityBaseStats DataTable Damage · Cooldown · ExplosionRadius ProjectileSpeed · FireRate · MaxAmmo 후속 작업 Augment Modifier Table 증강 · 파츠 · 영구 강화로 변경되는 수치 Delta DataAsset DataTable DataTable

▲ 스킬 수치 데이터 계층 구조 — AbilityBaseStats DataTable이 이번 작업의 대상

AbilityBaseStats DataTable 구성

Row Name을 AbilityTag__StatTag 조합으로 짓는 규칙을 사용했다. 에디터에서 어떤 스킬의 어떤 수치인지 한눈에 파악할 수 있다.

💡 Effect.Damage.Base 분리 이유

CombatStat.Damage는 DataTable/증강으로 계산되는 스킬 공격력 스탯. Effect.Damage.Base는 GE에 SetByCaller로 전달하는 피해 입력값. 두 개념이 다르므로 태그도 분리했다.

변경 전 ExplosionRadius = 400.000000
DataTable ExplosionRadius 400
▶ 변경 전 실행 영상
변경 후 ExplosionRadius = 800.000000
DataTable ExplosionRadius 800
▶ 변경 후 실행 영상

bModifiable 컬럼으로 증강/파츠 변경 가능 여부를 DataTable에서 함께 관리한다. CombatStat.ProjectileSpeed는 false로 설정해 게임플레이 중 변경되지 않도록 했다.

UNSCombatStatComponent 캐싱 구조

컴포넌트 초기화 시 DataTable을 한 번만 읽어 TMap 캐시에 저장한다. 이후 Ability 조회는 캐시만 참조하므로 DataTable 접근 비용이 없다.

C++ · UNSCombatStatComponent
// 초기화 시 DataTable → 캐시 빌드
void UNSCombatStatComponent::BuildCache()
{
    if (!IsValid(AbilityBaseStatsTable)) { return; }

    TArray<FNSAbilityBaseStatRow*> Rows;
    AbilityBaseStatsTable->GetAllRows(TEXT(""), Rows);

    for (const FNSAbilityBaseStatRow* Row : Rows)
    {
        if (!Row->AbilityTag.IsValid() || !Row->StatTag.IsValid()) { continue; }
        StatCache.Add({Row->AbilityTag, Row->StatTag}, Row->BaseValue);
        ModifiableCache.Add({Row->AbilityTag, Row->StatTag}, Row->bModifiable);
    }
}

// 기본 수치 조회
bool UNSCombatStatComponent::TryGetBaseAbilityStat(
    const FGameplayTag& AbilityTag, const FGameplayTag& StatTag, float& OutValue) const
{
    if (const float* Found = StatCache.Find({AbilityTag, StatTag}))
    {
        OutValue = *Found;
        return true;
    }
    return false;
}

PlayerState / ASC / SkillBase 조회 흐름

개별 Ability가 PlayerState를 직접 캐스팅하지 않도록 ASC 래퍼와 GA_SkillBase 편의 함수 계층을 추가했다.

CombatStat 조회 흐름 GA_SkillBase GetBaseStatOrDefault TryGetBaseStat NSASC 래퍼 PlayerState 직접 캐스팅 없이 위임 ANSPlayerState CombatStatComponent 소유 CombatStat Component TMap Cache

▲ Ability → ASC 래퍼 → PlayerState → CombatStatComponent 조회 흐름

C++ · UNSAbilitySystemComponent (래퍼)
bool UNSAbilitySystemComponent::TryGetBaseAbilityStat(
    const FGameplayTag& AbilityTag, const FGameplayTag& StatTag, float& OutValue) const
{
    const auto* PS = GetOwnerPlayerState<ANSPlayerState>();
    if (!IsValid(PS)) { return false; }
    return PS->GetCombatStatComponent()->TryGetBaseAbilityStat(AbilityTag, StatTag, OutValue);
}
C++ · UGA_SkillBase (편의 함수)
// 실패 시 DefaultValue 반환
float UGA_SkillBase::GetBaseAbilityStatOrDefault(
    const FGameplayTag& StatTag, float DefaultValue) const
{
    float OutValue = DefaultValue;
    if (const auto* ASC = GetNSAbilitySystemComponent())
    {
        ASC->TryGetBaseAbilityStat(GetAbilityTag(), StatTag, OutValue);
    }
    return OutValue;
}

ProjectileShot ExplosionRadius 연동

C++ · GA_RangerProjectileShot
// DataTable 조회 실패 시 DefaultExplosionRadius fallback 사용
const float ExplosionRadius = GetBaseAbilityStatOrDefault(
    TAG_CombatStat_ExplosionRadius, DefaultExplosionRadius);

UE_LOG(LogNS, Log, TEXT("[ProjectileShot] ExplosionRadius: %.1f"), ExplosionRadius);

Projectile->InitProjectile(ExplosionRadius);

✅ 로그 검증 결과

DataTable의 ExplosionRadius 값을 400 → 800으로 변경했을 때, 다음 실행에서 로그가 [ProjectileShot] ExplosionRadius: 800.0으로 바뀌는 것을 확인했다.

증강 시스템 역할 분리 설계

기존 StackEffectClass GE와 새로 설계 중인 CombatStatModifier의 역할을 명확히 분리하기로 했다. 같은 수치를 양쪽에 넣으면 중복 적용 위험이 있다.

StackEffectClass GE
  • MaxHealth
  • Defense
  • MoveSpeed
  • 전역 Attribute 변화
CombatStatModifier
  • ProjectileShot Damage
  • ProjectileShot ExplosionRadius
  • Cooldown / FireRate
  • MaxAmmo
  • 스킬별 수치 변화

⚠ 후속 작업 예정

Augment Modifier Row / DataTable 구조를 추가하고, AugmentInventory의 보유 증강 + 스택 정보 기준으로 최종값을 계산하는 흐름을 연동한다.

Damage 구조 후속 설계 정리

Damage 리팩터링은 이번 작업에서 진행하지 않고 후속으로 분리했다.

Damage 처리 흐름 CombatStat.Damage DataTable / 증강 / 파츠 영구 강화로 계산 Effect.Damage.Base SetByCaller로 GESpec에 전달 Damage Meta Attr GE Execution이 계산한 임시값 Health 실제 복제되는 결과 Attribute

▲ Damage 처리 흐름 — 이번 작업에서는 구현 없이 방향만 정리

테스트

  • 테스트 GameMode에서 PlayerState를 BP PlayerState로 변경
  • BP PlayerState의 CombatStatComponent에 AbilityBaseStats DataTable 연결
  • ProjectileShot ExplosionRadius가 DataTable 값에 따라 변경되는지 확인
  • ExplosionRadius 400 → 800 변경 시 로그 반영 확인
  • 인게임 GameMode fallback 값 사용 상태 확인 (공통 PlayerState 미설정)

⚠ 미완료

인게임 GameMode의 PlayerStateClass 공통 설정이 아직 적용되지 않았다. 몬스터 대상 테스트도 레벨 / GameMode 파일 Lock 상황으로 보류했다.

오늘 배운 점

핵심 성과

스킬 수치를 코드에서 분리해 DataTable로 외부화하는 기반 구조가 만들어졌다. 이제 디자이너나 팀원이 코드 수정 없이 에디터에서 수치를 조정할 수 있다.

  • AbilityTag + StatTag 기준 캐시 구조로 런타임 DataTable 접근 비용 없음
  • Ability가 PlayerState를 직접 캐스팅하지 않도록 ASC 래퍼 계층 분리
  • TryGet(실패 직접 처리)과 OrDefault(fallback 사용) 역할 분리
  • StackEffectClass GE와 CombatStatModifier 역할을 명확히 구분
  • Damage 흐름 설계 방향 정리 (CombatStat → SetByCaller → Meta → Health)

이슈 / 확인된 문제

  • 인게임 GameMode / PlayerState 공통 설정이 팀원 작업과 맞물려 있어 후속 정리 필요
  • 증강 Modifier 반영, Damage SetByCaller, Cooldown SetByCaller, MaxAmmo 연동 미구현
  • 팀 / 아군 판정, 파츠, 영구 강화와의 통합은 후속 시스템 정리 이후 진행

다음 작업

  • Augment Modifier Row / DataTable 구조 추가
  • AugmentInventory Owned / Stacks 이벤트와 CombatStatComponent 연동
  • 증강으로 ProjectileShot ExplosionRadius 증가 흐름 검증
  • Damage SetByCaller + Damage Meta Attribute 구조 리팩터링
  • Cooldown SetByCaller 기반 계산 정리
  • AutoFire MaxAmmo / CurrentAmmo Attribute 연동 검토
  • 인게임 GameMode PlayerStateClass 공통 설정 정리

'프로그래밍 > 개발일지' 카테고리의 다른 글

[TIL] 연발사격 처리 아키텍쳐 결정  (0) 2026.05.29
[TIL] Listen Server 기반 프로토타입 빌드 패키징 정리  (0) 2026.05.27
[TIL] Unreal 프로젝트 에셋 관리를 Google Drive + rclone + bat 파일로 분리하기  (2) 2026.05.21
[UnrealEngine] 멀티플레이어 디펜스 `SagoMagic` 프로젝트 KPT 회고  (0) 2026.04.24
[개발전반] Ureal Engine 5를 이용해 한 달동안 진행했던 팀프로젝트 TPS게임 개발 KRP  (0) 2026.03.05
'프로그래밍/개발일지' 카테고리의 다른 글
  • [TIL] 연발사격 처리 아키텍쳐 결정
  • [TIL] Listen Server 기반 프로토타입 빌드 패키징 정리
  • [TIL] Unreal 프로젝트 에셋 관리를 Google Drive + rclone + bat 파일로 분리하기
  • [UnrealEngine] 멀티플레이어 디펜스 `SagoMagic` 프로젝트 KPT 회고
hanong8
hanong8
hanong8 님의 블로그 입니다.
  • hanong8
    HaNong
    hanong8
  • 전체
    오늘
    어제
    • 분류 전체보기 (115) N
      • 프로그래밍 (112) N
        • Unreal Engine 5 (47) N
        • C++ (22)
        • UML (2)
        • 자료구조 (2)
        • 알고리즘 (9)
        • 개발일지 (8) N
        • DirectX11 (5)
        • Git (2)
        • 코드카타 (15)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    UE5 #GAS #Multiplayer #C++ #Projectile
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hanong8
[Unreal Engine 5] GAS Ranger ProjectileShot 심화 — 스킬 수치 DataTable 연동
상단으로

티스토리툴바