FPS에서는 소총, 권총, 저격총의 공격 방식이 서로 다르지만 탄약, 재장전, 명중 판정처럼 공통으로 처리할 기능도 많습니다. OUTPOST에서는 이를 WeaponBase에 모으고 무기별 차이를 자식 클래스로 분리했습니다.
1. 공통 기능을 각각 구현하면 생기는 문제
무기마다 탄약 수를 확인하고, 재장전 시간을 기다리고, 공격 명중 여부를 검사하는 로직을 복사해서 사용하면 같은 버그를 여러 곳에서 수정해야 할 수 있습니다.
그래서 WeaponBase를 추상 클래스로 두고 탄약·발사 간격·명중 처리·이펙트·재장전 등의 기능을 공통으로 제공하도록 했습니다. 각 무기는 실제 발사 방식에 따라 Fire()와 AltFire()를 구현합니다.
public abstract class WeaponBase : MonoBehaviour
{
public float damage = 50f;
public int maxAmmo = 30;
public int currentAmmo;
public float fireRate = 0.1f;
public float reloadTime = 2f;
public abstract void Fire();
public abstract void AltFire();
}
공통 부모 클래스는 모든 무기의 세부 동작까지 결정하지 않고, 여러 무기가 반드시 가져야 하는 구조를 정합니다.
2. 같은 발사 경로를 사용하되 무기별 보정은 다르게
소총 AssaultRifle은 발사할 때마다 누적 탄퍼짐을 증가시키고 연사 횟수에 따라 반동을 계산합니다. 반면 Pistol은 누적 연사 패턴 없이 기본 탄퍼짐과 이동 보정값을 적용합니다.
// AssaultRifle.Fire()의 핵심 부분
_accumulatedSpread += spreadIncreasePerShot;
float totalSpread =
(baseSpread + _accumulatedSpread) * GetMovementMultiplier();
ShootRaycast(totalSpread);
ShootRaycast()는 WeaponBase에 있습니다. 카메라 중심을 기준으로 Raycast를 수행하고, IDamageable을 가진 대상에게 피해를 전달하며, 머리 명중 여부와 피격 UI를 처리합니다. 무기별로 다른 것은 발사 조건과 퍼짐·반동을 계산하는 부분입니다.
또 이동 중, 점프 중, 앉은 상태에 따른 명중률 보정은 GetMovementMultiplier()로 공통화했습니다.
3. 재장전도 공통 비동기 처리
bool cancelled = await UniTask.Delay(
(int)(reloadTime * 1000f), cancellationToken: token)
.SuppressCancellationThrow();
if (cancelled) { isReloading = false; return; }
int needed = maxAmmo - currentAmmo;
int take = Mathf.Min(needed, reserveAmmo);
currentAmmo += take;
reserveAmmo -= take;
재장전이 끝나면 현재 탄약과 예비 탄약을 계산해 실제 채울 수 있는 수만큼 옮깁니다. 같은 계산을 소총·권총에 따로 작성할 필요가 없습니다. CancellationToken으로 오브젝트 파괴 등으로 작업이 종료된 경우에도 대비했습니다.
4. 무기 교체는 WeaponManager에서
무기를 교체할 때는 WeaponManager가 현재 슬롯과 활성 무기를 결정합니다. 각 WeaponBase가 모두 입력을 받지 않도록 씬에서 등록한 무기의 handleInputDirectly를 false로 설정하고, 매니저가 현재 무기에만 HandleFireInput()을 전달합니다.
이렇게 해두면 무기 객체는 발사 동작을, 매니저는 어떤 무기를 사용할지를 담당하게 됩니다.
정리
WeaponManager → WeaponBase → AssaultRifle/Pistol/SniperRifle로 역할을 구분했습니다. 공통 기능을 묶되 무기 특성까지 무리하게 통일하지 않고 Fire()에서 확장하도록 했습니다. 신규 무기 추가 시 공통 공격 로직을 재사용할 수 있다는 것이 장점입니다.
참고한 구현 코드
'Project > OUTPOST' 카테고리의 다른 글
| [Unity UI] 별도 이미지 없이 미니맵과 저격 조준경 만들기 (0) | 2026.08.29 |
|---|---|
| [Unity UniTask] 헬기 접근부터 이륙까지 하나의 탈출 연출로 만들기 (0) | 2026.08.29 |
| [Unity NavMesh] 좁은 통로에서 좀비가 한 줄로 몰리는 문제 줄이기 (0) | 2026.08.29 |
| [Unity NavMesh] 좀비가 플레이어와 발전기 사이에서 목표를 바꾸게 만들기 (0) | 2026.08.29 |
| [Unity] 웨이브 수치를 데이터로 분리하고, 적이 눈앞에서 생성되지 않게 만들기 (0) | 2026.08.29 |