Staff 신규무기 전투 구현
1. Family 태그로 stuck-active 해소
공격 변형(콤보/스프린트/점프/카운터)이 각자 형제(sibling) 태그라, 다른 어빌리티의 `Cancel/BlockAbilitiesWithTag`가 부모 하나(`Ability.Player.Attack`)만 나열하면 변형들이 누락됨 → 변형 공격이 안 끊겨 active로 남는 stuck-active 버그.
해결: 공통 family 태그를 AssetTags에 가산(식별 태그는 유지). Cancel/Block은 family만 참조함.
// 각 공격 어빌리티 생성자 — 식별 태그 + family 병기
FGameplayTagContainer Tags;
Tags.AddTag(Ability_Player_Attack); // 식별(기존 BP 참조 유지)
Tags.AddTag(Ability_Type_Attack); // family — Cancel/Block 단일 포착
SetAssetTags(Tags);
❌ Before: `CancelAbilitiesWithTag`에 변형마다 개별 나열 → 신규 변형 추가 시 누락 반복
✅ After: `CancelAbilitiesWithTag.AddTag(Ability_Type_Attack)` 한 줄 → 신규 변형도 family만 달면 자동 편입
> ⚠️ 주의: family가 광범위하면 의도 외 차단 발생. 가드가 `Ability.Type.Attack`을 Block + 카운터도 family 보유 → 가드 홀드 중 카운터까지 차단됨. 차단에 "예외"가 필요한 어빌리티는 family 가입을 재검토.
2. 같은 입력, 다른 어빌리티 — CanActivate 게이팅 공존
입력 라우팅(InputTag)은 그대로 두고, `CanActivateAbility`에서 조건으로 상호배제함. 같은 입력에 두 어빌리티가 바인딩돼도 하나만 발동됨.
// 스태프 전용 어빌리티만 true
bool UGA_Blink::CanActivateAbility(...) const
{
if (!Super::CanActivateAbility(...)) return false;
const UWeaponComponent* W = Avatar->FindComponentByClass<UWeaponComponent>();
return W && W->GetWeaponDataRef().WeaponTypeTag == Weapon_Type_Staff;
}
// 반대편(GA_Dash)은 WeaponType != Staff 일 때만 true → 정확히 한쪽만 발동
분기 기준은 자유: 무기 타입(Blink/Dash, Parry/Guard) 또는 데이터 유무(`AttackComboDefinition.IsNull()`이면 GA_Attack 자동 비활성).
3. 베이스 클래스 추출 = 로직 재사용(DRY)
공통 흐름은 추상 베이스에 두고 훅 메서드만 override함.
// 게이지 소모 흐름은 베이스가, "효과"만 자식이 구현
void UGA_HeavyAttack_Base::ActivateAbility(...) {
if (!Gauge->HasChargedSlot()) { /* 종료 */ }
const FGameplayTag Elem = Gauge->ConsumeOldestSlot();
ExecuteHeavyEffect(Elem); // ← 자식 override: 검=광역 AoE / 스태프=투사체 발사
}
패링 동일 패턴으로 정리: `UGA_ParryBase`(패링 윈도우·성공·카운터·스태거 공통) ← `GA_Parry`(전 무기 기본), `GA_Guard`(방패=막기+패링).
> UPROPERTY를 자식 → 베이스로 올려도 기존 BP 에셋의 할당 값이 보존됨. UE가 프로퍼티를 이름으로 직렬화·해석하기 때문. 작동 중인 어빌리티를 안전하게 베이스로 끌어올릴 수 있음.
4. 플레이어 투사체 = 근접과 "동일 데미지 규약"
투사체 overlap에서 GE를 적용할 때 근접 트레이스와 같은 계약을 맞춰야 피격 반응·게이지·피드백이 일관됨.
Spec.Data->SetSetByCallerMagnitude(Data_Damage_Mul, Mul);
Spec.Data->AddDynamicAssetTag(Attack_Type_Heavy); // 방어 판정 차원
Spec.Data->AddDynamicAssetTag(HitReactTypeToTag(React)); // 피격 반응 차원
Context.AddInstigator(PlayerPawn, this); // Instigator=폰, EffectCauser=투사체(분리)
> `Attack.Type.*`(방어 판정: 가드/패링 분기) ↔ `HitReact.Type.*`(피격 모션)은 독립 차원이라 둘 다 따로 주입해야 함. 투사체는 가해자(폰)와 직접 가해체(투사체)가 갈리므로 Instigator/EffectCauser를 분리.
5. Blink 충돌 — ObjectType 쿼리로 "적 통과 / 벽 막기"
"적은 통과시키되 벽은 막는다"는 트레이스 채널이 아니라 ObjectType 쿼리로 깔끔히 분리됨.
// 벽만 검사(WorldStatic/Dynamic) → Pawn은 쿼리 대상이 아니라 자동 통과
FCollisionObjectQueryParams Walls;
Walls.AddObjectTypesToQuery(ECC_WorldStatic);
Walls.AddObjectTypesToQuery(ECC_WorldDynamic);
World->SweepSingleByObjectType(WallHit, Start, End, FQuat::Identity, Walls, Capsule, Params);
// 도착지에 적이 겹치면 진행축에 투영해 그 적 "앞"에 멈춤
const float DistAlong = FVector::DotProduct(EnemyLoc - Start, Dir);
MaxDist = DistAlong - (MyRadius + EnemyRadius + Gap);
| 방식 | 동작 |
| `SweepByChannel(ECC_Visibility)` | 채널 응답에 따라 막힘 — 의도 외 충돌 섞이기 쉬움 |
| `SweepByObjectType(WorldStatic/Dynamic)` | 지정 오브젝트만 검사 → 적 Pawn을 깔끔히 무시 |
6. 데이터 구조체 ↔ 게임플레이 클래스 결합 끊기
데이터 헤더에서 게임플레이 클래스를 전방 선언 + `TSubclassOf`로만 참조 → include 순환·결합 회피.
class AStaffProjectile; // 전방 선언만 (헤더 include 불필요)
USTRUCT() struct FWeaponStaffAttack {
UPROPERTY(EditAnywhere) TSubclassOf<AStaffProjectile> ProjectileClass;
};
UHT는 전방 선언만으로 `TSubclassOf` 프로퍼티를 정상 생성함(런타임에 UClass 해석). 풀 정의가 필요한 `.cpp`에서만 include.
비교 정리
| 패턴 | 문제 | 해결 |
| family 태그 | sibling 누락 stuck-active | 공통 태그를 AssetTags에 가산, Cancel/Block은 family만 참조 |
| 어빌리티 공존 | 같은 입력 충돌 | `CanActivateAbility` 조건 게이팅(무기 타입/데이터 유무) |
| 상속 재사용 | 로직 중복 | 추상 베이스 + 훅 override, UPROPERTY 베이스 승격해도 BP 값 보존 |
| 투사체 데미지 | 근접과 반응 불일치 | SetByCaller + Attack.Type + HitReact.Type 동일 주입 |
| 선택적 충돌 | 적·벽 구분 어려움 | 트레이스 채널 대신 ObjectType 쿼리 |
핵심 요약
> GAS Cancel/Block 누락은 공통 family 태그를 AssetTags에 가산해 구조적으로 해소함 (단, 차단 예외가 필요한 어빌리티는 가입 주의).
> 입력 변경 없이 `CanActivateAbility` 조건 분기만으로 무기별 어빌리티를 공존·상호배제할 수 있음.
> 공통 흐름은 추상 베이스 + 훅 override로 재사용. UPROPERTY를 베이스로 올려도 기존 BP 값은 이름 기반 직렬화로 보존됨.
> 투사체 데미지는 근접과 동일 규약(SetByCaller / Attack.Type / HitReact.Type 분리 주입)으로 맞춰야 반응·게이지가 일관됨.
> "적 통과·벽 막기"는 트레이스 채널이 아니라 ObjectType 쿼리로 깔끔히 분리함.
'내일배움캠프_Unreal > TIL' 카테고리의 다른 글
| 내일배움캠프_Unreal/TIL - Day 140 (260622) (0) | 2026.06.22 |
|---|---|
| 내일배움캠프_Unreal/TIL - Day 137 (260617) (0) | 2026.06.17 |
| 내일배움캠프_Unreal/TIL - Day 133 (260611) (0) | 2026.06.11 |
| 내일배움캠프_Unreal/TIL - Day 130 (260608) (0) | 2026.06.08 |
| 내일배움캠프_Unreal/TIL - Day 128 (260604) (0) | 2026.06.04 |