🚗 Unity Car Survival Prototype — 데이터·이벤트 기반 차량 생존 슈팅 만들기
자동차에 무기를 장착해 몰려오는 적을 처치하며 스테이지를 버티는 탑다운 프로토타입입니다.
싱글톤 매니저, 무기/업그레이드, 스테이지/스폰, 리스폰, UI·사운드까지 확장 가능한 구조로 구현했어요.
🧾 개요
- 요약: ScriptableObject(WeaponData) + 이벤트 기반(UI/상태) + 제네릭 싱글톤 매니저로 빠르게 기능을 붙여 나갈 수 있는 구조.
- 핵심 요소: 자동 주행(CarAI), 자동 사격(BaseWeapon), 업그레이드(WeaponSlotsManager), Respawn, 스테이지 타이머/스폰.
- 대상 독자: 서바이벌라이크/탑다운 프로토타입을 빨리 만들고 싶은 분, 아키텍처 참고가 필요한 분.
🎮 게임 소개
차량을 조작(AI 주행)하며 시간이 지날수록 강해지는 적을 상대하는 탑다운 생존 슈팅입니다.
처치 보상으로 골드/경험치를 모아 무기를 업그레이드하고, 위기 상황에서는 Respawn으로 트랙 위 최근접 노드에 복귀해 흐름을 이어갑니다.
- 목표: 가능한 오래 생존하며 Stage를 올리고 더 강한 웨이브를 버티기
- 루프: 처치 보상 → 골드/EXP → 업그레이드 패널에서 레벨업/티어업
🖼️ 플레이 영상 & GIF
✨ Features
- Generic Singleton: ClassName.Instance로 전역 접근(중복 생성 방지/선 생성 불필요)
- 무기 시스템(SO): WeaponData로 피해/사거리/발사 속도/쿨타임/티어 정의 → BaseWeapon 공통 파이프라인 + CannonWeapon 파생
- 업그레이드 & 슬롯: WeaponSlotsManager가 슬롯 mount에 무기를 스폰/교체하고 레벨·티어 업 로직 처리, UpgradePanelController로 UI 연동
- 스테이지 & 스폰: StageManager 타이머로 Stage 상승, EnemyManager가 NavMesh 영역에 적 스폰(최대 생존 수 유지)
- Respawn 버튼: “가장 가까운 트랙 노드”로 즉시 복귀(속도·웨이포인트 리셋)
- UI/HUD: 체력/경험치/레벨, 골드, 속도(km/h), 적 머리 위 HP 빌보드
- 사운드: BGM + 풀링 기반 SFX + 슬라이더로 볼륨 제어
- 센서 & 이동 제어: 전방 센서로 정지/재가속, 런타임 파라미터 튜닝 샘플 제공
📁 프로젝트 구조(요약)
02. Scripts
├─ Base
│ ├─ BaseCondition.cs
│ ├─ BaseWeapon.cs
│ └─ Singleton.cs
├─ Camera
│ └─ FollowCamera.cs
├─ Controller
│ └─ UpgradePanelController.cs
├─ Enemy
│ ├─ AI
│ │ └─ EnemyAI.cs
│ ├─ Attack
│ │ ├─ EnemyAttack.cs
│ │ └─ EnemyCondition.cs
│ └─ Interface
│ └─ IDamageable.cs
├─ Manager
│ ├─ Enemy
│ │ └─ EnemyManager.cs
│ ├─ Player
│ │ ├─ PlayerManager.cs
│ │ └─ RespawnManager.cs
│ ├─ SkillManager.cs
│ ├─ CurrencyManager.cs
│ ├─ Weapon
│ │ └─ WeaponSlotsManager.cs
│ ├─ GameManager.cs
│ ├─ SoundManager.cs
│ └─ StageManager.cs
├─ Player
│ ├─ Skill
│ │ ├─ AttackController.cs (partial)
│ │ ├─ AttackController.Heal.cs (partial)
│ │ └─ AttackController.SpinAttack.cs (partial)
│ ├─ CarAI.cs
│ └─ PlayerCondition.cs
├─ Scriptable
│ └─ WeaponData.cs
├─ UI
│ ├─ Enemy
│ │ └─ EnemyHealthUI.cs
│ ├─ Player
│ │ ├─ CarSpeedUI.cs
│ │ └─ GoldUI.cs
│ └─ UIConditionBinder.cs
├─ Util
│ ├─ controllingCarAI.cs
│ └─ SensorManager.cs
└─ Weapon
├─ Projectile
│ └─ SimpleProjectile.cs
└─ CannonWeapon.cs
🧱 설계/디자인 패턴
- Singleton(제네릭): 매니저 전역 접근/중복 생성 방지
- Observer(이벤트 기반): PlayerCondition 이벤트 → UI가 구독해 즉시 갱신
- Strategy(무기 확장): BaseWeapon 공통 파이프라인 + 파생 무기
- Interface(결합도↓): IDamageable로 피해 처리를 통일
- Partial Class: AttackController를 Spin/Heal로 분리해 관심사 정리
- Data-Driven & Composition: ScriptableObject·컴포넌트 조합 중심 설계
🧰 사용 기술 & 시스템
- NavMesh: CarAI 웨이포인트 스냅, EnemyManager 스폰 샘플링
- WheelCollider + Rigidbody: GetWorldPose로 메시 동기화, 모터/브레이크/조향 제어
- Catmull-Rom Spline: 트랙 노드 사이 부드러운 경로 생성(루프/샘플 수 옵션)
- Physics Overlap/Trigger: Spin(OverlapSphere), 투사체/근접 충돌 → IDamageable
- TMP + UGUI: HUD/패널 구성
🚗 핵심 동작 요약
- CarAI: trackNodes → 스플라인 웨이포인트 생성 → 바퀴/조향/가속 갱신, carFront 기준 도달 판정, CurrentSpeedKmh 제공
- Respawn: 현재 위치(가능하면 carFront) 기준 최근접 노드 탐색 → CarAI.TeleportToNode() → 속도·각속도 초기화 + 웨이포인트 리셋
- 무기/업그레이드: 슬롯 mount에 프리팹 스폰, WeaponData로 레벨업/티어업, UI 반영
- 스테이지/스폰: 타이머로 Stage 상승, NavMesh 영역 내 최대 생존 수 유지
- UI: 플레이어 HUD + 적 HP 빌보드
🕹 플레이 방법
- 시작: 플레이하면 StageManager가 타이머를 돌리며 Stage가 주기적으로 상승
- 이동: CarAI가 trackNodes를 따라 주행(루프/랜덤/커스텀 경로). 전방 센서 접촉 시 일시 정지 후 재출발
- 전투: WeaponSlotsManager가 슬롯 mount에 무기 스폰 → BaseWeapon 파이프라인으로 자동 조준·발사
- 성장: 처치 보상으로 골드/EXP 획득 → 업그레이드 패널에서 레벨업/티어업
- 복구: 길 이탈 시 Respawn으로 최근접 트랙 노드 복귀
기본 흐름은 AI 주행 + 자동 사격. 입력형 조작을 추가하고 싶으면 별도 컨트롤러로 쉽게 확장 가능합니다.
🧪 씬 세팅 체크리스트
- 싱글톤 매니저 배치: Game/Stage/Enemy/Sound/Currency/Skill/WeaponSlots/PlayerManager
- 차량: CarAI + 바퀴(콜라이더/메시) + carFront 지정, trackNodes 등록
- 무기: WeaponData 생성 → 슬롯 mount에 연결(시작 시 자동 스폰)
- UI: 슬라이더/텍스트를 인스펙터로 바인딩
- Respawn 버튼: RespawnManager.RespawnToNearestTrackNode() OnClick
- NavMesh: 스폰 영역/트랙 주변 베이크
🧯 Troubleshooting (Deep Dive)
왜 발생했는가 → 어떻게 고쳤는가 → 재발 방지까지 실제로 겪었던 이슈 중심으로 정리.
1) 생성자/필드 초기화에서 FindObjectsOfType/Singleton.Instance 호출
현상
UIConditionBinder가 생성자/필드 초기화 타이밍에 Singleton.Instance를 참조하면서 UnityException 발생.
원인(Why)
MonoBehaviour는 생성자/필드 초기화 시점에 씬 오브젝트가 준비되지 않음. 이 타이밍의 FindObjectsOfType/Instance 접근은 금기.
해결(How)
- 참조 획득은 Awake/Start, 이벤트 구독은 OnEnable, 해제는 OnDisable.
- 가능하면 [SerializeField] 후 인스펙터에서 수동 바인딩.
재발 방지(Prevent)
- 팀 규칙: “필드 초기화/생성자에서 Singleton.Instance 금지”
- 코드리뷰 체크 항목에 추가.
2) PlayerManager.carTransform 미할당으로 Respawn 버튼 NullReference/UnassignedReference
현상
버튼 클릭 시 RespawnManager가 carTransform 접근하다 예외.
원인
- PlayerManager.Awake 실행 순서 지연, 또는 인스펙터 미바인딩.
- 씬 활성/비활성 순서에 따른 타이밍 이슈.
해결
- RespawnManager.ResolveRefs(): PlayerManager → 씬 검색 → car.transform 순으로 자체 복구 로직 추가.
- Script Execution Order로 PlayerManager.Awake 선행.
재발 방지
- 핵심 참조는 인스펙터 명시 바인딩 우선.
- 런타임에서도 한 번 더 널 가드.
3) UI OnEnable에서 즉시 SFX → SoundManager NRE
현상
UpgradePanelController.OnEnable() 내 PlaySfx() 호출 시 NRE.
원인
사운드 풀/오디오소스가 아직 초기화되지 않은 타이밍에 호출.
해결
- SoundManager.Awake()에서 풀 확실히 초기화.
재발 방지
- 가이드: “UI의 OnEnable에서 즉시 오디오 재생 금지”
- 씬 부팅 순서 문서화.
4) BaseCondition 보호된 필드 접근(CS0122)
현상
EnemyHealthUI에서 BaseCondition.health/maxHealth 직접 접근 시 접근 제한자 에러.
원인
캡슐화 위반: 외부 UI가 내부 상태를 직접 읽으려 함.
해결
- Public 프로퍼티/이벤트로 노출(Health, Health01, 변경 이벤트).
- UI는 이벤트를 구독해 슬라이더만 업데이트.
재발 방지
- 규칙: “UI는 이벤트/프로퍼티만 사용”.
5) 빌보드 HP UI 떨림/뒤집힘
현상
카메라 회전 시 HP 슬라이더가 순간적으로 뒤집히거나 흔들림.
원인
Update 순서/축 처리 미흡, LookAt이 Z축까지 뒤집음.
해결
void LateUpdate() {
var cam = Camera.main.transform;
Vector3 dir = cam.position - transform.position; dir.y = 0f; // 수평만
if (dir.sqrMagnitude > 0.0001f)
transform.rotation = Quaternion.LookRotation(dir);
}
재발 방지
- 월드 스페이스 UI는 LateUpdate에서 위치/방향 갱신 고정.
6) 투사체 임팩트 후 TrailRenderer/파티클 누수
현상
충돌 직후 본체 파괴 시 Trail/VFX가 같이 사라지거나, 반대로 고아 오브젝트로 남음.
원인
Trail이 본체에 붙은 채 파괴되거나, 별도 라이프사이클 미관리.
해결
- 충돌 시 Trail을 detach → 별도 타이머로 제거.
- Impact VFX는 풀링 또는 Destroy(go, duration)로 정리.
재발 방지
- 본체/트레일/임팩트 라이프사이클 분리 원칙.
7) NavMesh 스폰 지점이 벽/경계 밖으로 샘플링
현상
적이 벽 내부/경계 밖에서 등장하거나 추락.
원인
NavMesh.SamplePosition 반경 과소, areaMask 미설정.
해결
- 후보 지점에서 SamplePosition(point, out hit, **radius**, **areaMask**)로 여유 반경 제공.
- allowedAreaNames로 마스크 명시.
재발 방지
- 스폰 영역 Gizmo/BoxCollider 시각화, 반경/마스크 프리셋 유지.
8) WheelCollider 시각/물리 불일치(튐/박힘)
현상
속도가 오르거나 경사면에서 바퀴 메시가 뜨거나 섀시가 튄다.
원인
메시 동기화가 Update에서 이뤄짐, 물리 타임스텝/보간 불일치.
해결
- FixedUpdate에서 GetWorldPose(out pos, out rot)로 메시 동기화.
- Rigidbody Interpolate 사용, Fixed Timestep(권장 0.02s) 점검.
재발 방지
- 차량 비주얼 동기화는 항상 FixedUpdate.
9) 이벤트 중복 구독 → UI 두 배 갱신
현상
씬 재로드/패널 토글 후 HP/골드 텍스트가 중복 업데이트.
원인
OnEnable에서 AddListener만 하고 OnDisable 해제 누락.
해결
- 모든 구독은 OnEnable ↔ OnDisable 페어 관리.
- 필요 시 구독 전 RemoveListener로 1차 청소.
재발 방지
- 리뷰 체크리스트: “이벤트 구독 해제했나?”
10) Stage 타이머 드리프트/폭주
현상
시간이 지날수록 Stage 속도가 빨라지거나 UI가 밀림.
원인
Time.time 기반 반복이 정지/프레임 드롭에 영향, 코루틴 중복 실행.
해결
- 단일 코루틴 가드(isRunning) + WaitForSeconds.
- 일시정지 대응 필요 시 unscaledDeltaTime 누적.
재발 방지
- 모든 루프/타이머에 중복 실행 가드 + 정지/재개 시나리오 점검.
'Unity' 카테고리의 다른 글
Unity - Fog of War, 3D(XZ)에서 2D(XY)로 바꾸기 (0) | 2025.09.17 |
---|---|
Unity - 3D Squad Swarm (팀 프로젝트) (1) | 2025.09.05 |
Unity - Project_Up (3) | 2025.08.13 |
Unity - 3D Survival Game(인벤토리 기능) (6) | 2025.08.08 |
Unity - Inventory 인벤토리 시스템 구현 정리 (3) | 2025.08.07 |