KKflow

MAKING · 제작기

스택 타워 제작기: 블록 쌓기가 랜드마크 정복이 되기까지

첫날에만 24번 커밋한 블록 쌓기 게임이 어떻게 세계 랜드마크를 정복하는 게임으로 바뀌었는지, 실루엣을 네 번 다시 그린 이야기와 봇으로 맞춘 밸런스까지 커밋 기록으로 돌아봤습니다.

· KKflow

스택 타워는 화면을 탭해 움직이는 블록을 떨어뜨려 쌓는 3D 웹게임입니다. 저장소의 첫 커밋은 2026년 8월 22일, 제목은 "스택 타워 v2 구현 — 퀄리티 업그레이드 기획안(P0~P2) 적용"입니다. 그 전에 만들어 둔 첫 버전이 있었고, 하루 전날 그 버전을 진단한 기획안을 쓴 뒤 두 번째 버전을 처음부터 다시 만든 것입니다. 이 글은 그 뒤 한 달 남짓의 커밋 기록을 따라간 제작기입니다. 개발은 AI 코딩 도구와 함께 했습니다.

첫날, 커밋 24개

8월 22일 하루에만 커밋이 24개 쌓였습니다. 데일리 챌린지와 피버 모드, 황금 블록이 들어갔고, 바람·반전·안개 같은 스테이지 규칙, 코인과 상점, 이어하기, 연속 출석, 일일 미션과 부스터가 차례로 붙었습니다. 같은 날 온라인 랭킹과 서비스 워커까지 들어갔습니다.

게임은 지금도 HTML 파일 하나입니다. 3D 그래픽은 three.js로 그리고, 효과음은 음원 파일 없이 Web Audio로 그 자리에서 합성합니다. 첫 커밋 때 44KB였던 이 파일은 지금 300KB 가까이 됐습니다.

랭킹은 가입 없이, 대신 정직하게

온라인 랭킹은 로그인 없이 닉네임만으로 참여합니다. 서버(Supabase)의 랭킹 표는 외부에서 직접 읽고 쓸 수 없게 막고, 정해진 함수로만 기록을 올릴 수 있게 했습니다. 기기마다 보드별로 한 줄만 두고 더 높은 점수로만 갱신되게 해서 도배를 막았습니다. 다만 점수를 브라우저가 계산해서 보내는 구조라 마음먹으면 조작할 수 있다는 한계는 소개 문서에 그대로 적어 두었습니다. 상점의 강화 아이템 효과를 작게 잡은 것도 같은 이유입니다. 커밋 메시지는 "랭킹이 있는 게임이라 효과를 작게, 상한을 분명히 잡았다"입니다.

플레이 화면에는 광고를 두지 않는다

8월 23일에는 광고 자리를 잡았습니다. 이 게임은 화면 아무 곳이나 탭해서 조작하기 때문에, 플레이 화면에 광고가 있으면 실수로 누르게 됩니다. 광고 정책상 무효 클릭이 되는 배치이기도 해서 플레이 화면에는 광고를 두지 않기로 했습니다.

랜드마크를 네 번 다시 그리다

8월 23일에 작은 기능이 하나 들어갔습니다. 블록 한 층을 3m로 환산해 지금 쌓은 높이를 실제 건축물과 비교하는 기능입니다. 이 기능이 결국 게임의 중심이 됐는데, 그 과정이 순탄하지는 않았습니다.

  1. 실제 축척 실루엣 (8월 25일) — 타워 뒤에 다음 목표 랜드마크의 실루엣을 실제 비율로 세웠습니다. 에펠탑은 화면의 6배가 넘었는데, 커밋에는 "그게 오히려 노린 그림이다"라고 적었습니다. 하지만 대피라미드는 화면을 덮어 배경색처럼 보였습니다.
  2. 수치 오류 — 같은 날 다시 보니 "원본 수치부터 틀렸다." 에펠탑의 밑변을 64m로 넣었는데 실제로는 125m였습니다.
  3. 실제 축척 포기 (8월 27일) — 커밋 메시지는 이렇게 시작합니다. "실제 축척으로 세운다는 애초의 판단이 세로 화면에서 성립하지 않았다." 개선문은 공중에 뜬 두 덩어리로 보였습니다. 크기를 화면에 맞게 정규화하고, 타워가 올라갈수록 실루엣이 아래부터 차오르는 방식으로 바꿨습니다. 차오르는 정도는 화면 밝기를 직접 재서 확인했습니다.
  4. 하늘로 올리기 — 마지막으로 실루엣을 타워 위쪽 하늘 띠로 옮겼습니다. 자유의 여신상은 "그냥 뾰족한 기둥"처럼 보여서, 팔을 든 모양을 따로 그리는 전용 코드를 만들었습니다.

같은 날 게임의 컨셉 자체가 바뀌었습니다. 커밋 기록의 표현으로는 "'탭 한 번으로 즐기는 블록 쌓기'에서 '세계 랜드마크를 정복한다'로." 도전 모드의 이름이 "랜드마크"로 바뀌었고, 영어 UI도 함께 들어갔습니다. 랜드마크는 지금 기린(2층)과 첨성대(3층)부터 에펠탑(110층), 롯데월드타워(186층), 부르즈 할리파(276층)까지 22곳입니다.

테스트가 놓친 버그들

