WIKITHE GGI
개인 제품·2026.08 ~ · 개발 중(미출시)·기획·개발

THE GGI

도끼 하나로 베고 캐고 싸우는 복셀 3D 게임. 에셋을 만들지 않고 코드로 굽는다.

TypeScriptReact Three Fiberthree.jsViteElectronPython

상태 · 미출시, 개발 중. 공개 빌드도 스토어 페이지도 없다.

THE GGI는?

도끼 하나로 나무를 베고 광물을 캐고 몬스터를 잡는 복셀 3D 게임이다. 지금 기획 정본의 한 문장은 "도끼만 쓰는 액션 어드벤처. 싱글 1인칭 3D" 다.

곡괭이도 검도 망치도 없다. 나무도 돌도 철도 금도 전투도 전부 도끼로 한다. 플레이어가 "설마 이것도 도끼로?" 하고 물으면 게임의 대답은 늘 같다.

제목은 도끼 -> 더 끼 -> GGI 에서 나왔다. 세계관적 의미는 없다.

왜 만들었나

이 게임이 존재하는 이유는 질문 하나다. 에셋을 하나도 안 만들고 어디까지 굴러가는가.

2D 픽셀 RPG 를 하다가 에셋 부담에 막혀 3D 로 왔고, 로우폴리로도 만족이 안 돼 복셀로 왔다. 그래서 규칙이 셋이다.

형태는 복셀 배열을 코드로 생성한다. 파라미터로 굽는다
사람이 그리는 것은 MagicaVoxel .vox 파일뿐이다. 넣으면 같은 경로로 렌더된다
외부 3D 에셋은 사지도 받지도 않는다

핵심 문장은 "물건 하나를 만들지 말고 물건을 만드는 함수를 만든다" 다. buildAxe(AxeParams) 하나에서 벌목도끼, 손도끼, 전투도끼가 파라미터만 달리해서 나온다. 나무도 buildTree 하나다.

첫날에는 3D 조각(kaykit gltf)을 좌표로 놓고 콜라이더를 손으로 따로 넣는 구조였다. 조각 사이에 틈이 생기고, 겹치면 지붕이 지지직거리고, 콜라이더가 형태와 따로 놀아 몸이 판자를 통과했다. 복셀은 형태가 곧 격자라 콜리전이 형태에서 나온다. 그날 조각을 걷어내고 레벨을 복셀로 다시 지었다.

왜 복셀인가

복셀을 고른 결정적 근거는 밀도가 공짜라는 것이다. greedy meshing 이 같은 색 인접면을 하나로 합치므로, 복셀을 촘촘하게 할수록 병합 효율이 오른다.

7.6배
복셀 밀도를 올렸을 때
1.5배
늘어난 삼각형
12,000
레벨 한 장 삼각형 예산
~8,900
정점 AO 넣은 뒤 가장 무거운 레벨

로우폴리의 "면을 늘리면 비싸진다" 는 전제가 여기서는 깨진다. 정점 AO 를 넣으면 밝기가 다른 꼭짓점끼리 못 합쳐 삼각형이 늘었는데(마을 4,522 -> 8,012), 세 지역 다 예산 안에 들었다.

Decision #01복셀 결은 지오메트리가 아니라 셰이더에서

복셀마다 명도를 조금씩 흔들면 표면에 결이 생긴다. 그런데 이걸 정점 색으로 하면 병합 키가 갈려서 마을이 8,012에서 37,522 삼각형으로 뛴다. 4x4x4 로 뭉쳐야 11,408 인데 그때는 결이 아니라 얼룩이다. 결은 재질(픽셀 셰이더)로 옮겼다. 복셀의 장점이 병합인데, 그 병합을 깨는 표현은 메시에 두지 않는다.

도끼밖에 없다

처음엔 규칙만 있었다. 도구를 하나라도 더 넣으면 이 게임의 전부가 사라진다는 규칙이다. 이유는 나중에 세계관이 붙였다.

나무 마왕의 증오가 세계를 나무로 바꾸고 있다. 숲의 몬스터는 잡몹이 아니라 형태를 잃고 나무가 된 생존자들이다. 캘 것이 나무에서 나니 곡괭이가 쓸모없고, 적이 나무라 검을 들 이유가 없다. 도구를 하나로 좁힌 게 아니라 이 세계에서 도끼 말고는 쓸 데가 없어진 것이다.

그리고 그 모든 도끼질의 끝에서, 플레이어는 자신이 숲의 원수와 같은 행동을 하고 있었다는 사실을 마주한다. 그래서 기획서에 "흐리면 안 되는 것" 넷이 박혀 있다.

도끼 외의 도구를 넣지 않는다 · 몬스터를 나무와 무관하게 늘리지 않는다 · 마왕을 악당으로 만들지 않는다 · 반복 벌목을 자동화하지 않는다. 편의로 하나씩 타협하면 마지막 반전이 회수될 자리가 없어진다.

전체 구조

