가족 미니게임 웹앱 miniplay 제작기

Next.js 15 + Zustand + Web Audio API로 만든 가족용 벌칙 미니게임 모음 PWA 개발 후기

miniplay 메인 화면

🎮 플레이하러 가기 → miniplay.kr

네 줄 요약

  • 가족끼리 모여서 할 수 있는 벌칙/파티 미니게임 7종을 웹앱으로 만들었다
  • 스택: Next.js 15 (App Router) + TypeScript + Tailwind CSS + Zustand + Web Audio API
  • 설치 없이 즉시 플레이 — PWA로 홈 화면에 추가하면 앱처럼 동작
  • 효과음과 BGM을 외부 오디오 파일 없이 Web Audio API로 전부 합성해서 만들었다

1. 왜 만들었나

집에서 아들과 보드게임 대신 뭔가 가볍게 할 수 있는 게 없을까, 하는 생각에서 시작했습니다.

앱 스토어에서 비슷한 앱을 찾아보면 광고가 도배되어 있거나(가장 큰 문제), 설치해야 하거나, 인원 설정이 안 되거나. “그냥 URL 하나로 바로 시작할 수 있는 가족 게임” 이 있으면 좋겠다는 생각이 들었습니다.

조건은 딱 네 가지. 설치 없이 URL 하나로 바로 시작, 2~6명이 쓸 수 있는 차례 관리, 아이들이 봤을 때 반응할 수 있는 소리와 애니메이션, 그리고 와이파이 없어도 되는 오프라인 지원.


2. 게임 라인업

우선 7종의 미니게임을 만들었습니다. 모두 벌칙 결정 또는 순서 정하기 용도로 쓸 수 있습니다.

게임 설명
🪜 사다리 게임 사다리를 직접 만들어 운명을 결정
🎡 숫자 룰렛 금지 숫자 모드 / 미션 모드
👉 화살표 스핀 화살표가 가리키는 사람이 벌칙
🐊 악어 이빨 12개 이빨 중 1개가 함정
🐹 쏙쏙 햄찌 두더지 잡기 (3단계 난이도)
💣 째깍 폭탄 폭탄 터지기 전에 넘겨라
🎈 풍선 팡 펌프질할수록 위험 — 언제 터질지 모른다

게임마다 독립적으로 동작하지만, 플레이어를 등록하면 차례 관리와 벌칙 카운트가 자동으로 돌아갑니다.


3. 기술 스택과 선택 이유

Next.js 15 (App Router)

홈 페이지는 Server Component로 SEO를 확보하고, 각 게임 페이지는 'use client'로 인터랙션에 집중했습니다. 게임 7개가 모두 독립적인 라우트(/game/balloon, /game/bomb 등)이기 때문에 App Router의 파일 기반 라우팅이 딱 맞았습니다.

Zustand — 전역 상태 관리

플레이어 목록, 점수, 현재 차례를 전역으로 관리해야 했습니다. Redux는 이 규모에 쓰기 좀 민망했고, Context API는 예전에 리렌더링으로 고생한 기억이 있어서 처음부터 Zustand로 갔습니다.

miniplay-players 키 하나로 플레이어 정보를 localStorage에 동기화합니다. 게임 간에 이동해도 차례가 날아가지 않습니다.

// 플레이어를 등록하면 게임 간 이동해도 차례가 유지된다
const useGameStore = create<GameState>((set, get) => ({
  players: [],
  scores: [],
  turn: 0,
  nextTurn: () => set({ turn: (get().turn + 1) % get().players.length }),
  // ...
}));

Tailwind CSS — 글래스모피즘 + 커스텀 애니메이션

가족 게임답게 파스텔 톤 + 글래스모피즘 디자인을 잡았습니다. Tailwind config에 15개 이상의 커스텀 키프레임을 등록했습니다. balloon-shake, bomb-tick, screen-shake, particle-fly 같은 게임별 전용 애니메이션이 핵심입니다.

Web Audio API — 파일 없는 사운드 시스템

이 프로젝트에서 가장 재미있었던 부분입니다.

