내일배움캠프_Unreal/TIL

내일배움캠프_Unreal/TIL - Day 153 (260709)

하임 2026. 7. 9. 20:57

체감 및 타격감 변경 — 감도 · FOV · 전투 SFX · 히트 피드백


1. 감도 — 곱셈 결합에서 배타(통합/세부) 모드로

기존 마우스 감도는 실제 감도 = 통합 배율(Master) × 축별(X/Y) 구조였다. 둘 다 기본 1.0이라 평소엔 티가 안 났지만, X만 올리면 통합 배율까지 곱해져 사람이 예상한 값과 실제 감도가 어긋났다. 노브가 둘인데 서로 곱해지는 게 문제.

❌ Before — 두 노브가 곱해짐

const float Master = Settings ? Settings->MouseSensitivity : 1.f;
const float SensitivityX = (Settings ? Settings->MouseSensitivityX : 1.f) * Master;
const float SensitivityY = (Settings ? Settings->MouseSensitivityY : 1.f) * Master;

✅ After — 배타 모드. 통합이면 단일 값(양축 공통), 세부면 X/Y만

const bool bUnified = !Settings || Settings->bUseUnifiedSensitivity;
const float SensitivityX = bUnified ? Settings->MouseSensitivity : Settings->MouseSensitivityX;
const float SensitivityY = bUnified ? Settings->MouseSensitivity : Settings->MouseSensitivityY;
  • UI엔 모드 버튼(Btn_SensMode_Unified/_Detailed)을 붙이고, 현재 모드를 BP에 통지해 안 쓰는 슬라이더를 회색 처리하도록 OnSensitivityModeChanged를 열었다.
  • 어느 쪽이든 값 하나만 최종 감도에 들어가서, 곱셈으로 예측이 어긋나던 문제가 사라짐.
void URetrieveSettingsPanelWidget::HandleSensModeUnified()
{
    if (bRefreshingControls) return;
    if (auto* S = GetUserSettings()) { S->bUseUnifiedSensitivity = true; }
    RefreshCurrentPage();   // 슬라이더 값 갱신 + 모드 통지
    APPLY_PREVIEW(ERetrieveSettingsCategory::Controls);
}

2. FOV — ALSCamera 잔여 의존성 정리

FOV 조절이 예전엔 ALS 카메라 컴포넌트를 거쳤는데, 지금 구조에선 그 경로 없이도 FOV가 먹고 있었다. ALSCamera 모듈은 잔여 의존성만 남은 상태여서, 기능이 안 깨지는 걸 확인하고 잘라냈다.

- "ALSCamera",                      // Retrieve.Build.cs 모듈 의존성
- #include "AlsCameraComponent.h"   // RetrieveSettingsSubsystem.cpp

교훈: 쓰지 않는 카메라 모듈을 링크만 하고 있으면 빌드·의존성 그래프만 무거워진다. 동작 경로가 이미 바뀌었으면 잔여 의존성을 남기지 말 것. 모션블러 기본값도 이 김에 정리(옵션에서 토글 가능, 기본 ON).


3. 리스폰 버튼을 설정 패널 → 시스템 메뉴로

강제 리스폰 버튼이 설정 패널(Btn_Respawn)에 임시로 달려 있었다. 하지만 "강제 리스폰"은 설정 항목이 아니라 인게임 액션이라 자리가 어색했고, 설정 패널에선 인게임인지 검사해 버튼을 숨기고 클릭 시 세션 상태를 재확인하는 가드가 필요했다.

시스템(포즈) 메뉴로 옮기니 그 가드가 통째로 사라짐. 시스템 메뉴는 인게임에서만 열리니까.

void URetrieveSystemMenuWidget::HandleRespawnClicked()
{
    // 강제 리스폰 → 사망 흐름(Result→Retry)이 세션 상태를 전환하며 이 패널을 정리한다.
    if (ARetrievePlayerController* PC = Cast<ARetrievePlayerController>(GetOwningPlayer()))
        PC->RequestUnstuck();
}

