티스토리 뷰
🟩 오늘의 목표
- Tracer 이펙트가 타겟에 닿지 않거나 잔상이 남는 실전 문제를 에디터 설정을 통해 해결한다.
- 무기 발사 로직을 CanFire, GetFireDirection, ProcessHit 등으로 부품화하여 코드 가독성과 유지보수성을 높인다.
- 적군(IEnemy) 판정 및 HUD 히트마커 애니메이션 연동을 통해 타격감 피드백을 완성한다.
🟧 1. 파티클 이펙트(Beam/Tracer) 트러블슈팅
에디터에서 빔(Beam)이나 트레이서(Tracer)가 의도대로 작동하지 않을 때 직접 해결한 실전 데이터다. 단순히 코드 문제가 아니라 이미터(Emitter) 설정의 디테일이 중요하다는 것을 확인했다.
| 항목 | 해결 방법 | 결과 |
| 빔 크기 조절 | Initial Size 수치 축소 | 굵고 투박하던 빔이 날렵한 Tracer로 변경됨 |
| 빔 방향 오류 | Target Absolute 옵션 체크 | 끝점이 엉뚱한 곳을 향하지 않고 목표 지점을 정확히 가리킴 |
| 사라지는 시간 | Emitter Duration을 1.0 → 0.03으로 수정 | Lifetime 모듈과 별개로 발사 즉시 깔끔하게 소멸됨 |
🟧 2. 무기 시스템의 부품화: 핵심 헬퍼 함수
무기 발사 본체 함수가 비대해지는 것을 막기 위해 로직을 기능별로 독립시켰다.
🟦 사격 조건 검증 및 조준 보정
사격 직전에 모든 제약 사항을 검사한다. 특히 FireRate의 80%만 지나도 발사를 허용하는 Tolerance(허용치) 설계는 연사 시 프레임 오차로 인한 끊김을 방지하는 핵심 팁이다.
// 1. CanFire (사격 가능 조건 검증)
bool UWeaponComponent::CanFire() const {
if (!CurrentStat || bIsReloading || CurrentAmmo <= 0) return false;
if (GetCurrentScore() < CurrentStat->UnlockScore) return false;
float CurrentTime = GetWorld()->GetTimeSeconds();
return (CurrentTime - LastWeaponSwitchTime >= WeaponSwitchDelay) &&
(CurrentTime - LastFireTime >= CurrentStat->FireRate * 0.8f);
}
// 2. GetFireDirection (조준 방향 및 캐릭터 회전)
FVector UWeaponComponent::GetFireDirection(const FVector& StartPos) const {
APlayerController* PC = Cast<APlayerController>(Cast<ACharacter>(GetOwner())->GetController());
FHitResult Hit;
if (PC && PC->GetHitResultUnderCursor(ECC_Visibility, false, Hit)) {
FVector Target = Hit.ImpactPoint;
Target.Z = StartPos.Z; // 수평 사격 보정
FVector BodyDir = (Target - GetOwner()->GetActorLocation());
BodyDir.Z = 0.f;
if (!BodyDir.IsNearlyZero()) GetOwner()->SetActorRotation(BodyDir.GetSafeNormal().Rotation());
return (FVector::Dist2D(GetOwner()->GetActorLocation(), Target) < 100.f) ?
GetOwner()->GetActorForwardVector() : (Target - StartPos).GetSafeNormal();
}
return GetOwner()->GetActorForwardVector();
}
🟧 3. 피드백 및 피격 상호작용 통합
🟦 시청각 피드백과 UI 연동
발사 시점의 Muzzle Flash와 Sound, 그리고 탄환이 맞았을 때의 Impact Effect와 Hit Marker 애니메이션을 유기적으로 연결했다.
// 3. PlayFireEffects (발사 피드백)
void UWeaponComponent::PlayFireEffects(const FVector& StartPos, const FName& Socket) {
if (CurrentStat->MuzzleFlash)
UGameplayStatics::SpawnEmitterAttached(CurrentStat->MuzzleFlash, Cast<ACharacter>(GetOwner())->GetMesh(), Socket);
if (CurrentStat->FireSound)
UGameplayStatics::PlaySoundAtLocation(this, CurrentStat->FireSound, StartPos);
}
// 4. ProcessHit (피격 판정 및 UI 연동)
void UWeaponComponent::ProcessHit(const FHitResult& Hit) {
if (!Hit.GetActor() || !IsValid(Hit.GetActor())) return;
if (CurrentStat->HitSound) UGameplayStatics::PlaySoundAtLocation(this, CurrentStat->HitSound, Hit.ImpactPoint);
if (CurrentStat->ImpactEffect) UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), CurrentStat->ImpactEffect, Hit.ImpactPoint, Hit.ImpactNormal.Rotation());
if (auto* Enemy = Cast<IEnemy>(Hit.GetActor())) {
Enemy->TakeDamage(CurrentStat->Damage);
// 크로스헤어 히트마커 애니메이션 실행
auto* PC = Cast<APlayerController>(Cast<ACharacter>(GetOwner())->GetController());
if (PC) if (auto* HUD = Cast<ADevHHUD>(PC->GetHUD()))
if (auto* Crosshair = HUD->GetCrosshairWidget()) Crosshair->PlayHitMarker();
} else {
UGameplayStatics::ApplyDamage(Hit.GetActor(), CurrentStat->Damage, nullptr, GetOwner(), nullptr);
}
}
🟧 핵심 요약
- 에디터 설정의 중요성 : Tracer 잔상의 원인은 Lifetime이 아니라 Emitter Duration 설정값일 수 있음을 확인했다.
- 로직 모듈화 : 복잡한 사격 시스템을 CanFire, ProcessHit 등으로 부품화하면 디버깅과 유지보수가 비약적으로 쉬워진다.
'내일배움캠프 Unreal_7기 > 본캠프' 카테고리의 다른 글
| [CH 3] 팀프로젝트 - KPT 회고 (0) | 2026.03.05 |
|---|---|
| [CH 3] 팀프로젝트 - 애니메이션 상하체 분리 및 사격 예약 시스템 고도화 (0) | 2026.02.27 |
| [CH 3] 팀프로젝트 - 수류탄 데칼 시스템 디버깅 및 발사 효과 구현 (0) | 2026.02.23 |
| [CH 3] 팀프로젝트 - 무기 및 에임 시스템 고도화 (0) | 2026.02.20 |
| [CH 3] 팀프로젝트 - shoulder launcher(수류탄) 시스템 구현 (0) | 2026.02.19 |