랜드마크를 목표로 삼는 모드에서는 이상한 장면이 나왔습니다. 화면에 보인 적도 없는 목표에 대해 "스핑크스 정복!" 배너가 뜨고, 상단에는 "첨성대 18/3"이 표시됐습니다. 원인은 스핑크스와 오페라 하우스에 실루엣이 없었던 것이었습니다. 부스터로 높이를 건너뛰면 "부르즈 할리파 301/276" 같은 표시도 나왔습니다. 목표 계산을 한 함수로 모으고, 목표에 닿으면 판이 끝나도록 규칙을 정리했습니다. 그 전에는 "'도전'이라 해놓고 목표를 찍어도 배너만 뜨고 판이 계속 굴러갔"습니다.

모양을 맞추는 스테이지에서는 안내선대로 놓으면 좁히는 단계가 100% 빗나가는 버그가 있었습니다. 기존 자동 테스트가 이걸 잡지 못한 이유도 커밋에 적었습니다. 테스트가 화면을 보지 않고 정답을 이미 알고 있는 상태로 블록을 놓았기 때문입니다.

색 맞추기 규칙을 이름에 맞추다

9월 초에는 같은 색 블록을 이어 쌓는 규칙을 다시 설계했습니다. 출발점은 "매치 확률은 늘 1/3 고정"이라는 문제였습니다. 판단할 거리가 없었던 겁니다. 20만 판을 시뮬레이션해서 넘길 수 있는 블록의 개수를 정했고, 넘기기 버튼은 블록을 조준해서 버리는 통으로 바꿨습니다. 버튼은 "판단만 있고 손은 놀았다"는 이유였습니다.

규칙 이름과 실제 동작이 맞지 않는 문제도 있었습니다. 이때 내린 결정이 "문구를 규칙에 맞추는 대신 규칙을 이름에 맞췄다"입니다. 그리고 골드러시 스테이지가 사실상 깰 수 없는 상태라는 걸 발견해 40판 중 34판 클리어에서 40판 모두 클리어로 고쳤습니다. 이때 스스로에게 남긴 규칙이 "수치 없이 고쳤다고 말하면 안 된다"입니다.

밸런스는 봇에게 맡겨 재다

9월 14일에는 조준 오차가 정규분포를 따르는 봇을 만들어 스테이지를 자동으로 플레이시켰습니다. 블록을 키워 가야 하는 스테이지는 보통 실력의 조준으로 클리어율이 0.8%였습니다. 기댓값이 음수라 목표 폭에 영원히 닿을 수 없는 구조였고, 규칙을 고쳐 43%로 올렸습니다. 새로 넣은 관성 규칙은 처음에 속도에 비례하게 만들었더니 잘 조준하는 봇도 클리어율이 0%여서, 고정값 0.22로 바꿨습니다.

1:1 대결과 흐르지 않는 시계

8월 말에는 친구와 겨루는 1:1 대결을 붙였습니다. 데일리 챌린지가 이미 날짜 시드로 모두에게 같은 블록 순서를 주고 있었기 때문에, 커밋 메시지대로 "공정성은 이미 풀려 있었다." 같은 시드로 두 사람이 플레이하고 점수만 비교하면 됩니다. (시드 난수의 원리는 이 글에 따로 정리했습니다.)

대신 서버 쪽에서 시간이 흐르지 않는 문제를 만났습니다. 데이터베이스의 now()는 트랜잭션이 시작된 시각을 돌려줘서, 대결 시간을 재는 값이 3.00에서 멈춰 있었습니다. 실제 현재 시각을 돌려주는 clock_timestamp()로 바꿔 해결했습니다. "친구가 링크를 눌렀는데 아무 일도 없다"는 문제도 있어서, 초대 코드를 주소에 직접 담는 방식으로 고쳤습니다.

오프라인, 그리고 캐시 v55

처음에는 웹 앱 매니페스트만 있고 서비스 워커가 없어서 실제로는 설치도 오프라인 실행도 되지 않았습니다. 서비스 워커를 붙이면서 HTML은 네트워크 우선, 나머지 파일은 캐시 우선으로 나눴습니다. 그 뒤로 게임 파일이 바뀔 때마다 캐시 이름의 버전을 직접 올렸는데, 지금 v55입니다. 업데이트가 사용자에게 닿게 하려면 이 숫자를 올리는 일을 잊으면 안 됩니다. (자세한 내용은 PWA 글에 정리했습니다.)

지금의 스택 타워, 그리고 남은 숙제

  • 모드 6가지 — 클래식, 타임어택, 스테이지, 데일리, 랜드마크, 1:1 대결
  • 스테이지 규칙 13종, 목표 유형 8종
  • 랜드마크 22곳, 업적 33종
  • 자동 회귀 테스트 32종 (전부 돌리는 데 약 12분)

남은 숙제도 기록에 적혀 있습니다. 9월 중순 보고서 기준으로 1:1 대결은 아직 실제 기기에서 충분히 검증하지 못했고, 부르즈 할리파(828m)보다 높은 목표가 없어 그 이상을 쌓는 사람에게는 다음 목표가 보이지 않습니다. 데일리 챌린지의 날짜는 지금 기기의 현지 날짜를 쓰고 있어서, 시간대가 다른 곳에서는 하루가 바뀌는 시점이 다릅니다. 하나의 기준 시간대로 맞추는 것이 다음 작업입니다.