티스토리 뷰

🟩 오늘의 목표

  • 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 등으로 부품화하면 디버깅과 유지보수가 비약적으로 쉬워진다.