내일배움캠프_Unreal/TIL

내일배움캠프_Unreal/TIL - Day 141 (260623)

하임 2026. 6. 23. 21:00

GAS 어빌리티 비용 처리 & Git LFS 대역폭 절감


1. GAS 어빌리티 비용 — "조건(gate)"과 "비용(cost)"은 다르다

강공격이 무한 발동되던 원인: CanActivateAbility가 원소 게이지 ≥1칸을 조건으로만 검사하고, 정작 소모도 비용도 없었음. 리팩토링이 게이지 소모(ConsumeOldestSlot)를 잘라냈는데 스태미너 비용을 연결하지 않아 비용이 0이 된 것.

# git으로 추적한 "끊긴 지점"
git show <refactor-commit> -- GA_HeavyAttack_Base.cpp
- const FGameplayTag ConsumedElement = Gauge->ConsumeOldestSlot();  // 슬롯 1칸 소모(원래)
- ExecuteHeavyEffect(ConsumedElement);
+ ExecuteHeavyEffect(ResolveCurrentElementTag());                   // 소모 없음

❌ 조건만 있고 비용 없음 → 자원이 안 줄어 무한

return IsValid(Gauge) && Gauge->HasChargedSlot();   // 게이지 1칸이면 영구 발동

✅ 비용 = 스태미너(CostGameplayEffectClass), 게이지는 조건이 아님

// CanActivateAbility: 무기 타입만 게이트
return bIsStaff == bActivateForStaff;
// BP: CostGameplayEffectClass = GE_Cost_Stamina_100 (Instant, Stamina -100)
//  → Super::CanActivateAbility 안의 CheckCost가 스태미너<100이면 발동 차단
//  → ActivateAbility의 CommitAbility가 실제 -100 소모

GAS 비용 메커니즘 (Lyra 정석):

  • CostGameplayEffectClass = Instant GE(속성 −N). 직접 차감/체크 코드 불필요.
  • CheckCost(= CanApplyAttributeModifiers, 속성이 0 미만 되면 false) → 발동 차단을 자동 수행.
  • CommitAbility → 비용 GE 적용(소모). 평타류는 Activate에서 이미 호출 중이라 BP 한 줄로 끝.

자원 시스템에서 "쓸 수 있는 조건"과 "쓰면 닳는 비용"은 별개. 조건만 걸고 비용을 안 걸면 무한이 된다. GAS에선 비용은 항상 CostGameplayEffectClass로 — 게이트(CheckCost)와 소모(CommitAbility)가 한 세트로 따라온다.


2. Git LFS — 대역폭/스토리지 모델과 캐시 선배포

UE 프로젝트의 .uasset은 LFS. 머지로 부풀어 오른 브랜치를 8명이 받으면 대역폭이 얼마나 드는지 계산함.

GitHub LFS 청구 모델 (핵심):

동작 카운트되는 quota
push(업로드) 스토리지 (대역폭 아님)
pull/clone/fetch(다운로드) 대역폭

델타 계산develop 대비 신규 LFS 객체 = 팀원이 받는 양:

git lfs ls-files -l origin/develop | awk '{print $1}' | sort -u > dev   # OID 집합
git lfs ls-files -l HEAD          | awk '{print $1}' | sort -u > head
comm -13 dev head > new     # HEAD에만 있는 신규 OID = 다운로드 델타

→ 결과: 신규 2,819개 / 1.18 GB. 나 포함 8명 중 7명이 각각 1.18 GB 다운 = 8.25 GB 대역폭(최악치). 무료 quota는 월 10 GiB → 거의 한도에 근접.

캐시 선배포로 대역폭 우회: LFS 객체는 content-addressed(OID=sha256) → 한 머신의 캐시가 다른 머신에서 그대로 유효. .git/lfs/objects에 미리 넣어두면 pull 시 캐시 히트 → 서버 다운로드 0.

# 신규 객체만 패키징 (전체 캐시 14GB X, 델타 1.13GB O)
while read o; do d="lfs_delta/${o:0:2}/${o:2:2}"; mkdir -p "$d"; \
  cp ".git/lfs/objects/${o:0:2}/${o:2:2}/$o" "$d/"; done < new
# 팀원: lfs_delta/* 를 .git/lfs/objects/ 에 병합 → 그다음 git pull
  • ⚠️ 순서: 캐시 seed → pull. pull 먼저 하면 git-lfs가 서버에서 받아버림(대역폭 소모).
  • push는 여전히 필요(서버 스토리지/CI/신규 clone용). 캐시 배포는 대역폭 절감 보조 수단.
  • 검증: 팀원이 git lfs fsck / git lfs ls-files(객체 * 표시)로 완전성 확인.

LFS는 다운로드만 대역폭. 대용량 자산을 여러 명이 받을 땐, 콘텐츠 주소 기반인 LFS 캐시(.git/lfs/objects)를 사내망/드라이브로 선배포하면 서버 대역폭 0으로 끝낼 수 있다. (단 델타만 — 전체 캐시는 14배 컸음.)


3. 데이터 드리븐 vs CDO + 데드 데이터 탐지

데드 데이터 판정 = "소비처 grep". 무기 행(FRetrieveWeaponDataRow)에 인라인 SprintAttack/JumpAttack/ParryCounter가 있었지만, 어빌리티들은 전부 별도 DataAsset(UAttackComboDefinition)에서 읽고 있었음(원소 variant까지). 무기 행 인라인 필드를 읽는 코드는 0개 → 데드.

grep -rn "\.SprintAttack\|ResolveSprintVariant" Source/   # 소비처 확인 → DataAsset만 나옴

같은 개념은 한 홈에: 스태프 강공은 DT_WeaponData(데이터), 활은 GA_BowShot CDO(어빌리티)로 같은 "투사체 발사"를 서로 다른 곳에서 설정 → 무기 행의 투사체 필드가 활에선 조용히 무시됨(오설정 유발). 둘 다 동일 SpawnConfigured를 호출하면서도 데이터 출처만 갈림.

⚠️ DataTable 주의(재확인): DT에 쓰이는 USTRUCT의 UPROPERTY를 제거하면 그 DT 애셋이 안 열릴 수 있음 → 필드는 추가만, 제거 지양. 데드 필드 정리도 "제거"는 위험 절차(에디터 닫고/리빌드/로드확인) 동반.


비교 정리

헷갈린 것 A B
어빌리티 조건(gate) = 쓸 수 있나(CheckCost·태그) 비용(cost) = 쓰면 닳나(CommitAbility)
LFS quota 스토리지 = push/보관량 대역폭 = download량
무브셋 데이터 DataAsset/DataTable(공유·다수) CDO(어빌리티 1개에 고정)

핵심 요약

  • "조건"과 "비용"은 다르다. 조건만 걸고 소모/비용을 안 걸면 자원 무한. GAS 비용은CostGameplayEffectClass(CheckCost가 차단 + CommitAbility가 소모).
  • GitHub LFS는 다운로드만 대역폭, push는 스토리지. 대용량을 N명이 받으면 N배 대역폭.
  • LFS 객체는 OID 기반이라 캐시(.git/lfs/objects) 선배포로 서버 대역폭 0 가능 — 단 델타만(전체는 14배).
  • 데드 데이터는 소비처 grep으로 판정. 같은 개념을 DataTable·CDO 두 군데 두면 한쪽이 조용히 무시된다.