Flutter 플러그인 0개로 게임을 출시했다 — Tidy Trove 출시기
Windows 개발자 모드가 막혀서 pub get이 플러그인을 설치할 수 없었다. 저장·오디오·광고를 전부 MethodChannel로 직접 구현하고 Google Play에 출시하기까지.
Tidy Trove는 안개 덮인 9×9 보드에 블록을 배치해 칸을 발굴하고 너구리를 구출하는 캐주얼 퍼즐이다. Google Play에 정식 출시했고, 지금도 라이브다. 이 글은 그 과정에서 겪은 제일 이상한 제약 — Flutter 플러그인을 하나도 못 쓰는 환경 — 을 어떻게 뚫었는지에 대한 기록이다.
pub get이 안 된다고?
시작은 황당했다. shared_preferences를 추가하고 flutter pub get을 돌렸는데 실패.
원인을 파보니 개발 머신의 Windows 개발자 모드가 꺼져 있었고, 이 상태에선 pub get이
플러그인 패키지 설치에 필요로 하는 NTFS 심볼릭 링크를 만들 수 없다. 개발자 모드를
켜면 끝나는 문제지만, 이 머신에선 정책상 켤 수 없었다.
선택지는 둘이었다. 환경을 바꾸거나, 플러그인 없이 가거나. 환경을 바꿀 수 없으니 답은 정해져 있었다 — 네이티브 기능을 전부 직접 구현한다.
MethodChannel로 다 만들기
Flutter의 MethodChannel은 Dart와 네이티브(Kotlin) 사이의 얇은 다리다. 플러그인들이 내부적으로 쓰는 그 통로를 직접 쓰면, 플러그인 없이도 네이티브 기능에 닿을 수 있다.
| 필요한 것 | 보통 쓰는 플러그인 | 직접 만든 것 |
|---|---|---|
| 게임 저장 | shared_preferences | tidytrove/paths 채널 → Android filesDir에 파일 저장 |
| BGM/SFX | just_audio | tidytrove/audio 채널 → SoundPool(효과음) + MediaPlayer(배경음) |
| 광고 | google_mobile_ads | tidytrove/ads 채널 → AdMob 전면/리워드 SDK 직접 연결 |
전부 MainActivity.kt 안에 있다. 각 채널은 메서드 이름과 인자를 받아 네이티브 API를
호출하고 결과를 돌려주는 단순한 구조다. 플러그인이 해주던 일을 손으로 쓰는 것뿐인데,
막상 해보니 생각보다 코드가 적었다 — 필요한 기능만 만들면 되니까.
잃은 것과 얻은 것
잃은 것: iOS 대응. 플러그인은 양쪽 네이티브 코드를 다 들고 오지만, 직접 구현은 Android만 썼다. iOS를 하려면 Swift로 같은 채널을 한 번 더 써야 한다. Android 우선 출시라 감수했다.
얻은 것: 의존성 0의 홀가분함. 플러그인 버전 충돌, 메이저 업데이트 마이그레이션, “이 플러그인이 내부에서 뭘 하는지 모르겠다” 같은 문제가 아예 없다. 앱 크기도 줄었고, 광고 SDK 초기화 타이밍 같은 민감한 부분을 직접 제어할 수 있었다.
게임 자체는 Flame으로
게임 루프와 렌더링은 Flame 엔진에 맡겼다. 9×9 보드, 안개 발굴, 블록 드래그 — 전부 Flame의 컴포넌트 시스템 위에서 돈다. Flutter는 메뉴·도감·설정 같은 UI 셸을 담당한다. 이 분업은 만족스러웠다. UI는 위젯으로, 게임은 게임 엔진으로.
100개 스테이지는 손으로 만들고 stage_data.dart에 상수로 박았다. 스테이지 생성기와
밸런스 시뮬레이터를 Dart CLI 툴로 만들어서 난이도 곡선을 검증했다.
배운 것
- 제약이 설계를 단순하게 만든다. 플러그인을 못 쓰니 “정말 필요한 네이티브 기능이 뭔가”를 먼저 묻게 됐고, 답은 셋뿐이었다(저장·소리·광고).
- MethodChannel은 무섭지 않다. 플러그인 소스를 한 번 열어보면 안다 — 그들도 결국 같은 통로를 쓴다.
- 출시가 스펙을 이긴다. iOS 포기, 플러그인 포기 같은 결정 덕에 게임이 스토어에 올라갔다. 완벽한 크로스플랫폼 구조를 고민만 했다면 아직 출시 못 했을 거다.
Tidy Trove는 Google Play에서 무료로 받을 수 있다.