MP3나 WAV 파일을 단 하나도 사용하지 않았습니다. 모든 효과음과 BGM을 Web Audio API의 오실레이터와 게인 노드로 실시간 합성합니다.

// 사인파 톤 하나로 클릭음, 위험 알림, 팡파레까지 만든다
tone(freq: 400, type: 'sine', duration: 0.14, volume: 0.28, endFreq: 800)
noiseBurst(vol: 0.6, decay: 0.08)  // 노이즈 버스트로 타격감 표현
  • useAudio 훅: 클릭, 틱, 경고, 안전, 위험, 팡파레, 폭발 등 10종의 효과음
  • useBgm 훅: 160 BPM, C 메이저 스케일, 8마디 루프의 배경음악

외부 오디오 파일이 없으니 빌드 결과물이 훨씬 가볍습니다. 브라우저 자동재생 정책도 첫 터치 시 AudioContext를 초기화하는 걸로 해결했고요.


4. 턴 시스템과 벌칙 오버레이

플레이어 관리

2~6명까지 등록할 수 있고, 프리셋 이름(엄마, 아빠, 형, 누나 등)을 원터치로 추가하거나 직접 입력할 수 있습니다. 드래그 앤 드롭으로 순서도 변경 가능합니다.

벌칙 오버레이

벌칙에 걸리면 풀스크린 오버레이가 뜹니다. 22개의 랜덤 파티클이 날아가고, 큰 이모지가 흔들리는 애니메이션이 재생됩니다. 아이들이 이 연출에 반응이 좋아서 꽤 공을 들였습니다.

점수는 해골 아이콘으로 누적 표시되고(최대 5개), 게임을 전환해도 상태가 유지됩니다.


5. PWA로 만든 이유

“엄마 그 게임 앱 어디 있어?” 라는 질문을 원천 차단하기 위해서입니다.

  • 홈 화면에 추가하면 네이티브 앱처럼 전체 화면으로 실행
  • Service Worker로 오프라인 캐싱 — 와이파이 안 되는 차 안에서도 동작
  • @ducanh2912/next-pwa로 Next.js와 통합, 프로덕션에서만 활성화

푸시 알림도 구현했습니다. VAPID 키 기반 브라우저 푸시로, Redis에 구독 정보를 저장하고 관리자 API로 일괄 발송하는 구조입니다.


6. 피드백 시스템

앱 내에 플로팅 피드백 버튼을 달았습니다. 사용자가 카테고리를 선택하고 메시지를 작성하면, GitHub Issue로 자동 생성됩니다.

앱 스토어 리뷰가 없으니 이게 유일한 피드백 채널입니다. GitHub API 연동이라 따로 백엔드를 만들 필요가 없었고, Issue 트래커에서 바로 관리할 수 있어서 편합니다.


7. 만들면서 느낀 것

사운드가 게임의 반을 먹는다

처음엔 시각적 피드백만으로 충분할 거라고 생각했습니다. 근데 아이들한테 처음 보여줬을 때 반응이 딱 “재미없어”였습니다. 소리 넣고 나서야 눈빛이 달라졌습니다. Web Audio API로 직접 합성하는 건 처음이라 이것저것 삽질이 있었는데, 막상 다 만들고 나니 이 프로젝트에서 가장 재미있었던 부분이 됐습니다.

가족이 사용자면 피드백이 즉각적이다

기획 → 개발 → 테스트가 저녁 식탁에서 한 번에 일어납니다. “이거 버튼이 너무 작아”, “여기서 소리 나면 좋겠어” 같은 피드백이 실시간으로 오고, 그날 바로 고칩니다. 사이드 프로젝트 동기 부여 걱정? 매일 옆에서 써주는 사람이 있었으니 그건 없었습니다.

PWA는 가족 앱에 최적

코드 푸시하면 다음에 켤 때 바로 반영됩니다. 앱 스토어 심사 대기도, 버전 관리 걱정도 없습니다. 가족끼리 쓰는 용도에 네이티브 앱을 만드는 건 솔직히 오버입니다.


🔗 링크