언리얼 사격 시스템 정리 (라인트레이스 사격, 연발, 부분 랙돌, 부위 판정)

언리얼 Shooting 학습 노트. 강의 10.1~10.3 개념만. 사격 조준 보정, 연발 타이머, 부위 판정 헷갈릴 때 다시 보기.

사격 애니메이션과 몽타주

몽타주 만들 때 애니메이션 우클릭 > Create AnimMontage로 만들면 스켈레톤이 자동 지정돼 실수 없음. 슬롯은 상체용(SXUpperBody)으로 변경.

가장 흔한 함정: 우클릭해도 사격 애니메이션이 안 나옴 → 블렌드 시간 문제. 사격 애니메이션 재생 길이(0.23초)보다 Blend In/Out의 Blend Time(0.25초)이 길면 블렌드만 하다 끝남. Blend Time을 짧게(0.1) 줄여야 함. 짧은 애니메이션엔 항상 블렌드 시간부터 의심.

무기-몽타주 연결: 무기 액터가 AttackMontage를 들고, 캐릭터는 CurrentWeapon을 통해 GetCurrentWeaponAttackAnimMontage()로 가져옴. 무기를 주울 때(HandleOnPickUp) CurrentWeapon에 자기 자신을 세팅.

견착/에임오프셋 (애님 인스턴스 연동)

견착 분기: 애님 인스턴스에 bIsUnarmed 추가. 현재 무기 몽타주가 nullptr이면 맨손, 아니면 견착. Anim Graph 각 스테이트에서 이 bool로 분기.

에임오프셋(마우스 위아래로 총구 따라가기): AimOffset 애셋 준비 후, 애님 인스턴스에서 컨트롤 로테이션의 Pitch를 NormalizeAxis로 정규화한 NormalizedCurrentPitch를 매 프레임 갱신해 AimOffset에 연결.

이펙트/사운드/카메라 쉐이크

머즐 이펙트·사운드: 몽타주 노티파이로 Play Sound, Play Particle Effect를 원하는 프레임에 배치(노티파이 트랙 추가해서 분리).

카메라 쉐이크: DefaultCameraShakeBase 상속 BP 만들어 값 설정 → 캐릭터에 TSubclassOf로 들고 → 발사 시 PlayerController->ClientStartCameraShake. Client- 접두사는 해당 클라이언트에서만 흔들리게 한다는 의미(멀티 고려).

라인 트레이스 사격의 조준 보정 (이 챕터 핵심 원리)

문제: 총알은 총구(Muzzle)에서 나가야 하는데, 조준은 화면 중앙(카메라)을 기준으로 함. 총구와 카메라 위치가 달라서 그냥 총구에서 카메라 방향으로 쏘면 크로스헤어와 안 맞음.

해결 흐름:

  1. GetPlayerViewPoint로 카메라 위치/회전을 얻음
  2. 카메라 전방으로 FocalDistance만큼 간 지점을 초점으로 잡음
  3. 총구 위치를 카메라 조준선에 투영(내적)해서 보정된 초점(FinalFocalLocation) 계산 — 총구가 어디 있든 조준선상의 같은 목표를 향하도록
  4. 그 보정된 트랜스폼의 X축을 총알 방향으로, 총구를 시작점으로 LineTraceSingleByChannel
  5. 맞은 액터가 캐릭터면 TakeDamage

핵심은 "조준은 카메라 기준, 발사는 총구 기준"을 내적 투영으로 일치시키는 것. 디버그는 콘솔 변수(ShowAttackRangedDebug)로 토글해서 DrawDebug로 시각화.

크로스헤어: 화면 중앙 UI. PlayerController BeginPlay에서 CreateWidget + AddToViewport. 사격이 화면 중앙 기준이므로 크로스헤어와 트레이스 초점이 일치해야 함.

단발/연발 토글 (타이머 패턴)

치트시트:

  • TimeBetweenFire = 60 / FirePerMinute (분당 발사수를 발사 간격 초로 변환)
  • 휠 클릭으로 bIsFullAutoFire 토글
  • 연발 시작: 입력 Started에서 SetTimer(핸들, TryFire, TimeBetweenFire, true) — 마지막 true가 반복
  • 연발 종료: 입력 Completed에서 ClearTimer

함정: 단발 로직과 연발 로직이 같은 입력(AttackRanged)에 묶임. 단발은 InputAttackRanged에서 bIsFullAutoFire가 false일 때만 TryFire 호출, 연발은 Started/Completed에 별도 바인드. 분기 안 하면 단발+연발이 동시에 발사됨.

줌(아이언사이트) — FOV 보간

TargetFOV/CurrentFOV 두 변수. 입력 Started에서 TargetFOV를 좁게(45), Completed에서 원래대로(70). Tick에서 CurrentFOV를 FInterpTo로 TargetFOV에 부드럽게 수렴시키고 SetFieldOfView. 즉시 바꾸지 않고 보간해야 자연스러움. (시점 전환 챕터의 InterpTo 패턴과 동일)

부분 랙돌 피격 모션 (Physics Blend)

피격 시 별도 피격 애니메이션 없이 물리로 흔들리게:

  • spine_01 기준 하위 본만 SetAllBodiesBelowSimulatePhysics(true)
  • SetAllBodiesBelowPhysicsBlendWeight로 물리 영향 비중 조절(1이면 완전 물리)
  • 타이머로 일정 시간 후 복원

선행 조건 함정: 이 로직은 GetMesh()->SetSimulatePhysics(false) 상태가 먼저여야 함. 안 그러면 전신이 랙돌됨.

자연스러운 복원: 블렌드 웨이트를 1→0으로 즉시 끄지 말고, Tick에서 FInterpTo로 서서히 줄임. 0에 도달하면 SimulatePhysics를 끄고 블렌딩 종료. 즉시 끄면 캐릭터가 툭 끊김. (역시 InterpTo 패턴)

부위 판정과 헤드샷 (중요 함정)

HitResult.BoneName으로 맞은 본 이름을 가져와 부위별 처리. 헤드샷이면 BoneName이 "head"인지 비교(대소문자 무시)해서 데미지 차등.

가장 중요한 함정: BoneName이 None으로 나오면 캡슐에 맞은 것. 캡슐 콜리전은 본 정보가 없음. 해결하려면 공격 전용 트레이스 채널을 만들어 캡슐에는 반응 안 하고 SkeletalMesh에만 반응하도록 설정해야 함. (Collision 챕터의 채널 분리가 여기서 실제로 필요해지는 지점)

관통 패턴

이 챕터도 동일: 부드러운 전환(줌 FOV, 랙돌 복원)은 직접 끊지 말고 Tick + FInterpTo로 보간, 반복 동작(연발)은 직접 카운트하지 말고 타이머로, 타이밍(피격 복원)은 타이머 델리게이트로. 앞 챕터들에서 반복된 "직접 폴링하지 말고 보간/타이머/델리게이트로 잇는다" 구조의 연장.

+ Recent posts