최종 실습: 2D 택배 분류 게임 출시 후보 완성하기
20강. 최종 실습: 2D 택배 분류 게임 출시 후보 완성하기
1. 이번 강의에서 해결할 문제
기능이 각각 동작해도 시작부터 재시작까지 연결되고 긴 플레이와 잘못된 설정을 견뎌야 출시 후보가 됩니다. 전체 구조와 증거 문서를 하나의 완료 기준으로 묶습니다.
2. 학습 목표
3. 핵심 개념
최종 책임 흐름은 다음과 같습니다.
GameFlow ─상태─> Spawner / Input ─명령─> PackageActor
│ │
Difficulty + Selector ─데이터─────┘ Trigger 결과 1회
│
GameSession ─이벤트─> HUD / Audio / HighScoreStore
│
GameRules + PackageDefinition
GameFlow는 시스템 활성화, GameSession은 점수·목숨, Spawner는 생성 시점, Selector는 종류 순서, PackageActor는 이동·중복 차단, Presenter는 표시·사운드를 맡습니다.
핵심 Unity API와 책임 경계
| API·기능 | 호출 주체 | 구체 결과 | 주의할 점 |
|---|---|---|---|
Awake | 각 Component | Inspector 필수 참조와 값 범위를 첫 실행 전에 검증 | 오류를 기록한 뒤 잘못된 시스템 시작을 막습니다. |
StartCoroutine·WaitForSeconds | Spawner | 택배 생성과 다음 생성 사이의 대기 순서를 표현 | 중복 Coroutine과 Time Scale 정책을 확인합니다. |
OnTriggerEnter2D | Zone | PackageActor의 분류·놓침 결과를 1회 요청 | Collider·Rigidbody2D 설정과 TryResolve가 필요합니다. |
C# event | GameSession 발행, Presenter 구독 | 점수·목숨 변경이 HUD·Audio에 전달 | OnEnable·OnDisable 구독 대칭을 유지합니다. |
Time.timeScale | GameFlow | scaled 이동·대기를 일시정지 | UI 입력과 unscaled 동작은 별도로 제어합니다. |
PlayerPrefs | HighScoreStore | 비민감 최고 점수를 로컬에 유지 | 인증·결제·개인정보를 저장하지 않습니다. |
SceneManager.LoadScene | GameFlow | Scene 소유 런타임 객체를 새로 생성 | Build Scene 등록과 Time Scale 복원을 확인합니다. |
출시 후보에서는 “한 클래스가 모든 API를 호출한다”보다 API 결과가 어느 책임으로 전달되는지가 중요합니다. 물리 Trigger는 PackageActor의 한 번 처리 계약을 거쳐 GameSession에 도달하고, 점수 이벤트는 Presenter로만 나갑니다.
4. 단계별 실습
시작 전 상태
- 1~19강 결과가 같은 Unity 버전에서 컴파일되고 Main Scene을 Play할 수 있어야 합니다.
- Console 오류 0개, 현재 Git 상태와 ProjectVersion을 기록합니다.
- 최종 Scene과 Prefab을 복사해 새 구조로 다시 만들지 말고 기존 결과물의 책임과 참조를 점검합니다.
- 모든 기능을 동시에 켜기 전에 아래 출시 후보 게이트를 순서대로 닫습니다.
작은 수직 조각과 출시 후보 게이트
| 게이트 | 통합 범위 | 통과 증거 |
|---|---|---|
| 0 | Main Scene + GameFlow + GameSession | Start 버튼 뒤 Playing, 점수 0, 목숨 기본값 |
| 1 | PackageDefinition + Pool + PackageActor 한 개 | 한 종류가 생성·이동하고 올바른 Zone에서 결과 1회 |
| 2 | Selector + 두 종류 + 오분류·놓침 | 실제 Type과 Zone Type에 따라 점수·목숨이 손 계산과 일치 |
| 3 | Pause·GameOver·Restart | 입력·생성·Time Scale·Panel이 상태표와 일치 |
| 4 | Difficulty + 연속 제한 + 빠른 생성 | 최소 간격과 4회째 강제 변경, Pool 상태 초기화 확인 |
| 5 | HUD·Audio·HighScore | 이벤트 1회당 표시·소리 1회, 재시작 뒤 최고 점수 유지 |
| 6 | 15분 플레이 + 독립 빌드 | 지속 증가 없음, 시작부터 재시작까지 완료 |
각 게이트의 실패는 재현 입력 → 기대값 → 실제값 → 첫 불일치 책임 순서로 기록합니다. Gate 1이 실패한 상태에서 Difficulty와 Audio까지 켜지 않습니다.
4-1. 최종 프로젝트 구조
Assets/_Project/
├─ Art/ # 직접 제작 또는 라이선스 확인 에셋
├─ Audio/
├─ Data/ # GameRules, Difficulty, PackageDefinition
├─ Docs/ # 기획, 테스트, 알려진 문제, 포트폴리오
├─ Prefabs/Package.prefab
├─ Scenes/Main.unity
└─ Scripts/
├─ Core/ # GameSession, GameFlow, HighScoreStore
├─ Data/ # ScriptableObject 타입
├─ Gameplay/ # Spawner, Selector, Actor, Zone, Pool, Input
├─ UI/HudPresenter.cs
└─ Audio/AudioPresenter.cs
4-2. Inspector 연결 점검
Unity Editor·Inspector 작업
- Package Prefab에 SpriteRenderer, Collider2D, Rigidbody2D와 PackageActor를 연결합니다.
- Pool에 Prefab, Spawner에 Pool·SpawnPoint·Bounds·Difficulty·Selector를 연결합니다.
- Blue·Orange Zone에 각각 올바른 PackageDefinition을 연결합니다.
- Session에 GameRules, Flow에 Session·Spawner·Input·HighScoreStore를 연결합니다.
- HUD와 Audio Presenter의 이벤트 발행자·표시·클립 참조를 연결합니다.
필수 참조는 각 컴포넌트의 Awake에서 이름이 포함된 오류로 검증하고, null이면 해당 시스템 시작을 막습니다. Find 계열을 Update에서 호출하지 않습니다.
4-3. 구조 선택 근거
문서 · 파일 경로: Assets/_Project/Docs/Architecture.md
# 구조 선택 근거
- Update: 현재 택배의 연속 이동과 입력 읽기에만 사용한다.
- Coroutine: 택배 생성과 간격 대기를 순서대로 표현한다.
- Object Pool: 빠르게 반복되는 동일 Prefab의 생성·제거를 재사용한다.
- ScriptableObject: 택배·게임·난이도 설계 수치를 인스턴스와 분리한다.
- event: GameSession이 HUD와 Audio 구현을 모르게 한다.
4-4. 최종 테스트
문서 · 파일 경로: Assets/_Project/Docs/FinalTest.md
# 최종 테스트
- [ ] 시작 → 플레이 → 일시정지 → 재개 → 게임 오버 → 재시작
- [ ] 두 종류 이상과 대응 Zone, 정답 점수 증가
- [ ] 오분류·놓침에서 목숨 정확히 1 감소
- [ ] 점수·최고 점수·목숨·상태 UI 일치
- [ ] 3회 연속 허용, 4회째 다른 종류
- [ ] 시간 경과에 따른 간격·속도 변화와 최소값
- [ ] 0.1초 빠른 생성에서 Pool·중복 판정 오류 없음
- [ ] 택배가 없는 상황에서 오류 없음
- [ ] 목숨 0에서 입력·추가 생성 중단
- [ ] Inspector 연결 누락에서 구체적 오류
- [ ] 15분 플레이에서 메모리·비활성 객체·구독의 지속 증가 없음
4-4-1. 손으로 검산할 기준 시나리오
GameRules가 정답 +100점, 오분류·놓침 목숨 -1, 시작 목숨 3이라고 가정합니다. 실제 에셋 값이 다르면 표 숫자를 먼저 프로젝트 값으로 바꿉니다.
| 순서 | 행동 | 기대 점수 | 기대 목숨 | 기대 상태 |
|---|---|---|---|---|
| 1 | Blue → BlueZone | 100 | 3 | Playing |
| 2 | Orange → BlueZone | 100 | 2 | Playing |
| 3 | Blue → MissZone | 100 | 1 | Playing |
| 4 | Orange → MissZone | 100 | 0 | GameOver |
| 5 | Restart | 0 | 3 | Start 또는 프로젝트의 시작 대기 상태 |
Trigger가 겹치더라도 한 Package는 표의 한 행만 진행해야 합니다. HUD와 Audio는 GameSession의 같은 이벤트를 관찰하므로 실제 점수·목숨과 한 프레임 이상 어긋난 상태가 계속되면 구독 또는 초기 표시를 확인합니다.
4-5. 빌드 전과 알려진 문제
ReleaseChecklist.md에 Scene, 해상도, 아이콘, 저작권, 입력, Console과 독립 빌드 결과를 기록합니다. KnownIssues.md에는 재현 절차, 영향, 임시 대응과 다음 수정 항목을 작성합니다.
4-6. 포트폴리오 기록
문서 · 파일 경로: Assets/_Project/Docs/Portfolio.md
# Delivery Sorter Release
## 게임 소개
색과 아이콘을 빠르게 읽어 떨어지는 택배를 올바른 지점으로 보내는 2D 액션 게임입니다.
## 맡은 기능
게임 상태, 데이터 기반 난이도, 연속 생성 제한, 판정 이벤트, HUD, Pool과 로컬 최고 점수
## 기술적 문제 해결
겹친 Trigger의 중복 점수를 PackageActor.TryResolve로 한 번만 허용하고 재현 테스트를 추가했습니다.
## 핵심 재미와 현재 한계
- 재미: 짧은 판단과 즉시 피드백, 점차 빨라지는 압박
- 한계: 키보드 중심 입력, 2종 콘텐츠, 로컬 기록만 지원
- 다음 개선: 실제 기기 터치, 프로파일링, 튜토리얼·추가 택배
4-7. 최종 파일 확인
PowerShell · 실행 위치: 프로젝트 루트
Get-ChildItem .\Assets\_Project\Scripts -Recurse -Filter *.cs
Get-ChildItem .\Assets\_Project\Docs -Filter *.md
Get-Content .\ProjectSettings\ProjectVersion.txt
git status --short
예상 결과: 책임별 코드와 기획·테스트·출시·포트폴리오 문서, 정확한 Unity 버전이 보입니다. C# 컴파일과 Play Mode·빌드 검증은 Unity Editor와 독립 실행 파일에서 수행합니다.
4-8. 이후 확장 아이디어
- 모바일 입력: Input System Touch Action, 드래그 어댑터, Safe Area와 진동 피드백
- 성능 최적화: 실제 기기 Profiler, Sprite Atlas, 목표 FPS·메모리 예산
- 광고: 실제 SDK는 추가하지 않고 게임 규칙과 Adapter 경계, 테스트 광고·개인정보 정책을 먼저 설계
- 스토어 출시: 아이콘·스크린샷·서명 키·등급·개인정보와 현재 스토어 정책 점검
5. 코드가 동작하는 이유
설계 데이터와 런타임 상태, 생성과 판정, UI와 오디오가 이벤트 경계로 연결됩니다. 한 시스템을 바꾸어도 다른 책임의 내부 코드를 덜 수정하며, 상태 전환이 입력·생성·화면을 일관되게 조정합니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
따라 하기: 출시 후보 기준선 만들기
Gate 0~3만 켠 상태로 위 기준 시나리오를 실행합니다. 각 행마다 GameSession 값, HUD 텍스트, 활성 Panel, Spawner·Input 상태를 기록합니다. 네 행이 통과하기 전에는 난이도나 새 콘텐츠를 추가하지 않습니다.
값과 실패 바꿔 보기
- Difficulty의 최소 생성 간격을 0.1초로 낮춰 Pool 부족, 중복 판정, Frame Spike를 재현하고 Profiler 캡처를 남깁니다.
- Session·Pool·TMP·AudioClip 참조를 하나씩 비워
Awake가 구체적인 필드·오브젝트 이름을 보고하는지 확인합니다. - BlueZone과 MissZone을 겹쳐 PackageActor의 TryResolve가 첫 결과만 허용하는지 검사합니다.
- Pause 직후 GameOver와 Restart를 반복해 Time Scale, 이벤트 구독과 비활성 Pool 개수가 누적되지 않는지 확인합니다.
직접 확장
출시 후보 기준선과 독립 빌드가 통과한 별도 브랜치에서만 세 번째 PackageDefinition과 Zone을 추가합니다. Selector 목록·데이터·Inspector 연결만 바꾸고 GameSession과 Presenter에 종류별 분기를 추가하지 않는 것을 목표로 합니다. 확장 후 Gate 1~6을 다시 실행해 회귀 결과를 남깁니다.
스스로 확인
막혔을 때
| 증상 | 첫 확인 책임 | 해결 방향 |
|---|---|---|
| Start 뒤 아무 Package도 안 나옴 | GameFlow → Spawner → Pool | 상태 로그, Coroutine Handle, Pool Prefab과 SpawnPoint 순으로 확인합니다. |
| 점수·HUD·소리가 서로 다름 | GameSession event → Presenter | 규칙 원본 값과 구독 수를 기록하고 Presenter가 값을 재계산하지 않게 합니다. |
| 재시작할수록 Callback 증가 | OnEnable·OnDisable 또는 DontDestroyOnLoad 중복 | 구독 해제와 Scene에 남는 전역 객체 수를 확인합니다. |
| Editor에서는 되지만 독립 빌드 실패 | Build Scene·플랫폼 설정·대소문자 경로 | Player.log와 ReleaseChecklist의 첫 오류부터 수정합니다. |
| 긴 플레이에서 메모리 계속 증가 | Pool 반환·이벤트·로그·에셋 할당 | Profiler Timeline과 Memory 비교로 증가 주체를 특정합니다. |
모든 통과와 실패를 기록한 뒤 핵심 재미, 한계, 다음 개선, 맡은 기능과 기술적 문제 해결 경험을 완성합니다.
8. 이해 점검 질문 3개
9. 핵심 요약
최종 실습: 2D 택배 분류 게임 출시 후보 완성하기 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.