이전에 진행했던 개인 프로젝트에서 스스로도 고쳐야겠다고 생각했고, 튜터님도 보완이 필요하다고 말씀해주신 부분을 개선했다.
이전 프로젝트와 리팩토링한 버전의 프로젝트 링크이다.
https://github.com/00ovo00/HighRoWin
https://github.com/00ovo00/HighRoWin2
문제점
보다시피 Item, MovingObject, StationaryObject 모두 공통되면서도 다른 부분이 있는데 개별로 관리하고 있어 중복된 코드가 많으며 한눈에 들어오지 않는다. 특히 풀에 반환하는 시점을 코루틴으로 관리하고 있어 시간이 지나면 자동으로 반환되는 문제가 있다.
| 오브젝트 | 차이점 | 공통점 |
| Item | 플레이어와 트리거되면 풀로 바로 반환 | ObjectPooling 대상 Scriptable Object 데이터 기반으로 초기화 일정 범위 내 랜덤 위치에서 스폰 도로 위에 있지 않으면 풀로 반환 활성화된 리스트를 각 타입에서 관리 |
| MovingObject | 움직이는 방향을 설정하는 과정 필요 플레이어와 트리거되면 게임 종료 도로 바깥쪽으로 벗어나면 풀로 반환 |
|
| StationaryObject | - |
공통점은 추상클래스나 인터페이스를 통해 공통으로 정의하고, 차이점은 이를 상속 받는 각 클래스에서 override 하여 사용하도록 설계했다.
리팩토링 목표
초기 버전은 각 스크립트가 개별적으로 풀링·스폰·수명 관리 로직을 가지고 있어 중복과 결합도가 높다.
이번 리팩토링의 핵심은 다음과 같다.
- 공통 행위를 추상화해 코드 복사를 제거
- 상속·인터페이스를 통해 타입 안전하게 풀링/스폰을 일원화
- 확장-친화적인 구조를 설계
설계 선택 배경
- 인터페이스(ISpawnable)
낮은 결합 + 넓은 호환성.
풀 매니저나 외부 시스템은 ISpawnable만 알면 어떤 객체든 동일하게 다룰 수 있다. - 추상 + 제네릭(BaseSpawner)
제네릭 파라미터로 타입 안정성 확보(컴파일 시점 오류 발견).
스포너를 몇줄만 고쳐 새 타입을 만들 수 있어 확장성 극대화. - 템플릿 메서드 패턴
BaseSpawner가 스폰 흐름(타이밍·위치)을 제어하고, 세부 구현을 하위 클래스에 넘김 → 개방/폐쇄 원칙(OCP) 준수. - PoolableObject의 공통 로직
지형 레이캐스트 기반 자동 회수 → 과도한 코루틴 삭제로 GC 감소.
OnSpawned/OnDespawned로 느슨한 연결.
와이어프레임

핵심 구성요소
| 모듈 | 역할 | 주요 포인트 |
| ISpawnable | OnSpawned / OnDespawned / ReturnToPool / IsOnRoad 메서드 정의 | 어떤 객체든 “풀링 가능” 행동을 보장 |
| PoolableObject | MonoBehaviour + ISpawnable 추상클래스 | 공통 필드 poolTag, 레이캐스트로 자동 반환 로직 |
| BaseSpawner<T, TSO> (제네릭) | 스폰 공통 메커니즘(월드 좌표 계산 등) | 타입 제한으로 타입 안전 보장 |
| Item MovingObject StationaryObject |
각 객체의 개별 동작만 기술 | 공통 풀 관리 메서드는 상위 클래스에 의존 |
| ItemSpawner MovingObjectSpawner StationaryObjectSpawner |
BaseSpawner 상속, SO 데이터에 따라 객체 스폰 | 3줄 내외의 로직 추가로 구분 |
이전 코드와의 비교
| 항목 | 초기 버전 | 현재 버전 | 개선 이점 |
| 스폰 로직 | 각 Spawner가 OnEnable() 안에서 1) 확률 체크 → 2) PoolingManager.SpawnFromPool() → 3) 각종 초기화 호출을 직접 수행 |
스폰 규칙은 BaseSpawner에, “데이터마다 다를 부분”만 오버라이드 |
- 코드 중복 제거 - 새 오브젝트 추가 시 스포너 코드는 10줄 내외 |
| 풀링 대상 클래스 | MonoBehaviour → 각각 풀 태그·반환 방법을 직접 보관 | PoolableObject 상속 + ISpawnable 인터페이스 | - 단일 책임: 공통 로직 한곳 관리 - 풀로 반환 시 타입 누락/오타 방지 |
| 데이터 주입 | 각 스포너 내부에서 값 하드코딩 혹은 SO 일부만 활용 | SO + 제네릭으로 타입 연결 BaseSpawner<MovingObject, MovingSO> |
- SO 필드 ↔ 스크립트 오타 방지 - 밸런스 조정 시 데이터 파일만 수정 |
| 도로 이탈 처리 | 객체별로 FixedUpdate()에 벽 트리거·타이머 코루틴 등 다양 | PoolableObject.Update()에서 레이캐스트 통합 처리 | - 풀 반환 로직 일원화 → 버그 감소 |
개선 효과
| 지표 | Before | After | 효과 |
| 스포너 클래스당 평균 LOC | ≈ 100 | ≈ 25 | LOC –75% 감소 |
| 신규 오브젝트 추가 시 수정 파일 수 | 3–4개 | 1개(SO) + 선택적 새 Spawner | 새 기능 추가 개발 속도 향상 |
| 풀 반환 누락/중복 버그 | 자주 발생 | PoolableObject.ReturnToPool() 단일 경로 | 버그 재발 0건, 디버깅 용이 |
이번 리팩토링을 진행하며 공통 로직의 추상화를 학습하고 프로젝트를 더 좋은 구조로 개선할 수 있었다.
'프로젝트 기록' 카테고리의 다른 글
| [트러블슈팅] 버튼 이벤트 인덱스 오류 (0) | 2025.10.01 |
|---|---|
| [Warning-of-the-Monsters] 인간 상태 머신 설계 (0) | 2025.05.24 |
| UIManager 설계 (0) | 2025.05.24 |
| PoolManager 설계 (0) | 2025.05.24 |