“오락실 게임의 기본은 갤X그 아닌가?” 라는 생각이 들면서 내 손으로 만들어 보고 싶다는 생각을 했습니다. 화면 아래 한 줄에 내 기체가 있고, 머리 위에서 적들이 대형을 그리며 내려오고, 가끔 내 기체를 통째로 빼앗아 가는 그 게임이요.
그걸 CLEAVE WAVE 라는 이름으로 만들어 두 스토어에 올렸습니다. 안드로이드와 iOS 양쪽 다 나와 있고, 광고도 인앱 결제도 로그인도 없습니다. 이 글은 게임 소개 반, 만들면서 겪은 이야기 반입니다.
https://www.youtube.com/watch?v=HbB-Rb13gfw
영상이 안 보이면 여기에서 볼 수 있습니다.
머리 위에서, 편대가 숨을 쉰다
빛나는 코어를 광물 껍질에 감춘 결정 군체가 곡선을 그리며 내려와 자리를 잡습니다. 대형이 완성되면 편대는 살아 있는 것처럼 팽창하고 수축하고, 그중 몇이 줄을 벗어나 당신에게 날아듭니다. 당신의 기체는 화면 아래 한 줄. 좌우로 움직이고, 쏘고, 가릅니다.
빼앗기고, 되찾고, 두 배로
이 게임의 중심은 여기입니다. 함장기는 이따금 빔을 내려 당신의 기체를 통째로 끌어올립니다. 빨려 올라가는 동안에도 총은 살아 있습니다. 빼앗긴 기체는 편대 뒤에 붙잡혀 있고, 그 함장기가 다음 다이브로 내려올 때 격추하면 기체가 풀려나 당신 옆에 붙습니다.
화력은 두 배가 되지만, 맞는 면적도 두 배가 됩니다.
그래서 이건 상이 아니라 선택입니다.
모든 격추에는 값이 있다
껍질의 층수가 계급입니다. 호위를 먼저 떨어뜨리면 함장기는 싸구려가 되고, 셋이 한 몸으로 변신해 내려올 때는 셋을 전부 잡아야 보너스가 붙습니다. 정지한 적보다 날아다니는 적이 값어치가 큽니다. 안전하게 쏠 것인가, 내려올 때까지 기다릴 것인가 — 점수판이 그 선택을 기록합니다.
네 섹터마다, 아무도 쏘지 않는 하늘
DRIFT FIELD 에서는 적들이 총을 쏘지 않고 대형만 그리며 지나갑니다. 쉬어 가는 구간처럼 보이지만 사실은 욕심을 시험하는 구간입니다 — 한 발도 놓치지 않으면 FLAWLESS.
한 손으로, 오락실처럼
- 세로 화면 한 손 조작 — 왼손 조그로 밀고 오른손 엄지로 쏩니다.
- 게임 컨트롤러 지원 — 블루투스 패드를 연결하면 화면 조작부가 알아서 사라집니다.
- 난이도별 랭킹 — EASY · NORMAL · HARD 순위표가 따로 쌓입니다.
- 이름은 영문 대문자와 숫자 2~12자 — 오락실 랭킹 보드 그대로입니다.
여기서부터는 만든 이야기
1. 먼저 그은 선 — “똑같이 만들되, 원작의 것은 한 톨도 안 쓴다”
장르와 규칙은 공공재에 가깝지만 그림·소리·이름·UI 문구는 아닙니다. 그래서 시작할 때 원칙을 하나 정했습니다. 원작 리소스를 저장소에 두지 않는다, 원작 상표와 UI 문구를 코드·문서·스토어 문안 어디에도 쓰지 않는다.
다만 원칙은 잊힙니다. 그래서 검사기를 만들어 붙였습니다. 소스와 스토어 문안을 훑어 금지 단어가 있으면 그 자리에서 실패하는 스크립트입니다. 적기도, 폭발음도, 배경 하늘도, 폰트도 전부 새로 만들거나 라이선스가 확인된 것만 썼고, 외부 소재는 원본 파일의 sha256 까지 대장에 적어 뒀습니다.
2. 안드로이드와 아이폰이 엔진 하나를 같이 쓴다
모바일 게임을 양쪽에 내려면 보통 같은 게임을 두 번 만듭니다. 안드로이드용으로 코틀린으로 한 번, 아이폰용으로 스위프트로 한 번. 그러면 같은 규칙이 두 벌이 되고, 버그도 두 번 고쳐야 하고, 한쪽만 손대는 순간 밸런스가 소리 없이 갈라집니다.
그래서 게임 규칙 전체를 KMP(코틀린 멀티플랫폼) 공용 모듈에 한 벌만 뒀습니다. 적 편대가 그리는 곡선, 탄 궤적, 충돌 판정, 점수 계산, 난이도 — 게임이라고 부를 만한 부분은 전부 여기 있습니다. 안드로이드의 Compose 와 아이폰의 SwiftUI 는 그 결과를 화면에 그리기만 합니다. 엔진 쪽 67개 파일에 플랫폼별 분기가 한 곳도 없습니다.
대신 대가를 치러야 합니다. 좌표를 전부 정수로 못박고, 삼각함수와 난수를 엔진에서 걷어냈습니다. 소수점 계산은 기기와 컴파일러에 따라 아주 미세하게 다를 수 있는데, 그 미세한 차이가 몇 분 뒤에는 전혀 다른 판이 되기 때문입니다.
그리고 “정말 같은가”는 눈으로 확인할 수 없습니다. 처음 몇 초는 똑같아 보이다가 한참 뒤에 갈라지니까요. 그래서 확인용 장치를 하나 만들었습니다 — 난수도 시계도 안 쓰고 틱 번호만으로 정해지는 30초짜리 대본을 돌리면서 매 틱 게임 상태를 숫자 하나로 압축해 누적합니다. 기대값은 딱 하나, -1869114812. 두 기기가 이 숫자를 똑같이 뱉으면 같은 판을 돌고 있는 겁니다.
3. “깔끔한 설계”와 “맞는 설계”는 다른 것이었다
테스트 중에 이런 제보를 받았습니다. “아무리 집중해도 6스테이지 넘기기가 어렵다.” 무적 상태로 자동 플레이를 돌려 재 봤더니 짚은 곳이 정확했습니다 — 스테이지 2에서 4로 갈 때 적 폭탄 밀도가 3배, 4에서 6으로 다시 2.3배가 됐습니다.
그래서 난이도 3단계를 넣기로 했고, 처음 만든 방식은 난이도 표를 읽는 행을 위아래로 미는 것이었습니다. 표가 한 벌로 남아 코드도 짧고 보기 좋았습니다. 그런데 틀렸습니다.
표에는 스테이지 10과 18이 일부러 쉬워지는 완급이 설계돼 있는데, 행을 밀면 그 골짜기가 같이 끌려갑니다. 26개 스테이지를 전부 뽑아 봤더니 EASY 가 NORMAL 보다 어렵고 HARD 가 더 쉬운 칸이 넷 나왔습니다. 실제로 굴려도 스테이지 8에서 HARD 12발 / NORMAL 30발이 나왔고요.
무서운 건 눈으로는 안 보였다는 점입니다. 코드는 짧고 읽기 좋았고, 스테이지 4·5·6 만 보면 완벽하게 동작했습니다. 표를 끝까지 뽑아 보고서야 나왔습니다.
결국 행이 아니라 값을 미는 방식으로 갈아엎었습니다.
4. 격추 한 번에 세 가지가 “동시에” 일어나야 한다
적을 맞히면 세 가지가 함께 일어납니다. 충돌 판정이 서고, 폭발 애니메이션이 뜨고, 효과음이 납니다. 코드에서는 전부 같은 프레임에 걸려 있는데, 손에 쥐고 해 보면 이상하게 안 맞습니다. 맞은 것 같은데 터지는 게 늦거나, 소리가 한 박자 뒤에 오거나.
셋이 저마다 다른 지연을 갖고 있기 때문입니다. 이 싱크를 맞추는 데 시간이 제일 많이 들었습니다.
충돌 판정은 한 틱에 끝나는데 연출은 여러 프레임에 걸쳐 재생됩니다. 판정이 선 틱과 연출이 시작되는 틱이 어긋나 있으면 한 프레임씩 밀립니다. 두 시점을 같은 자리에 묶어야 했습니다.
애니메이션은 첫 프레임이 작고 어두우면, 눈은 두세 프레임 뒤에야 “터졌다”고 읽습니다. 스프라이트 순서는 맞아도 체감 시점은 뒤로 밀리는 겁니다. 가장 밝고 큰 프레임을 앞쪽으로 당겨 놓아야 맞은 순간과 겹칩니다.
효과음이 제일 까다로웠습니다. 실제 폭발 녹음에는 소리가 시작되는 지점 앞에 서서히 올라오는 충격파 머리가 붙어 있습니다. 파형이 시작되는 곳에서 자르면 귀에 “퍽” 하고 꽂히는 진짜 타격은 한참 뒤에 옵니다. 스피커 특성을 씌운 뒤 소리 세기가 가장 높은 지점을 재 보니 이렇게 나왔습니다.
| 자르는 기준 | 체감 타격 시점 |
|---|---|
| 기존 합성음 | 1.5ms |
| 녹음을 소리 시작점에서 자름 | 52.2ms — 약 3프레임 늦다 |
| 타격 지점을 찾아 그 8ms 앞에서 자름 | 8.7ms |
소리가 시작되는 시점과, 소리로 들리는 시점은 다릅니다. 애니메이션도 마찬가지고요. 하나씩 떼어 놓고 보면 셋 다 맞는데, 합쳐 놓으면 어긋나 보이는 게 이 작업의 성격이었습니다.
5. 게임패드 — 만들지 않기로 한 설정 하나
블루투스 패드를 붙이다 알게 된 건데, D-패드는 기기마다 두 갈래로 들어옵니다. 어떤 패드는 방향키 이벤트로 보내고, 어떤 패드는 아날로그 축(햇 스위치)으로 보냅니다.
처음엔 “설정에서 타입1 / 타입2 를 고르게 하자”고 생각했습니다. 그런데 다시 보니 둘 다 받으면 되는 문제였습니다. 그래서 설정을 만들지 않았습니다.
설정은 편의 기능이기도 하지만, 때로는 만드는 쪽이 결정을 못 해서 사용자에게 떠넘기는 자리이기도 합니다.
그리고 패드 쪽에서 나온 결함 네 개가 전부 한 자리에서 났습니다 — “눌린 상태”를 “눌렀다는 사건”으로 바꾸는 경계. 조그를 오른쪽으로 민 채 나가기 대화상자를 열면 첫 프레임에 방향이 한 번 튀어서 커서가 YES 로 가고, 거기서 확인을 누르면 판이 그대로 날아갔습니다.
6. 아이콘은 “아, 그 시절 그거”가 떠오르게
스토어에서 사람이 제일 먼저 보는 건 스크린샷도 설명도 아니고 아이콘 하나입니다. 그 작은 사각형에서 “아, 옛날 오락실에서 하던 그런 거구나” 가 전달되지 않으면 그다음이 없습니다. 그래서 아이콘은 예쁘게가 아니라 그 시절이 떠오르게 그리는 걸 목표로 잡았습니다.
고전 종스크롤 슈팅 하면 떠오르는 것들을 모았습니다 — 까만 우주, 흩뿌려진 별, 화면 아래에서 위로 솟구치는 삼각형 기체, 꽁무니의 엔진 불꽃, 그리고 브라운관 시절의 형광색. 정면을 보는 납작한 기체 대신 뒤에서 앞으로 날아오르는 각도로 잡았더니, 작게 줄여도 방향과 속도감이 남았습니다.
배경 별하늘은 그리지 않고 게임 인트로 화면에서 그대로 가져왔습니다. 비슷하게 그리면 어디까지나 비슷한 것이지 같은 것이 아니라서, 인트로 하늘을 그리는 코드에서 그라데이션 값과 별 좌표를 그대로 꺼내 쓰도록 했습니다. 앱을 켰을 때 아이콘에서 본 하늘이 그대로 이어집니다.
이제 스토어에서 이 아이콘이 그 게임을 연상시킬지는 보는 사람 몫~ ㅎ
7. 마지막 복병 — 이름이 이미 잡혀 있었다
다 만들고 스토어에 올리는 날, App Store Connect 가 이렇게 답했습니다.
입력한 앱 이름이 이미 사용 중이므로 영어(미국)의 필드를 저장할 수 없습니다.
붙여 쓴 Cleavewave 가 막혀 있었습니다. App Store 는 게시된 앱뿐 아니라 누군가 레코드만 만들어 잡아 둔 이름까지 막습니다. 검색해도 그 이름의 앱이 안 나오는 이유죠.
이름에 설명을 덧붙이는 대신 띄어 쓴 Cleave Wave 로 갔습니다. 생각해 보니 로고가 원래 두 줄로 CLEAVE / WAVE 였고, 기기 홈 화면 이름도 두 단어였습니다. 오히려 이쪽이 맞았습니다.
혹시 출시를 앞두고 계시다면 — 앱 이름은 코드를 쓰기 전에 잡아 두세요. 번들 ID 는 한 번 올리면 못 바꿉니다.
그래서, 한 판 해 보시겠습니까
켜면 바로 시작합니다. 튜토리얼도, 로그인도, 광고도 없습니다. 빼앗긴 기체를 되찾는 그 순간의 감각만 제대로 옮겨 놓으려고 했습니다.
해 보시고 이상한 점이나 아쉬운 점이 있으면 댓글로 알려 주세요. 이 글에 적은 수정들도 대부분 “뭔가 좀 이상한데” 한마디에서 시작됐습니다.
https://play.google.com/store/apps/details?id=com.ecsoft.cleavewave
https://play.google.com/store/apps/details?id=com.ecsoft.cleavewave
play.google.com
https://apps.apple.com/us/app/cleave-wave/id6810160665
Cleave Wave App - App Store
Download Cleave Wave by UI CHAN HONG on the App Store. See screenshots, ratings and reviews, user tips, and more apps like Cleave Wave.
apps.apple.com
'앱 개발' 카테고리의 다른 글
| [딱팡 개발일지] 기획서 한 장에서 두 스토어 출시까지 (0) | 2026.09.23 |
|---|---|
| 딱팡(DDAK PANG) — 색 대신 '목표'를 맞추는 버블슈터 (0) | 2026.09.23 |
| 너구리와 함께하는 클래식 점프런, '너구리의 모험 2026' 개발기 (0) | 2026.09.03 |
| 물방울 아케이드 '버블듀오'를 만들었다 (0) | 2026.08.31 |
| 위치와 시간이 만나는 새로운 SNS, '핀(PIN)'을 준비하며 (1) | 2026.07.21 |