2026-05-28
[etc] 유저 의견으로 매일 자동 개발과 배포되는 웹게임 제작기
작년까지는 AI를 웹 대화형으로만 가끔 쓰고 있었는데, 올해 초에 대부분의 AI 도구가 크게 개선된 게 인상적이라 개인 프로젝트를 바이브 코딩 위주로 개발해보고 싶었어. 그런데 단순히 완성도를 높인 무언가를 만들기보단 자주 안 써본 기술로 실험적인 게임을 도전해보고 싶어서 아이디어 수준으로 프로토타입을 이것저것 만들다가 유저 의견이 매일 릴리즈에 반영되는 작은 미니 게임을 만들기로 결정했어. 이 글은 그 프로젝트의 초안부터 설계, 개발, 프롬프트 가이드 작성에 자동 업데이트 서비스 구축까지 하면서 생각나는 대로 쓴 기록이야. 모바일에서도 접속되니까 참고해. (근데 키보드랑 마우스 오버가 안 돼서 조금 불편할듯) game: https://spiralwave.frogred8.dev github: https://github.com/frogred8/SpiralWave - 프로젝트 개요 처음에 며칠 고민해보다가 "자원 수집형 게임" 컨셉으로 개발 방향을 잡았어. 내가 남과 경쟁하거나 죽어서 손해보는 방식의 게임을 안 좋아하거든. 먼저 종이에 내가 상상한 게임과 방식을 써서 gemini 웹에 올리면서, 이 그림의 내용으로 cli에 개발 요청할 프롬프트로 정리해달라고 시켰어. (그림1 참고) gemini-cli는 처음 써봐서 잘 몰랐는데, 그냥 저기서 정리해 준 프롬프트 수십줄 붙여주니까 알아서 typescript, vite, phaser로 잘 만들어 주더라고. 이렇게 웹페이지에 뜨는 단순한 컨셉의 게임이 설계부터 구현까지 30분만에 짜잔. 처음엔 흑백톤으로 시작해서 컬러를 모아가면 점점 화려해지는 컨셉으로 개발했는데 막상 만들어 놓으니까 한번 하기엔 재밌는데 금방 지루하더라고. 그래서 유저는 자원을 모아서 자신만의 스킬 트리를 구성해서 더 많은 자원을 모으는 사람이 이기고, 높은 점수를 획득한 사람의 요구 사항을 그 다음날 업데이트에 자동 적용하는 것으로 정했어. 웹은 내 전문 분야도 아니고 (css도 모름), 운영툴 만들 정도의 지식 수준뿐이라 기능의 대부분을 gemini에게 시켜서 만들었고, 좌표 조정만 조금씩 했어. 개발하던 중에 인상적이었던 작업을 몇 가지 소개해볼게. - 스킬 트리 개발 플레이 가능한 게임 기반이 만들어지고 나서 그 다음으로 스킬 트리를 만들었어. 당시 초기 프롬프트를 하나 보여줄게. "디아블로2 같은 형태의 스킬 트리를 만들어 줘. 이 때, 스킬 트리는 3*5의 항목으로 구성하고, 스킬 배우는 조건은 자원을 소모하며 선행 스킬을 먼저 배워야 해." 이 하나의 프롬프트로 UI와 기능이 구현된 스킬 트리 구성이 잘되더라. 그리고 선행 스킬이 배워진 상태에서 다음 스킬을 미리 예약하는 시스템도 프롬프트로 넣었는데, 예약된 선행 스킬의 하위 스킬을 예약하는 건 잘 안되더라고. 특히 예약 스킬을 취소할 때에 가장 하위 스킬이 아닌 중간 스킬을 취소하면 그 아래 스킬도 전부 취소해줘야 하는 작업은 몇 번 시켜봐도 예상대로 안 되거나 복잡도가 쓸데없이 높아지길래 이런 복잡한 부분은 직접 구현했어. 이 때 사용한 gemini 품질이 claude나 codex보단 떨어져서 그럴 수도 있지만 요구사항의 제약 조건을 하나씩 다 고민해서 써줘야하는게 생각보단 귀찮아서 한 번 시켜서 잘 못하면 그냥 내가 하는 편. 참고로 난 일부러 테스트 코드를 구성하지 않았는데 1. 잦은 대규모 설계 변경이 충분히 예상되고, 2. AI가 쓸데없이 커버리지만 올리는 테스트 코드를 만들기 쉬운 환경이고, 3. 어차피 깨질 테스트 코드에 유지 보수 비용과 시간을 투자하기보단 빠른 개발 속도가 우선이라서 테스트 코드 없이 진행했어. (+나혼자 작업) - UI 코드를 로직과 분리하는 대규모 리팩토링 하나의 파일에 기능만 계속 추가하다보니 어느새 천줄이 넘어가서 리팩토링을 진행했어. UI 코드와 초기값을 별도 파일로 분리해달라는 내용의 간단한 두줄이었는데 20분 정도 걸리는 꽤 긴 작업이었어. 사실 작업해 놓은 게 썩 마음에 들진 않지만 손보기 시작하면 너무 많이 고쳐야 해서 '이런 게 바이브 코딩이지'라는 생각으로 그냥 지나쳤는데, 이제 와서 생각해보면 내 동료가 저런 코드를 리뷰로 올리면 꽤나 많은 덧글을 달았을 것 같아. (변수, 파일 이름의 통일성이나 함수 역할 구분 등) AI가 했다고 생각하니 세세하게 피드백을 주는 것도 피곤한 일이고, 피드백을 주고 받는 사회적 재미가 없어서 더 귀찮은 느낌이 들었나 봐. 이후에 이런 큰 작업할 때는 설계 플랜을 먼저 짜달라고 해서 그 플랜을 기반으로 프롬프트를 다듬어서 에이전트에 전달했어. 근데 나온 결과가 마음에 안든다면 거기서 추가적인 수정을 요청하는게 아니라, 그냥 그 수정사항을 지우고 프롬프트를 보강해서 재작업 시키는게 낫더라. (토큰 낭비긴 하지..) 리팩토링 작업이 끝난 후에 빌드는 잘 되는데 특정 상황에서 null 객체 참조 에러가 발생했어. AI에게 재현 시나리오를 알려주고 해결을 맡겼더니 에러와 관련된 연관 함수에 가드 코드만 잔뜩 추가했고, 당연히 문제는 해결되지 않았지. 이전 수정 내용을 롤백하고 천천히 흐름을 따라가보니, UI 매니저로 분리되면서 상태 변경 후에 UI 갱신하는 순서가 반대로 되어 있는 게 문제였어. 그 두 줄의 순서를 바꾸니까 모든 문제가 깔끔하게 해결됐어. 이 문제를 해결하면서 AI의 실수가 비교적 인간적이라 기분이 묘했어. 사람도 가끔 사소하게 실행 순서를 바꾸는 것 만으로 모든 게 갑자기 이상해지는 경험을 하잖아? 함수의 역할을 잘 이해하지 못한 상태에서 수정하면 누구나 할 수 있는 실수인데 AI도 그 함정을 벗어나기는 쉽지 않구나 싶었어. - git 자동 커밋 이때부터 프롬프트 가이드 파일(GEMINI.md)을 처음 적용해봤어. 새로 실행시킬 때마다 못 알아듣는 게 귀찮아지기 시작했거든. 현재 프로젝트를 분석해서 사용하는 기술과 게임 컨셉, 모듈별 작동 방식을 프롬프트 형식으로 출력하라고 하니까 뚝딱 만들어주더라. 그런데 프롬프트로 지시한 작업을 내가 라인 단위로 리뷰하는 게 큰 의미가 없어서 (대부분이 딸-깍) 그냥 코드 작업이 끝나면 바로 커밋하게 구성하니까 이제야 좀 자동화된 느낌이 들었어. 최종적인 커밋 메시지에는 해당 커밋을 만들기 위해 에이전트가 작동한 시간과 지시한 프롬프트, 실제 작업에 대한 요약을 추가했고, 아래처럼 구성하게 만들었어. fix: [45s] Sync satellite orbit with boosted black hole radius (CODEX) (스페셜 아이템으로 블랙홀 범위가 커졌을 때 위성도 그 크기에 맞춰 이동하게 변경해) - OrbitSystem이 매 프레임 현재 유효 블랙홀 반지름을 조회해 위성 궤도 거리를 계산하도록 수정했습니다. - 반지름 부스트 시작과 축소 트윈 동안 위성 위치가 경계 변화에 맞춰 즉시 동기화됩니다. 에이전트가 작동한 시간 알아오는 건 프롬프트를 아무리 명확하게 적어도 에이전트가 최초 실행된 시간을 캐시해서 사용하길래 그냥 작업 시작할 때마다 shell로 시간을 임시 파일에 적어놓게 구성했어. 근데 에이전트 작동 시간이 너무 길어지면 가끔 한 번씩 시간 기록을 안하거나 커밋을 안해길래 작업 시작할 때마다 새로 실행하는 편이야. 이건 gemini에서 자주 그랬고, codex로 넘어가니 빈도는 크게 줄었는데 아직 100%는 아닌 듯. - 자동 업데이트 설계 고민 게임을 최초 설계할 때는 2시간마다 유저 의견을 적용한 버전이 메인 브랜치에 바로 개발되고, 자동 배포되는 방식으로 구성했어. 그런데 바이브 코딩을 하다보니 아무리 명확히 프롬프트를 전달해도 런타임 이슈가 지나치게 많이 나와서 (체감상 4번 중 한번은 런타임 버그 발생) 빌드 안정성을 확보할 수 없었어. UI 좌표든 플로우 자체의 문제든.. 이를 여러 개의 하네스로 검증해서 이슈를 최대한 줄일 수는 있겠지만 1. 그걸 다 구성할 만한 토큰도 부족하고, 2. 서버 부하가 큰 기능을 만들었을 때 이를 감당할 비용의 예측이 힘들고, 3. 내가 한동안 신경 쓰지 않아도 안정적으로 돌아가는 서비스를 원하기 때문에, 깔끔하게 포기했어. 다만 이 아이디어를 아예 포기한 건 아니고, 유저 의견이 적용된 테스트 빌드를 매일 하나씩 자동으로 내는 방식으로 조금 더 작은 버전으로 설계 방향을 바꿨어. 테스트 빌드를 직접 해보고 괜찮은 버전이 있으면 그걸 메인 빌드에 안정적으로 머지하는 방식으로 해 보려고. - cron 프로젝트 구성 매일 자동화된 업데이트를 위해서 node:cron을 사용했는데 사실 그냥 주기적으로 실행되기만 하면 되니까 shell로도 가능했을 거야. 아래처럼 간단한 워크플로우를 구성해서 이를 하나씩 완성해갔어. 1. 리더보드 데이터의 피드백 수집 2. 피드백 목록을 프롬프트로 정제 3. 프롬프트로 코드 생성 4. 빌드 및 릴리즈 노트 생성 5. 서버 배포 하나씩 보면 기술적으로 어려운 부분은 없는데 실제로 구현해보니 이것저것 추가되면서 저런 작업들이 12개 정도로 늘어났어. 특히 오라클 서버에 포트 4개 열어두고 이를 자동 업데이트 때마다 환형으로 재활용하는 부분이 조금 까다롭더라. 만약 서비스를 여러 개로 나눠서 띄웠으면 많이 복잡해졌을 듯. (현재는 monolithic 구조) 이거 외에도 git 별도 브랜치로 생성해서 빌드하는 부분이나, 생성한 릴리즈 노트를 다국어로 번역해서 저장하고 이를 캐시한다던가 등등.. 이걸 진행하면서 유저 접속마다 매번 발생하는 작업도 같이 최적화했는데 난 이런 부분에 대한 고민이나 구현이 참 재밌더라. 예를 들어, 이전에는 서버 목록이 바뀌면 현재 떠 있는 모든 인스턴스(지금은 5개)에게 매번 전달하고 재시작해야 갱신되는 방식이었어. 이걸 서버에 떠 있는 docker 인스턴스 내에 서버 목록 파일을 공통 volume으로 연결해서 모두 같은 파일을 바라보게 하고, 이 서버 목록 파일을 읽도록 변경했어. 서버 목록에 새 버전이 추가되면 저 서버 목록 파일 하나만 갱신해주면 모든 인스턴스가 동일한 내용을 공유하는거지. 다만 그렇게 하면 서버 목록을 업데이트할 때마다 서버를 재시작하거나 유저 요청마다 파일을 새로 읽어야 하잖아? 서버 재시작도 번거롭고, 웹게임이다 보니까 첫 화면의 요청이 가장 많을 텐데 매번 파일을 새롭게 읽는 게 싫어서 5분마다 만료되는 캐시에 저장하는 방식으로 바꿔서 이를 제어했어. 유저 요청을 프롬프트로 만드는 것도 실제로 해보니까 유저는 여러가지 언어(중국어, 일본어 등)로 요청할 거라서 프롬프트를 만들 때, 이게 다국어 요청 목록임을 알려주고 영어로 프롬프트를 정제해서 내놓으라고 했어. 이 때, 지나치게 큰 기능이나 보안상 이슈가 예상되는 기능은 제외하라고 했는데 잘 필터되는 것 같긴 하지만 혹시 몰라서 이 cron은 별도 로컬 컴퓨터에 실행 환경을 구성했어. (만약에라도 rm -rf /가 뚫리면..) 어쨌든 이 정제된 프롬프트로 릴리즈 노트를 만드는데 영어 버전을 기준으로 다국어로 번역해서 서버 목록에서 선택한 언어로 릴리즈 노트가 보이도록 구성했어. 실시간으로 언어 변경해보니까 한번에 잘 나와서 뿌듯뿌듯. - gemini에서 codex로 변경 처음 두 달은 gemini로도 충분하다고 느껴서 그냥 쓰고 있었는데, gemini는 생성한 코드를 그대로 배포하기엔 코드 안정성의 유지가 쉽지 않았고 무엇보다 작업 시간 예측이 안 될 정도로 너무 오래 걸렸어. 나중에는 1시간을 기다리다가 타임아웃으로 꺼지는거 몇 번 경험하고 나서 codex로 넘어가게 되더라. 바꾸고 나서 가장 크게 체감한 건 속도와 안내 방식이야. gemini는 지금 뭐하고 있는지 도통 알려주질 않고 시덥잖은 영어 농담만 나와서 답답했거든. 근데 codex는 '그 기능 구성을 위해 이런거 살펴보고 있어요'처럼 계속 안내해주면서 진행되니 좋았어. 마치 신입에게 일을 시켰는데 한명은 작업 끝날 때까지 아무런 중간 공유를 안해주고, 한명은 아침마다 진행 상황을 공유해주는 차이랄까? 작업 속도도 훨씬 빠르지만 이런 부분 때문에 작업 맥락을 파악하는게 더 쉬웠어. - 개발하다가 빠진 기타 기능들 + 유저가 리더보드의 메시지에 "like"를 누르는 기능 좋은 의견이면 top 10위 안에 들지 않더라도 영향을 주고 싶었어. 그런데 랭킹은 점수 순으로 되는데 like의 개수로 다시 정렬하는게 맞는지 애매하고, like 중복 방지를 하려면 식별자가 필요한데 계정을 만들 건 아니라서 이것도 꽤 품이 드는 일이야. 특히 유저는 여러가지 언어로 메시지를 쓸텐데 리더보드에서 like를 누르기 위해서 사람들이 인지할 수 있도록 다국어 번역도 필요해. 이 때, 메시지를 저장할 때마다 ai를 호출하긴 싫어서 이 기능은 절반 정도 만들다가 안하는게 낫겠다 싶어서 지웠어. + 스킬툴 개발 원래 스킬이 랜덤 배치가 아니라 디아블로처럼 완성도 높은 스킬 트리를 만들고 싶었어. 그래서 스킬 데이터인 json을 전달해서 ai에게 툴 제작을 요청했고, 생각보다 무척 잘 동작해서 만족하며 쓰고 있었는데 스킬 수십 개를 생각해내는게 쉽지 않더라. (상상력의 한계) 그리고 처음엔 스킬트리가 고정된 상태로 해보니까 게임을 반복하는게 점점 지루했는데, 시작할 때 스킬을 랜덤하게 배치하니 그 부분이 훨씬 괜찮아졌어. 결국 간단한 데이터 수정은 json 직접 수정이 더 빨라서 더 이상의 개발은 하지 않고 방치 상태. + 서비스 별 docker 분산 이건 관리의 편의를 위해서 감내한 부분이야. 현재 최종 구조는 docker 이미지 하나를 생성해서 실행하면 client+server+db 까지 포함되어 있어. 만약 서비스마다 docker를 분리했다면, 매일 새 버전이 나올 때마다 docker 여러 개를 띄워야 하는 관리의 복잡함이 생길 거야. 이런 부분은 실무에서 충분히 많이 해봤고, 굳이 여기서까지 고도화시킬 필요는 없어서 최대한 단순화시켰어. + 자신의 의견이 반영된 빌드가 나오면 메일로 소식 전달 아무래도 가벼운 웹게임 컨셉이다보니 재방문율이 낮을 거라 예상돼서 자신의 의견이 반영되면 와서 확인해보라고 알려주고 싶었어. 리더보드에 이름이 아니라 이메일로 식별자를 남겨보니 회원 가입 없이 남기는 이메일은 유효성이 높지 않고, 괜히 메일 도용해서 다른 사람들에게 날아가면 그것도 민폐라서 가벼운 마음으로 만들긴 힘들더라. 효과도 크지 않을 것 같아서 로컬에서만 잠깐 해보다가 삭제. + 사이드에 광고 추가 큰 트래픽은 아니더라도 서버+도메인 값이라도 벌면 좋겠어서 조금 알아보다가 포기. 구글 애드센스는 애드고시라고 불릴 정도로 오래 걸리고, 특히 거절 사유를 알려주지 않아서 답답하다는 후기가 많았어. 다른 광고 플랫폼은 저질 광고나 보상형 비디오 방식이 대다수라 넣고 싶진 않더라. 그리고 국내 플랫폼은 단가가 너무 싸서 굳이.. 그래서 그냥 리더보드 화면에 Buy me a coffee 링크 하나 달고 끝냈어. 이 프로젝트는 ai를 활용한 장난감+좋은 글쓰기 재료로 생각하려고. 만들면서 재밌었으니 그걸로 됐지. - 딸-깍 프로젝트 후기 AI만 써서 개발해보니 순수한 코딩과는 다른 재미가 있긴 했어. 생산성은 10배쯤 올라가지만 그렇게 생성한 기능의 테스트 시간은 줄어들지 않아서 내가 개발자인지 QA인지 모를 정도로 테스트만 하다보니까 점점 개발이 재미없어 지더라. (그래서 이 프로젝트도 한달 간의 유기 끝에 겨우 완성.. 조금 더 방치했으면 미완성으로 남을 뻔) 그리고 AI가 개발한 내용은 함수 단위의 품질은 괜찮은데 전반적인 코드 흐름을 읽기가 어려워. 난 읽기 쉬운 코드를 지향하기 때문에 함수 내에서도 의미 단위로 코드를 모아두거나 함수 정의 순서도 신경쓰는 편인데, 얘는 아무데나 정의해서 쓰니까 소설책 읽듯이 쭉 내려가면서 읽는 느낌이 아니라 사방팔방을 돌아다니며 찾아봐야 하는 느낌이야. 또 지엽적인 하드 코딩이 오히려 구조를 깔끔하게 유지할 수 있는데 AI는 이것조차 일반화시키려고 하다보니 쓸데없이 큰 패턴이 자주 들어가는 경향도 있어. 기능을 만들 때 확장성 추가 여부도 프롬프트에 들어가면 조금 더 잘 만들어 주려나.. - 결론 1) css도 모르는 상태에서 html 웹게임을 만들 수 있는 시대가 되었다. 2) AI 프로젝트를 진행하면서 내게 가장 부족했던 능력은 기획력+상상력(+반복 테스트) 3) 하네스(가이드) 구축은 현재 규모와 상황에 맞게 유연하게 조절하여 필요한 정도로만 구성해도 괜찮다. 4) gemini에서 codex로 바꾸니까 광명찾은 느낌. 역시 비싼 게 좋구나. 이전글: https://blog.frogred8.dev #frogred8 #game #vibe_coding
초기 설계 문서 (그림1)

게임 시작 화면

플레이 화면
