ParryByte 제작기
ParryByte 제작기
이번에는 ParryByte를 만든 과정을 정리해보려고 한다.
2026년 6월 11일 기준 최종 수정본에 맞춰 앱인토스, 원스토어 Android, 원스토어 Windows PC 포팅 내용과 새 실행 화면을 다시 반영했다.
ParryByte는 한 줄로 말하면 이런 게임이다.
1
작은 용량 안에 넣은 패링 액션 탄막 게임
처음에는 macOS에서 바로 실행되는 네이티브 앱으로 시작했다.
웹 브라우저에서 여는 게임이 아니라,
ParryByte.app을 더블클릭해서 실행하는 앱을 목표로 잡았다.
그리고 나중에는 같은 게임을 앱인토스, 원스토어 Android, 원스토어 Windows PC 버전으로도 옮겼다.
아래는 현재 앱인토스 WebView 버전을 실제로 실행해서 다시 촬영한 화면이다.
처음 목표는 1.44MB 안에 게임을 넣는 것이었다
처음 조건은 꽤 빡빡했다.
압축 해제 후 앱 파일 총량 1,474,560바이트 이하.
대충 게임 하나 만들고 이미지를 넣고 음악을 넣으면 금방 넘을 수 있는 크기다.
그래서 처음부터 방향을 이렇게 잡았다.
1
2
3
4
이미지 파일을 넣지 말자.
사운드 파일도 넣지 말자.
런타임도 크게 가져가지 말자.
전부 코드로 만들자.
macOS 버전은 Objective-C + Cocoa로 만들었다.
핵심 게임 코드는 src/ParryByte.m 하나에 들어가고,
아이콘도 tools/make_icon.m에서 직접 그려서 .icns로 생성한다.
빌드는 아주 단순하다.
1
2
make app
open build/ParryByte.app
검증도 같이 붙였다.
1
2
make smoke
make size
최종 수정본을 다시 빌드/검증하면서 확인한 결과는 이랬다.
1
2
3
4
5
bytes_logic_ok=yes
app_payload_bytes=193988
limit_bytes=1474560
margin_bytes=1280572
within_1_44mb=true
앱 페이로드가 약 194KB라서,
1.44MB 제한 안에 꽤 넉넉하게 들어왔다.
그림과 사운드를 파일이 아니라 규칙으로 만들었다
용량을 줄이려면 제일 먼저 자산을 의심해야 했다.
보통 게임은 스프라이트, 배경 타일, 이펙트, 아이콘, BGM, SFX 파일이 들어간다.
ParryByte는 이걸 거의 다 코드로 바꿨다.
| 요소 | 구현 방식 |
|---|---|
| 캐릭터 | 8×8 마스크와 팔레트 조합 |
| 몬스터 | 같은 마스크를 색상, 크기, 움직임으로 변형 |
| 배경 | 스테이지 팔레트와 선/타일 패턴으로 생성 |
| 파티클 | 사각형, 링, 흔들림 효과를 실시간 생성 |
| 아이콘 | Objective-C 코드로 도트 전사와 로고를 그림 |
| BGM/SFX | AudioQueue로 파형을 합성 |
가장 마음에 들었던 부분은
같은 8×8 마스크를 계속 재사용하면서도 다른 캐릭터처럼 보이게 만든 것이다.
작은 도트 전사 하나를 기준으로,
색상과 실루엣과 이펙트만 바꿔도 꽤 게임처럼 보였다.
그리고 소리도 .mp3나 .wav를 넣지 않았다.
패링, 베기, 피격, 클리어 같은 효과음을 코드에서 짧은 파형으로 만들고,
배경음도 간단한 음계와 리듬을 조합해서 합성했다.
덕분에 파일 수가 줄고,
앱 번들도 훨씬 작아졌다.
게임의 중심은 패링이었다
이 게임에서 제일 중요한 버튼은 공격보다 패링이다.
공격만 누르면 평범한 액션 게임이 되기 쉽다.
그래서 일부 적과 탄막은 패링을 해야 유리하도록 만들었다.
기본 흐름은 이렇다.
1
2
3
4
5
적이 공격한다
→ 타이밍에 맞춰 패링한다
→ 탄을 반사하거나 적을 밀쳐낸다
→ 패링 게이지가 차면 베기가 강화된다
→ POWER x2로 공격한다
패링에 성공하면 점수와 콤보가 올라가고,
화면 흔들림, 링 이펙트, 짧은 히트스톱이 같이 나온다.
반대로 패링을 열었는데 아무것도 막지 못하면 parryMisses가 기록된다.
그냥 방어 버튼이 아니라,
플레이 기록에 남는 핵심 행동으로 만든 것이다.
특히 방패 몬스터는 일반 베기나 반사탄으로 방패가 깨지지 않는다.
직접 패링해야만 방패가 깨진다.
이 규칙 하나만으로도 플레이어가 “공격만 누르면 안 되겠구나”를 배우게 된다.
5개 스테이지와 보스를 작게 쪼개서 만들었다
처음부터 거대한 월드를 만들기보다,
작은 구조를 반복해서 완성도를 올리는 쪽을 택했다.
게임 구조는 이렇게 잡았다.
1
2
3
5 Stages × 5 Steps
각 Stage의 Step 5에서만 Boss 등장
Stage를 클리어할 때마다 HP 2 회복
각 스테이지는 이름, 보스, 팔레트, 기믹, 새 몬스터를 가진다.
| Stage | 보스 | 핵심 느낌 |
|---|---|---|
| 성문 박자 | 드럼 코어 | 부채꼴 탄막과 지뢰 |
| 화염 송곳 | 번 램 | 순간이동 후 돌진 |
| 방패 공방 | 미러 불워크 | 패링으로 깨는 방벽 |
| 낙뢰 회랑 | 스톰 아이 | 회전 탄막과 번개탄 |
| 패링 왕좌 | 바이트 킹 | 돌진, 탄막, 방패, 1회 부활 |
한 보스에게 모든 기믹을 몰아넣지 않고,
스테이지마다 하나씩 특징을 나눠서 넣었다.
그래야 작은 게임이라도 뒤로 갈수록 새로워진다.
난이도는 숫자 몇 개로 체감이 달라지게 만들었다
난이도는 Easy, Normal, Hard, Byte 네 단계로 뒀다.
단순히 적 체력만 올리면 지루하다.
그래서 반응 시간과 리듬이 달라지도록 수치를 바꿨다.
- 적 이동/공격 속도 증가
- 투사체 속도 증가
- 공격 전 경고 시간 감소
- 공격 쿨다운 감소
- 플레이어 패링 지속시간 감소
- 패링 재사용 대기시간 증가
즉 높은 난이도에서는 적이 더 빨라지고,
플레이어가 패링할 수 있는 시간은 더 짧아진다.
이 게임의 핵심이 패링이므로,
난이도도 패링 타이밍을 중심으로 조절했다.
작은 게임이어도 결과 화면과 엔딩은 넣고 싶었다
용량이 작다고 해서 게임이 갑자기 끝나면 아쉽다.
그래서 패배 결과창과 엔딩 크레딧을 넣었다.
패배하면 이런 정보가 나온다.
- 생존 시간
- 도달한 Stage/Step
- 처치 수
- 입힌 피해
- 받은 피해
- 패링 횟수
- 패링 미스 횟수
마지막 보스를 쓰러뜨리면 바로 끝나지 않고,
슬로우모션으로 보스가 쓰러지고,
화면이 어두워진 뒤 엔딩 크레딧이 올라간다.
크레딧에는 처치 수, 패링 횟수, 공격 횟수, 받은 피해, 입힌 피해, 클리어 시간까지 나온다.
작은 게임이라도 플레이어가 한 판을 끝냈다는 느낌을 주고 싶었다.
설정과 제작자 메뉴도 넣었다
처음에는 그냥 키보드 조작만 있으면 된다고 생각했다.
그런데 플레이하다 보니 사람마다 편한 키가 다를 수밖에 없었다.
그래서 설정 창을 넣었다.
- 이동 키 변경
- 패링 키 변경
- 베기 키 변경
- 설정 키 변경
- Music on/off
- SFX on/off
- Music/SFX 볼륨 조절
그리고 C를 누르면 제작자 메뉴가 열린다.
여기에는 KMokky 제작 표기와 연락처만 넣고,
로컬 경로나 불필요한 개인정보는 넣지 않도록 했다.
작은 디테일이지만,
공모전 제출이나 스토어 제출을 생각하면 이런 부분도 은근히 중요했다.
앱인토스 버전으로 옮기기
macOS 네이티브 버전이 어느 정도 완성된 뒤에는,
같은 게임을 앱인토스 WebView 미니앱으로 옮겼다.
여기서는 Objective-C가 아니라
TypeScript + Canvas + Web Audio 구조로 다시 만들었다.
앱인토스 버전에서 중요했던 건 세 가지였다.
1
2
3
모바일 가로 화면
터치 조이스틱 + 오른쪽 버튼
토스 게임 SDK 연동
왼쪽에는 조이스틱,
오른쪽에는 PARRY, SLASH 버튼을 뒀다.
그리고 앱인토스 SDK를 통해 이런 흐름을 붙였다.
setDeviceOrientation()으로 가로 화면 요청getUserKeyForGame()으로 게임 유저 식별키 획득- 실패하면 로컬 게스트 키 fallback
- 게임 종료 시
submitGameCenterLeaderBoardScore()호출 - Rank 버튼에서
openGameCenterLeaderboard()호출
로컬 브라우저나 샌드박스에서는 SDK가 항상 동작하지 않을 수 있다.
그래서 실패하면 게임이 멈추는 게 아니라,
로컬 기록으로 자연스럽게 넘어가도록 만들었다.
검증도 붙였다.
1
2
3
4
cd Toss
npm run assets
npm run check
npm run test
최종 수정본 기준으로 타입 체크, 스모크 테스트, 원본 ParryByte 동일성 감사가 모두 통과했다.
1
2
ParryByte Toss smoke checks passed
ParryByte Toss parity audit passed
원스토어 Android 버전으로 옮기기
다음은 원스토어 Android 빌드였다.
구조는 Android Activity가 가로 모드 WebView를 열고,
게임 본문은 로컬 android_asset에 들어 있는 Canvas/Web Audio 앱으로 실행하는 방식이다.
중요한 방향은 이것이었다.
1
네트워크 없이 실행되는 로컬 게임 앱
그래서 원스토어 Android 버전은
로그인, 광고, 결제, 원격 분석 없이 동작한다.
권한도 진동 정도만 사용하고,
최고 기록은 기기 로컬 저장소에 저장한다.
원스토어 제출을 생각해서 패키지명, 가로 화면, 런처 아이콘, 스토어 이미지, WebView 설정도 같이 점검했다.
검증 명령은 이렇다.
1
2
cd OneStore
npm run test
결과는 통과였다.
1
ParryByte OneStore smoke checks passed
원스토어 Windows PC 버전이 제일 복잡했다
가장 예상보다 커진 부분은 원스토어 Windows PC 버전이었다.
처음에는 “Windows에서도 HTML 게임을 띄우면 되지 않을까?” 정도였는데,
스토어 제출을 생각하면 단순히 실행 파일 하나만 있으면 끝이 아니었다.
Windows PC 버전은 이런 구조가 됐다.
1
2
3
4
5
6
Setup.exe
→ %LOCALAPPDATA%\ParryByte 에 설치
→ ParryByte.exe 실행
→ launch.ps1 호출
→ 로컬 www/index.html을 file:// URI로 변환
→ Microsoft Edge 앱 창으로 실행
launch.ps1에서는 설치된 HTML 경로를 System.Uri.AbsoluteUri로 바꾼 뒤,
Edge가 있으면 --app=file://... 형태로 실행한다.
없으면 기본 방식으로 HTML을 열게 했다.
여기서 신경 쓴 부분은 경로 문제였다.
사용자 폴더에는 공백이나 한글이 들어갈 수 있다.
그래서 문자열을 대충 이어 붙이는 대신,
literal path와 URI 변환을 최대한 안전하게 사용했다.
Windows 버전은 제출 자료도 같이 만든다.
- 업로드용
Setup.exe - portable ZIP
- submission pack ZIP
- 원스토어 입력값 metadata
- 제출 runbook
- SHA256 체크섬
- 설치 검증 PowerShell
- 서명 스크립트
- E2E 검증 스크립트
- 보안 스캔 스크립트
- 최종 증거 번들 생성 스크립트
macOS에서 로컬로 만들 수 있는 부분은 자동화했지만,
Windows 실기기 설치/실행/제거,
Authenticode 서명,
Windows Defender 스캔,
IARC 등급과 원스토어 In-App SDK 판정은 외부 게이트로 남겨뒀다.
이 부분은 “코드 완성”과 “스토어 제출 준비”가 다르다는 걸 많이 느낀 부분이었다.
검증은 이렇게 했다.
1
2
cd OneStorePC
npm run test
이번 확인 결과는 통과였다.
1
ParryByte OneStorePC smoke checks passed
만들면서 느낀 점
이번 프로젝트에서 제일 크게 느낀 건,
제약이 있으면 오히려 방향이 선명해진다는 점이었다.
1.44MB 제한이 없었다면 그냥 이미지도 넣고,
사운드 파일도 넣고,
프레임워크도 더 크게 썼을 것 같다.
그런데 제한이 있으니 계속 이런 질문을 하게 됐다.
1
2
3
4
이 파일이 꼭 필요한가?
이 이미지를 코드로 그릴 수 없나?
이 효과음을 합성할 수 없나?
이 기능을 더 작은 규칙으로 표현할 수 없나?
덕분에 ParryByte는 작은 코드와 규칙으로 움직이는 게임이 됐다.
또 하나 느낀 건,
게임을 만드는 일과 출시 가능한 앱으로 만드는 일은 다르다는 것이다.
게임 화면이 돌아가는 것만으로는 부족했다.
스토어 제출을 하려면 아이콘, 스크린샷, 권한, 개인정보, 등급, 설치/제거, 체크섬, 서명, 검증 문서까지 필요했다.
특히 원스토어 Windows PC 버전을 만들면서 이 차이를 많이 체감했다.
최종 수정본 기준으로 다시 남긴 것
이번 업데이트에서는 블로그 안의 ParryByte 이미지도 현재 코드에서 다시 생성한 PNG로 교체했다.
parrybyte-gameplay.png: 현재 Toss 자산 생성기 기준 일반 플레이 장면parrybyte-boss.png: 현재 Toss 자산 생성기 기준 보스전 장면parrybyte-current-play.png: 로컬에서 실제 앱인토스 WebView 버전을 실행해 촬영한 화면parrybyte-gameplay-current.webm: 같은 실행 화면에서 짧게 녹화한 플레이 영상
덕분에 글에 남아 있는 화면이 예전 임시 캡처가 아니라, 지금 저장소의 최종 수정본과 맞게 됐다.
정리
ParryByte는 처음에는 작은 macOS 네이티브 게임으로 시작했다.
그리고 같은 핵심 규칙을 유지한 채,
앱인토스 WebView,
원스토어 Android,
원스토어 Windows PC까지 옮겨갔다.
핵심은 계속 같았다.
1
2
3
4
패링한다.
반사한다.
강화해서 벤다.
작은 용량 안에서 게임답게 보이게 만든다.
작은 게임이지만,
제약 안에서 기능을 고르고,
검증을 붙이고,
스토어 제출까지 생각하면서 만든 덕분에 배운 것이 많았다.
다음에 또 작은 게임을 만든다면,
이번처럼 먼저 핵심 조작 하나를 강하게 잡고,
그 주변에 스테이지와 플랫폼을 확장하는 방식으로 만들어보고 싶다.