voxel/
복셀을 굽는다. 도끼·나무·사람을 파라미터로. greedy meshing, .vox 로더
game/
규칙과 상태. three 도 React 도 모른다. 스윙·채집 판정은 순수 계산
scene/
React Three Fiber. 게임 상태를 읽어 그린다
ui/
HUD·인벤토리·로비. DOM 으로 그린다

층은 아래에서 위로만 안다. game/ 의 스윙 곡선과 채집 판정은 화면 없이 굴려 볼 수 있어야 해서 three 를 들이지 않는다.

감각 상수는 game/tuning 한 곳에만 둔다. 숫자를 만져 가며 손맛을 잡는 작업이라, 만질 자리가 여러 곳이면 못 잡는다. 실제로 조준 반지름과 충돌 반지름이 두 파일에서 어긋나 있던 적이 있다.

스윙은 축 하나와 각 하나다. 오일러 세 축을 손으로 섞었더니 스윙이 도는 평면과 날이 뻗은 축이 어긋나서 도끼가 옆면으로 때렸다.

걸린 것

증상과 원인이 달랐다

첫날 잡은 것들은 대개 증상과 원인이 달랐다.

증상원인
때리는 순간 프레임이 끊긴다히트스톱이 완전 정지라 명중마다 5프레임 같은 화면을 그렸다. 감속으로 바꿨다
나무가 땅에 파묻힌다쓰러지는 각이 90도가 아니라 97도였다
계단에서 막혔다 비벼야 오른다보간 중인 발 높이로 판정했고, 지면을 몸 중심 한 점으로만 쟀다
벽이 뚫린다콜라이더마다 밀 방향을 새로 골라 두 조각 사이에서 서로 반대로 밀었다

인벤토리를 연 동안 세계가 멈춰야 하는데, 프레임을 쓰는 코드가 멈춤 게이트를 빠뜨려 적이 계속 때리던 일은 세 번 났다. 그래서 프로젝트 문서 맨 앞 규칙으로 올렸다.

시스템이 먼저 쌓였다

장르가 한 달 사이 여러 번 바뀌었다. 액션 RPG(08-10) -> 로그라이크(08-13) -> 로그라이크 확정(08-18) -> 도끼 액션 어드벤처(08-23) -> 1스테이지 기획서로 1장 구조 전면 교체(08-25).

원인은 순서였다. 시스템을 먼저 쌓고 거기에 이야기를 맞춰 와서, 로그라이크와 퀘스트가 부딪히고 어드벤처로 옮기면 전승 시스템이 죽고, 그때마다 앞엣것을 다시 뜯었다. 08-23 에는 첫 사이클 테스트 뒤 지형·캠프·소리를 통째로 되돌렸다. 되돌릴 것과 남길 것을 먼저 갈라 두어서 리버트 하나로 끝났다.

Decision #02이야기를 먼저 확정하고 시스템을 문서에서 내린다

순서를 바로 세웠다. 스토리와 등장인물을 먼저 확정하고, 기획된 시스템을 문서상으로 전부 내리고, 추구미를 고른 뒤 그에 맞게 구현한다. 내린 시스템은 지우지 않고 보관했다. 왜 그렇게 정했는지의 근거가 같이 사라지면, 새 방향을 고른 뒤에 같은 고민을 처음부터 다시 하게 된다.

여러 세션이 한 폴더를 쓴다

이 프로젝트는 여러 AI 세션이 같은 작업 트리를 동시에 고친다. 브랜치가 아니라 한 폴더라 병합이 없다. 튜닝 상수를 일괄 치환하다 아홉 개가 동시에 깨졌는데 빌드는 통과해서 안 잡힌 적이 있고, git add 가 남의 편집을 끌고 간 적도 있다.

그래서 코드 지도 맨 앞에 규칙 셋을 박았다. 파일을 통째로 다시 쓰지 않는다(부분 치환만), 자기 구역 밖은 배선 한두 줄로, 끝나면 바로 커밋. 누가 어디를 만지는지 미리 갈라 두는 것이 유일한 안전장치다.

쿼터뷰 판정판

2026-09-13 부터 1인칭 본판과 별개로 쿼터뷰 판정판(src/q)을 짓고 있다. 본판 코드를 한 줄도 임포트하지 않는 격리된 베이스이고, 도끼 성역 한 화면에서 걷기·보기·휘두르기를 시험한다. 옮기기로 확정한 것이 아니라 판정하는 중이다.

카메라는 내려다보는 각 52.7도, 방위 45도, 화각 28도로 고정이다. 거리 40 은 "사람이 화면 세로의 몇 %인가" 를 실루엣 폭과 키 두 잣대로 재서, 둘이 7% 로 만나는 자리로 골랐다. 나무 꼭대기가 화면 밖으로 잘리는 건 받아들였다.

여기서 걸린 것이 둘이다.

화면이 조잡해 보였다. 그늘이 검은 게 아니라 아예 없었다. 본판에서 반구광 세기 하나만 옮기고 짝(림광·땅 쪽 색·정점 AO)을 안 옮겨서, 해가 안 닿는 세로면이 윗면 밝기의 6.8% 였다. 본판이 이미 고친 병을 다시 앓은 것이라 짝을 다 옮겼다.