교훈: 방어 코드가 늘어난다면 UI가 엉뚱한 곳에 있다는 신호일 수 있다. 있어야 할 곳에 두니 가시성 가드·상태 재확인이 다 필요 없어졌다. 버튼 배선은 BindWidgetOptional이라 WBP는 이름만 맞추면 됨.


4. 전투 SFX 팩 + 스윙·타격 노티

때리는 맛의 절반은 소리였다. Fantasy_Combat_02 SFX 팩(스윙 Whoosh, Big Hit, Hitmark, Short Impact)을 임포트하고:

  • 스윙음 — 검방/스태프 콤보 몽타주에 사운드 노티로 심어 애니 타이밍에 정확히 붙임.
  • 히트 임팩트음 — 팩 사운드로 교체하고, 타격 순간의 GameplayCue.Combat.Hit 큐(GCN_Hit_Impact)가 물게 함.

소리를 코드가 아니라 노티/큐로 데이터화하니, 이후 무기별로 소리만 갈아 끼우기 쉬워졌다.


5. 타격감 — 기본 넉백 400 + 히트 플래시 큐

넉백이 약해 맞아도 밀리는 느낌이 없었다. 일반 히트에 적용되는 전역 기본 넉백을 150 → 400으로 올림. 콘솔 변수라 플레이 중 라이브 튜닝 가능.

// SetByCaller 넉백 없는 히트에 적용(보스/플레이어/DoT 제외). 콘솔 라이브 튜닝.
static TAutoConsoleVariable<float> CVarDefaultHitKnockback(
    TEXT("retrieve.Combat.DefaultHitKnockback"), 400.f, TEXT("기본 히트 넉백 강도. 0=끔."));

여기에 피격 순간 번쩍이는 히트 플래시(GCN_HitFlash + 머티리얼 M_HitFlash)를 큐로 얹어, 소리·넉백·플래시가 같은 타이밍에 겹치도록 함.


6. (버그) 익명 네임스페이스 헬퍼 이름 충돌

작업을 합쳐 빌드하니 링크가 깨졌다. 상점 컴포넌트(RetrieveShopComponent.cpp)의 익명 네임스페이스 헬퍼 이름이 다른 번역 단위의 동명 헬퍼와 충돌.

- void SetInteractionTargetEnabled(AActor* Owner, bool bEnabled)
+ void SetShopInteractionTargetEnabled(AActor* Owner, bool bEnabled)

익명 네임스페이스라고 이름을 흔하게 두면 옆 파일과 부딪힌다. 접두어(Shop)로 구체화하는 걸로 끝.


비교 정리

구분 ❌ Before ✅ After
마우스 감도 통합 × 축별 곱셈(예측 어긋남) 배타 모드(통합=단일 값 / 세부=X/Y)
FOV 경로 ALSCamera 모듈 의존 잔존 잔여 모듈·include 제거
리스폰 버튼 설정 패널 + 인게임 가드 이중 시스템 메뉴(인게임 전용, 가드 불필요)
전투 사운드 빈약/코드성 SFX 팩 + 스윙 노티 + 히트 큐(데이터화)
히트 넉백 150(약함) 400 + 히트 플래시 큐(동시 타이밍)

핵심 요약

  • 감도는 곱셈 결합을 없애고 배타(통합/세부) 모드로 — 노브가 서로 곱해져 예측이 어긋나던 문제를 제거.
  • 동작 경로가 바뀐 ALSCamera 잔여 의존성은 모듈·include까지 잘라내 빌드·의존성을 가볍게.
  • 리스폰 버튼은 인게임 전용 시스템 메뉴로 이동 — 방어 코드 증가는 UI 위치가 틀렸다는 신호였고, 옮기니 가드가 사라졌다.
  • 타격감은 SFX 팩 + 스윙 노티 + 히트 큐 + 넉백 400 + 히트 플래시를 같은 타이밍에 겹쳐 완성.