# Spire Story — LLM과 함께 처음 만든 메이플스토리 덱빌딩 게임

- URL: https://soohan.dev/projects/nexon/spire-story
- Category: projects/nexon
- Published: 2026-09-29
- Tags: gamedev, maplestory, verse8, react, typescript, ai-coding
- Author: 박수한 (Soohan Park)

> 게임 개발 경험 없이 LLM과 함께 메이플스토리 덱빌딩 로그라이크를 만들어 Verse8에 공개했다. 캐릭터를 움직이고, 배경과 음악을 채우고, 전투와 레벨 디자인을 다듬어 4,995회의 플레이와 이용자 피드백을 받기까지의 기록이다.

---

지난 여름, MSU에서는 Verse8과 함께 Vibe Camp를 열었습니다.
Vibe Camp를 준비하면서 처음 게임을 개발한 경험과 그때 느낀 점을 정리해봤습니다.

게임 개발의 ㄱ자도 모르던 제가 게임을 하나 만들었습니다.
[Spire Story](https://verse8.io/N2j2S3T)라는 메이플스토리 덱빌딩 로그라이크입니다.
LLM과 함께 코드를 쓰고, 이미지와 음악을 채워 넣고, 직접 플레이하며 고쳐서 Verse8에 공개했습니다.

시작할 때는 몰랐던 일이 많았습니다. 캐릭터를 화면에 띄운 뒤에도 UI와 UX, 배경 이미지,
BGM, 효과음이 필요했습니다. 카드와 몬스터가 동작한 다음에는 어느 순서로 만나게 하고,
얼마나 강하게 만들지 정해야 했습니다. 특히 레벨 디자인은 예상보다 어렵고 복잡했습니다.

<Img src="https://nptgbqzcpg7dbphu.public.blob.vercel-storage.com/images/projects/nexon/spire-story/spire-story-cover.png" width="1254" height="1254" alt="메이플스토리 캐릭터와 카드, 탑을 그린 Spire Story 대표 이미지" caption="Verse8에 공개한 Spire Story의 대표 이미지." />

## 시그너스 기사단으로 덱을 쌓는 게임

Spire Story는 Slay the Spire처럼 갈림길을 고르고, 전투 보상으로 카드를 모으며 덱을 만드는
게임입니다. 지도에는 일반 전투와 엘리트 전투, 상점, 휴식, 이벤트가 있습니다. 다음 전투를
버틸 체력과 덱 구성을 보면서 어느 길로 갈지 선택합니다.

<Img src="https://nptgbqzcpg7dbphu.public.blob.vercel-storage.com/images/projects/nexon/spire-story/spire-story-battle.png" width="1600" height="1000" alt="미하일이 옥토퍼스와 슬라임을 상대하는 카드 전투 화면. 공격 카드 사용 후 옥토퍼스의 체력이 6으로 줄어 있다." caption="로컬에서 직접 플레이한 전투 화면. 적의 다음 행동과 남은 에너지를 보고 공격과 수비 카드를 고릅니다." />

처음에는 시그너스 기사단의 다섯 캐릭터를 고를 수 있습니다. 캐릭터마다 카드와 전투 방식이 다릅니다.

| 캐릭터 | 전투 방식 |
| --- | --- |
| 미하일 | 방어도를 쌓고, 방어와 공격을 연결합니다. |
| 오즈 | 화상을 쌓아 지속 피해를 줍니다. |
| 이리나 | 순풍을 활용해 공격을 강화합니다. |
| 이카르트 | 독과 은신을 활용합니다. |
| 호크아이 | 공격을 이어 쓰며 연계를 쌓습니다. |

같은 보스를 만나도 어떤 캐릭터를 골랐는지에 따라 필요한 카드가 달라집니다.
기본 캐릭터는 1막부터 4막까지 진행합니다. 다섯 캐릭터로 모두 클리어하면 숨겨진 캐릭터인
루미너스가 열리고, 루미너스는 별도의 5막에서 검은 마법사를 상대합니다.

## Verse8에서 시작해 로컬에서 다듬기

개발은 Verse8에서 시작했고, 중반 이후에는 로컬 환경에서 작업을 이어갔습니다.
Claude Code로 구현과 수정을 진행하고, Codex로 디버깅과 추가 검토를 했습니다.
게임의 BGM과 SFX는 Suno로 만들고, 이미지·시네마틱 제작과 배포에는 Verse8을 썼습니다.

게임 본체는 React 18, TypeScript, Vite로 만들었습니다. 화면은 DOM과 CSS, SVG로 그리고,
전투 규칙과 진행 상태는 화면 컴포넌트에서 분리했습니다. 진행 상황과 캐릭터 해금 기록은
브라우저의 `localStorage`에 저장하도록 했습니다.

개발 중 대화는 짧은 요청과 수정의 반복이었습니다. 초기 작업 기록에는 이런 요청이 남아 있더라고요.

> 캐릭터나 몹이 너무 가만히 있는 것 같아. 움직이는 스프라이트 이미지를 적용해줄 수는 없어?

움직이게 만든 뒤에는 공격할 때와 맞을 때의 동작이 필요했습니다. 이미지를 미리 불러오게
수정한 뒤에도 몬스터에는 빠져 있어서 다시 요청했습니다. 화면을 보고 부족한 부분을 설명하고,
수정된 결과를 다시 플레이하는 식이었습니다.

나중에는 다른 사람도 참고하도록 기능별 구현 프롬프트를 정리해두었습니다.

> // 예시
> 
> 플레이어가 한 런 시작 시 선택하는 캐릭터 6명. 각자 시작 HP·기본 데미지 타입·패시브 컨셉이 다르고, 카드 풀이 그 컨셉에 맞춰 짜여 있다.
> 
> - **미하일 (전사)** — HP 75, 물리 데미지. 패시브 컨셉: *방어도 시너지* — 방어도를 보유한 상태에서 공격하면 추가 효과. 카드 다수가 "방어도 보유 시 +N 피해" 또는 방어도를 데미지로 변환하는 효과.
> - **오즈 (마법사)** — HP 65, 화염. *화상 DoT* 위주. 적에게 burn 디버프를 쌓아 매 턴 종료 시 도트 피해. 카드들은 burn 가산 / burn 곱셈 / burn 수치로 한 번에 폭발시키는 효과.
> - **이리나 (궁수)** — HP 70, 바람. *순풍(tailwind) 콤보* — 순풍 스택을 쌓을수록 다음 공격에 추가 피해, 또는 순풍 만큼 멀티히트.
> - **이카르트 (도적)** — HP 68, 그림자/암속. *독+은신* — 적에게 poison(즉발 + 지속 일부) + 은신 진입 시 다음 공격 배수.
> - **호크아이 (해적)** — HP 70, 번개. *링크(link) 콤보 스케일링* — 공격 카드를 사용할 때마다 link +1, link 수치에 비례해 데미지가 커지는 카드들.
> - **루미너스 (히든)** — HP 125, 신성/암속 둘 다. *이퀼리브리엄* — 빛/어둠 두 게이지의 균형 메커니즘. 베이스 5명 모두 클리어해야 해금.
> 
> 각 캐릭터는 시작 덱 10장(루미너스만 11장)으로 시작: 기본 공격 5장 + 기본 방어 4장 + 그 캐릭터의 시그니처 카드 1장. 시작 골드 99G(루미너스 180G). 시작 유물 없음(루미너스만 1개 보유).
> 
> 캐릭터 선택 화면에는 그리드 형태로 6슬롯을 보여준다(루미너스는 잠금 시 숨김). 각 슬롯에 캐릭터 NPC 스프라이트 미리보기, 이름, 패시브 한 줄 설명, 캐릭터별 테마 색상(미하일 황금, 오즈 진홍, 이리나 연두, 이카르트 보라, 호크아이 청색, 루미너스 연보라). 이미 엔딩 본 캐릭터는 트로피 아이콘 표시.

이처럼, 정리한 문서에는 “이어하기를 만들어줘”보다 구체적인 조건을 담았습니다. 전투 중 메뉴에
나갔다 돌아오면 체력과 손패를 유지하고, 브라우저를 새로고침하면 진행 중이던 전투를
처음부터 다시 시작하게 했습니다. 지도에는 전투에서 승리한 뒤 클리어 기록을 남기도록
정했습니다. 이렇게 기능마다 동작을 확인할 기준을 적었습니다.

## 이미지 하나 뒤에 따라오는 일

움직이는 캐릭터에는 서 있기, 공격하기, 맞기 같은 동작마다 여러 이미지가 필요했습니다.
전투 중에 프레임을 새로 불러오면
깜빡임이 생겼고, 존재할 것으로 추측한 이미지 URL을 요청했다가 실패하는 경우도 있었습니다.

실제로 확인한 리소스 목록을 기준으로 로딩 대상을 정리하고, 게임에 들어가기 전에 이미지를
미리 불러오도록 바꿨습니다. 현재 코드는 `Image` 객체로 이미지를 읽고 `decode()`가 끝난 뒤
로딩 완료로 처리합니다. 동시에 같은 URL을 요청하면 이미 진행 중인 작업을 함께 기다립니다.
화면에서는 브라우저에 캐시된 이미지를 사용합니다.

로딩 방법도 바뀌었습니다. 처음에는 이미지를 일정 개수씩 묶어 불러오고 잠시 기다렸지만,
이후에는 하나가 끝나면 다음 이미지를 바로 읽도록 수정했습니다.

배경을 붙이는 작업도 생각보다 어려웠습니다. 4월에 팀에 진행 상황을 공유할 때는 단순히
배경으로 쓰려는 맵조차 잘 불러와지지 않는다고 적었습니다. 동시에 로컬에서 빌드하며
작업하는 것은 정말 편하다는 이야기도 남겼습니다.

<Img src="https://nptgbqzcpg7dbphu.public.blob.vercel-storage.com/images/projects/nexon/spire-story/spire-story-cinematic.png" width="1600" height="1000" alt="검과 방패를 든 미하일의 게임 시작 시네마틱 장면" caption="미하일을 선택한 뒤 재생되는 시네마틱의 한 장면. 게임을 실행해 캡처했습니다." />

음악과 효과음도 필요했습니다. BGM은 Suno로 만들었고,
[Spire Story OST 플레이리스트](https://suno.com/playlist/b72b9c8f-588c-4c5e-a45a-9a624fddab06)로도
모아 두었습니다. 메뉴와 지역, 보스전에 맞는 음악을 준비하고, 카드 사용이나 피격에 맞는
효과음도 붙였습니다. 일부 효과음은 코드로 합성했습니다.

UI와 UX를 정하고, 배경과 캐릭터를 배치하고, 음악과 효과음을 연결하는 일이 계속 이어졌습니다.
직접 만들어 보니 게임 하나에 필요한 애셋이 이렇게 많다는 것을 실감했습니다.

## 보여준 피해량과 실제 피해량을 맞추기

카드 게임에서는 화면에 적힌 숫자가 다음 선택의 근거가 됩니다. 이 게임도 카드의 효과와
적의 다음 행동을 미리 보여주는데, 전투 로직과 미리보기 계산이 어긋나는 문제가 있었습니다.

예를 들어 적이 한 번의 행동 안에서 플레이어에게 취약을 건 뒤 공격한다면, 미리보기에도
취약 때문에 늘어날 피해가 반영돼야 합니다. 행동을 시작하기 전 상태만 보고 계산하면 화면에 나온
숫자보다 더 큰 피해를 받습니다.

적의 행동을 순서대로 미리 계산하도록 고쳤습니다. 앞선 행동이 상태를 바꾸면 그 상태로
뒤의 공격 피해를 계산하는 방식입니다. 카드 미리보기에서도 약화 효과를 적용하는 순서를
실제 피해 계산과 맞췄습니다.

이 밖에도 카드 설명과 실제 효과, 부활 효과가 발동하는 조건을 수정했습니다.
전투 화면이 움직인 뒤에도 플레이어가 그 화면을 믿고 선택할 수 있게 만드는 작업이 남아 있었습니다.

## 예상보다 어려웠던 레벨 디자인

이번에 가장 어렵고 복잡하다고 느낀 부분은 레벨 디자인이었습니다. Spire Story에서는
몬스터의 체력과 공격력뿐 아니라, 전투와 휴식의 배치, 보상으로 얻는 카드,
상점에 도착할 때의 자원까지 한 판의 난이도에 영향을 줍니다.

지도에는 무작위로 갈라지는 경로를 넣되, 중간 보스를 거쳐 다음 구간으로 가게 했습니다.
같은 시드에서는 같은 지도가 만들어집니다. 지도에 놓이는 전투와 휴식, 상점의 비중은
진행 구간에 따라 달라집니다.

<Img src="https://nptgbqzcpg7dbphu.public.blob.vercel-storage.com/images/projects/nexon/spire-story/spire-story-map.png" width="1600" height="1000" alt="헤네시스 지도에서 시작 지점이 두 전투와 상점으로 갈라지고, 그 위로 전투와 휴식 경로가 이어지는 화면" caption="헤네시스의 경로 선택 화면. 전투와 휴식, 상점의 배치가 한 판의 난이도에 영향을 줍니다." />

여기에 캐릭터별 차이도 있습니다. 방어도를 활용하는 미하일과 독을 쌓는 이카르트는
같은 적을 상대하는 방식이 다릅니다. 카드 한 장을 바꾸면 다른 카드나 유물,
적의 상태 효과와 맞물리는 방식도 확인해야 합니다. 확인할 조합이 계속 늘어났습니다.

6월 1일에는 게임을 완성했고 공개 전까지 직접 플레이하며 놓친 버그를 잡겠다고 팀에
공유했습니다. LLM으로 구현을 진행하면서도, 한 판을 따라가며 난이도와 선택지를 확인하는
과정은 계속 필요했습니다.

## 클리어 기념 카드를 지갑에 남기기

그래도 나름 **블록체인 개발자**인데... 만든 게임에 블록체인이 없으니 뭔가 아쉽더라고요.

캐릭터로 게임을 클리어한 뒤에는 기념 NFT를 민팅하는 기능을 추가해봤습니다. 게임에서 클리어 기록을
확인하고, 지갑을 연결해 해당 캐릭터의 카드를 받는 흐름입니다.

단순한 ERC-721 컨트랙트를 Avalanche C-Chain에 배포했습니다.
한 지갑은 캐릭터별로 한 번씩 민팅할 수 있습니다.
클리어 여부는 게임의 로컬 기록으로 확인하며, 컨트랙트가 전투나 클리어 과정을 검증하지는 않습니다.
단순한 게임 클리어 리워드니까, 굳이 복잡한 기능은 넣지는 않았습니다. _(나중에 이 Spire Story가 더 흥한다면 또 모르겠지만요ㅎㅎ)_

7월에는 [SpireStory 컬렉션](https://opensea.io/collection/spirestory)을 보고,
생각보다 민팅한 사람이 꽤 있는 것을 봤습니다. 심지어 FP는 `$2,326` 이네요;;

아무튼, 제가 붙여 둔 기능을 실제로 이용하는 사람이 있다는 것이 뿌듯했습니다.

## 4,995번의 플레이와 몇 줄의 댓글

2026년 6월 8일, Verse8 MSU Space가 열리면서 Spire Story도 공개했습니다.
**2026년 9월 29일 확인한 [Verse8 게임 페이지](https://verse8.io/N2j2S3T)의 누적 플레이 수는
4,995회, 좋아요는 45개**였습니다.

댓글에는 이런 반응도 있었습니다.

> so cool!

— CNTikTokC2, 2026년 6월 9일 · [Verse8 댓글](https://verse8.io/N2j2S3T)

> both good and crisp?

— socket, 2026년 7월 5일 · [Verse8 댓글](https://verse8.io/N2j2S3T)

스토리가 더 있었으면 좋겠다는 반응도 있었습니다. 그 사이에서 이렇게 좋았다는 말을
남겨준 댓글은 반가웠습니다. 게임 개발을 모르던 제가 만든 게임을 누군가 플레이하고,
반응을 남겨줬다는 사실만으로도 뿌듯했습니다.

이번 경험을 통해 이제는 정말 아이디어와 기획력만 있으면 뭐든 만들어 볼 수 있겠다는 생각이
들었습니다. UI와 UX부터 음악, 효과음, 배경, 레벨 디자인까지 직접 정해야 하는 것이 얼마나
많은지도 알게 됐습니다.

지금은 추가 개발을 하고 있지 않습니다만, 게임은 여전히 [Verse8에서 플레이](https://verse8.io/N2j2S3T)할
수 있습니다.