주인공이 여러 번 반려됐다. 정면을 보고 그린 시안, 값으로 지어낸 형태가 둘 다 이 카메라에서 사람으로 안 읽혔다. 52.7도에서는 정수리가 보이는데 캐릭터 디자인은 정면 기준으로 만들어지기 때문이다. 결국 주인공을 깎은 나무로 바꿨다. 관절이 보이는 것도 얼굴이 없는 것도 나무라면 의도가 된다.

Decision #03나뭇결도 그림 파일 없이 셰이더로

나무 몸의 결은 텍스처도 UV 도 없이 셰이더로 굽는다. 축에서의 거리로 가르면 둥근 몸에 닫힌 고리가 생겨 대리석 얼룩이 되므로 나란한 평면으로 자른다. 여기서 셰이더 지역 변수를 patch 라 이름 지었다가 주인공이 한 조각도 안 그려진 적이 있다. GLSL 예약어라 컴파일이 실패하고, three 는 그 재질을 쓴 부위를 말없이 건너뛴다. 타입 검사도 빌드도 통과했다.

출시 껍데기

브라우저 게임이라 스팀에 내려면 exe 껍데기가 필요하다. Electron 으로 정했다. Tauri 가 아니다. Steamworks 공식 라이브러리가 Electron·NW.js 용이고, Tauri 쪽은 커뮤니티 플러그인뿐인 데다 Mac·Linux 에서 OS WebKit 을 써서 3D 가 Chromium 만 못하다.

껍데기는 창 하나와 세이브 파일이 전부다. 웹에서는 브라우저 저장소에 저장하지만 껍데기에서는 사용자 데이터 폴더의 파일로 쓴다. 임시 파일에 다 쓰고 이름을 바꾸므로 쓰다가 꺼져도 반쪽 세이브가 안 남고, 창이 닫히는 길이 여럿이라(X · Cmd+Q · Alt+F4) 닫히는 자리가 하나뿐인 껍데기가 마지막 저장을 붙잡는다. 로비에서 끄면 안 쓴다. 그때 세계는 처음 상태라 그대로 쓰면 멀쩡한 판이 빈 판으로 덮인다.

성능 측정에서도 껍데기가 기준이 됐다. 4K 모니터에서 크롬 전체화면은 35fps 로 떨어지는데, 같은 빌드를 Electron 으로 띄우면 안 떨어진다. 그때 로직이 1.2ms 였다. 로직이 1ms 대인데 프레임이 30ms 대면 우리 코드가 아니다. 출시 껍데기가 Electron 이니 브라우저 전체화면은 고칠 대상에서 뺐다.

기술 스택

  • 렌더: React Three Fiber · three · drei · postprocessing (물리 엔진 없음)
  • 앱: Vite · React · TypeScript
  • 형태: 자체 복셀 파이프라인(greedy meshing · 정점 AO · 인스턴싱) · MagicaVoxel .vox 로더 · 시안 삼면도 깎기 스크립트(Python)
  • 껍데기: Electron · electron-builder (Windows 설치 파일)

왜 이 스택

  • 물리 엔진 없음: 복셀은 형태가 곧 격자라 충돌이 형태에서 나온다. 조각 배치 시절에 콜라이더를 손으로 넣다 뚫렸던 자리를 그렇게 지웠다.
  • 복셀을 코드로 굽기: 에셋을 안 만든다는 축 그대로다. 사람은 시안 삼면도를 깎아 격자로 만든다. 시안을 보고 상자 목록을 손으로 적으면 무늬가 빠지는데, 정투영 세 장을 교차시키면 색째로 격자가 나온다.
  • Electron: 위 출시 껍데기 절.

지금 상태와 남은 것

미출시다. 2026-08-18 에 "한 달 안에 1장을 발매 퀄리티로 끝내고 얼리 액세스로 낸다" 는 목표를 세웠다. 1장 하나에 보스 하나로 범위를 좁힌 이유는, 보스 하나가 예고·판정·피드백·밸런스 네 겹이라 4장에 보스 넷을 발매 퀄리티로 올리면 몇 년이 걸리기 때문이었다. 그 기한은 지났고 아직 내지 않았다.

1인칭 본판: 1스테이지 조각은 다 있는데, 처음부터 끝까지 이어 붙여 본 기록이 아직 없다
기획: 1스테이지 보스 배치, 종료 조건, 세이브포인트 자리, 일부 인물 대사가 미확정이다
쿼터뷰: 판정 중. 몸 비율과 공중 자세가 판정 대기다
출시: Electron 껍데기와 Windows 설치 파일은 로컬에서 구워진다. 스팀 등록과 스토어 페이지는 아직이다

손맛은 화면 앞에서 사람이 판정한다. 헤드리스 캡처와 로직 시뮬레이션으로 손맛을 판정하려다 세 번 실패해서, 자동 검증은 타입 검사와 빌드 통과까지로 정했다.