Staff 전투 완성 - 얼음창 다중 투사체 · 지속 장판 · 부분 슈퍼아머 · 워프 옵션화
1. Water Heavy 얼음창 - 다중 투사체 "동시 소환 → 개별 발사"
3발이 머리 주변에 동시에 맺힌 뒤 각자 다른 타이밍에 날아가는 연출. 세 가지 장치가 필요했음.
(a) 지연 발사 - 스폰 후 제자리에 떠 있다 발사
ProjectileMovement의 속도를 0으로 두면 (중력 0 가정) 제자리에 뜬 채 대기 → 타이머로 저장한 방향에 발사.
void AStaffProjectile::ArmDelayedLaunch(const FVector& Direction, float Speed, float Delay)
{
PendingLaunchDirection = Direction; PendingLaunchSpeed = Speed;
ProjectileMovement->Velocity = FVector::ZeroVector;
ProjectileMovement->InitialSpeed = 0.f; // PMC가 안 움직임 = 스폰 위치에 정지
World->GetTimerManager().SetTimer(LaunchDelayTimerHandle, this, &AStaffProjectile::HandleDelayedLaunch, Delay, false);
}
(b) 소켓 앵커 + 오프셋 가산 - 좌/상/우 배치
기존엔 "소켓이 있으면 SpawnOffset 무시"였음. 다중 스폰을 위해 소켓을 앵커로 두고 그 위에 오프셋을 가산하는 모드(bAddOffsetToSocketBase)를 추가.
if (bUsedSocket && Params.bAddOffsetToSocketBase)
SpawnLocation += AvatarActor->GetActorRotation().RotateVector(Params.SpawnOffset);
(c) 볼리 상호 충돌 무시 - 형제 창에 막혀 소멸 방지 🐛
먼저 발사된 창이, 아직 떠 있는 형제 창에 부딪혀 즉시 파괴되는 문제. → 같은 볼리끼리 이동 스윕에서 서로 건너뛰게 등록.
// AStaffProjectile
void IgnoreOtherProjectile(AStaffProjectile* Other)
{ CollisionSphere->IgnoreActorWhenMoving(Other, true); } // 블로킹/오버랩 모두 무시
// GA_HeavyAttack::ScheduleProjectiles — 오프셋 개수만큼 동시 스폰 후 O(n²) 상호 등록
for (int32 i = 0; i < Offsets.Num(); ++i)
Volley.Add(SpawnOneProjectile(Offsets[i], /*bAddOffsetToSocketBase=*/true, FireDelays[i]));
for (a: Volley) for (b: Volley) if (a != b) Volley[a]->IgnoreOtherProjectile(Volley[b]);
리팩토링:
SpawnProjectile()(void, 단일) →SpawnOneProjectile(offset, bAddToSocket, delay)(스폰된 투사체 반환)로 바꿔 레거시 단일 경로와 다중 볼리 경로가 같은 스폰 함수를 공유.
2. Water Burst 지속 장판(소용돌이) - 매 프레임 노티 + 적별 간격 데미지
신규 실행 타입 EAttackExecutionType::AreaContinuous. 노티 구간 동안 매 프레임 호출되며, 데미지는 적별 간격으로, 넉백은 매 프레임 방사.
void UPlayerBurstComponent::DoAreaContinuousHit(const FBurstHitInstance& Hit, int32 HitIndex)
{
// 중심 반경 오버랩 (AoeRadius 재사용) ...
const double Now = World->GetTimeSeconds();
TMap<TWeakObjectPtr<AActor>, double>* TimeMap = &PerHitLastHitTime[HitIndex]; // 적별 마지막 타격 시각
// 이중 넉백 방지: 데미지 틱에서 GE 방향성 넉백은 끄고, 방사 넉백을 단일 소스로.
FBurstHitInstance DamageHit = Hit;
DamageHit.KnockbackStrength = 0.f;
DamageHit.KnockbackUpwardStrength = 0.f;
for (Target : Overlaps)
{
bool bApplyDamage = true; // 첫 진입 즉시 1틱
if (const double* Last = TimeMap->Find(Target)) bApplyDamage = (Now - *Last) >= Interval;
if (bApplyDamage) { ApplyHitToTarget(Target, DamageHit, ...); TimeMap->Add(Target, Now); }
// + 매 프레임 중심→바깥 방사 넉백 수집(보스 제외 옵션)
}
}
- 핵심 자료구조:
PerHitLastHitTime= per-HitIndex의TMap<약참조 액터, 시각>→ 장판 위에 서 있는 적마다 재적용 간격(ContinuousDamageInterval)을 독립 관리. - 이중 넉백 방지 🐛: 데미지 GE에도 방향성 넉백이 붙어 있으면 방사 넉백과 두 소스가 충돌 → 데미지 틱에서는 GE 넉백을 끄고 방사만 밈.
3. 공격 중 "부분 슈퍼아머" - 전역 억제 vs 비보스 억제 2단 구성
콤보/강공이 잡몹 피격엔 안 끊기고 보스 공격엔 끊기게. 기존 전역 억제(버스트 슈퍼아머)에 조건부 억제를 한 겹 더.
❌ Before — 억제 태그 1종(전역)만: 있으면 무조건 리액션 스킵
✅ After — 2단 억제
// (1) 전역: 모든 공격자에 대해 스킵 (버스트 슈퍼아머 = State.Player.Bursting)
if (ASC->HasAnyMatchingGameplayTags(ReactionSuppressionTags)) return;
// (2) 부분: 공격 중이면 '비보스' 공격자만 스킵 (보스는 통과)
if (ASC->HasAnyMatchingGameplayTags(AttackStateSuppressionTags)) // State.Player.Attacking / UsingHeavyAttack
if (!(bBossBypassesAttackSuppression && IsInstigatorBoss(Payload)))
return;
- 보스 판정: 피격 이벤트
Payload->Instigator의 ASC에Monster.Type.Boss태그가 있는지(IsInstigatorBoss). - 공용 안전장치:
AttackStateSuppressionTags기본값이 플레이어 공격 태그라, 이 태그가 없는 적에게는 영향 없음(플레이어/적 공용 컴포넌트). - 데미지는 두 경우 모두 정상 적용 — 억제되는 건 리액션(몽타주/어빌리티 캔슬)뿐.
4. AttackWarp - 무타겟 시 입력방향 회전을 옵션화
돌진 워프가 타겟이 없으면 항상 이동 입력 방향으로 회전했음 → 특정 콤보에선 원치 않는 재정렬. 플래그로 끌 수 있게.
// 무타겟일 때만 해당. 타겟이 있으면 여전히 워프.
- if (bPlayerControlled && !InputDir.IsNearlyZero())
+ if (bRotateToInputWhenNoTarget && bPlayerControlled && !InputDir.IsNearlyZero())
원칙: "무입력에도 정면 강제회전" 버그를 잡을 때 raw 이동 입력만 쓰도록 한 데 더해, 회전 자체를 데이터로 on/off → 콤보별로 재정렬을 허용/금지.
5. (짧게) 무기 히트볼륨 BoundsRadiusScale 기본값 3.0
Bounds 트레이스 반경 스케일 기본값을 3.0으로 올리고 데이터에 반영. 판정이 얇게 느껴지던 걸 완화(값만 변경, 로직 동일).
비교 정리
| 구분 | ❌ Before | ✅ After |
|---|---|---|
| 투사체 스폰 | 단일 SpawnProjectile()(void) |
SpawnOneProjectile(...)(반환) — 단일/볼리 공용 |
| 다중 발사 | 순차 스폰 | 동시 스폰 + 투사체별 FireDelays[i] 발사 지연 |
| 소켓+오프셋 | 소켓 있으면 오프셋 무시 | bAddOffsetToSocketBase로 앵커 위 가산 |
| 볼리 충돌 | 형제 창에 막혀 소멸 | IgnoreActorWhenMoving로 상호 무시 |
| 장판 데미지 | (없음) | AreaContinuous — 적별 TMap<Actor,시각> 간격 재적용 |
| 장판 넉백 | — | 매 프레임 방사(데미지 틱 GE 넉백 끔=이중 방지) |
| 피격 억제 | 전역 1종 | 전역(모든 공격자) + 부분(비보스만, 보스 예외) |
| 워프 회전 | 무타겟이면 항상 입력방향 | bRotateToInputWhenNoTarget로 on/off |
핵심 요약
- "동시 소환 → 개별 발사" 볼리는 ①속도 0 대기(지연 발사) ②소켓 앵커+오프셋 가산 ③형제끼리
IgnoreActorWhenMoving세 장치가 함께 필요. 특히 형제 충돌 무시를 빼면 먼저 나간 발사체가 소멸함. - 지속 장판은 매 프레임 노티 + per-target
TMap<Actor,시각>로 적별 간격 데미지, 넉백은 매 프레임 방사. 넉백 소스는 하나로(데미지 GE 넉백 끄기). - 슈퍼아머는 전역/부분 2단 — 전역은 모든 공격자, 부분은 비보스만(보스는
InstigatorASC의Monster.Type.Boss로 예외). 데미지는 항상 적용, 리액션만 억제. - "항상 하는 동작"이 콤보마다 거슬리면 데이터 플래그로 on/off(예: 워프 무타겟 회전) — 코드 분기보다 데이터로 끄고 켠다.
'내일배움캠프_Unreal > TIL' 카테고리의 다른 글
| 내일배움캠프_Unreal/TIL - Day 151 (260707) (0) | 2026.07.08 |
|---|---|
| 내일배움캠프_Unreal/TIL - Day 150 (260706) (0) | 2026.07.06 |
| 내일배움캠프_Unreal/TIL - Day 148 (260702) (0) | 2026.07.02 |
| 내일배움캠프_Unreal/TIL - Day 147 (260701) (0) | 2026.07.01 |
| 내일배움캠프_Unreal/TIL - Day 145 (260629) (0) | 2026.06.29 |