WIKI나라런
개인 제품·2025 ~ 2026·기획·개발 단독

나라런

생일 선물로 만든 러너. 같이 갔던 길을 달린다.

ReactViteCanvas 2Dstyled-componentsWeb Audio API

나라런은?

생일 선물로 만든 러닝 게임이다. 실제 도시를 달리면서 흩어진 글자를 주워 "HAPPY BIRTHDAY"를 완성한다.

시작은 쿠키였다. 쿠키를 구워서 주려다가, 쿠키만 주기에는 뭔가 심심해서 쿠키를 받으러 가는 길을 만들었다. 그렇게 만든 게 이것이다.

시작 화면. 여기서 PRESS TO START가 뜬다는 건 게임 리소스를 전부 받았다는 뜻이다.
시작 화면. 여기서 PRESS TO START가 뜬다는 건 게임 리소스를 전부 받았다는 뜻이다.

코스는 지도에서 고르지 않았다. 같이 갔던 곳 순서로 골랐다. 문래동에서 출발해 광화문, 청계천을 지나 판교에서 끝난다. 각 구간 1,000m씩 4,000m다. 중간에 지나가는 건물들도 마찬가지다. 배경을 채우려고 놓은 게 아니라, 그 사람과 같이 갔던 건물이라서 놓았다.

그래서 이 게임은 실제 지리 순서이면서 동시에 기억 순서다. 달리다 보면 그날들이 순서대로 나온다.

인트로. 왜 여기서 달려야 하는지를 먼저 설명한다.
인트로. 왜 여기서 달려야 하는지를 먼저 설명한다.

시작 화면이 진짜로 하는 일

여기서 하나 배웠다.

처음에는 시작 화면 없이 바로 달리게 했다. 그랬더니 배경과 소품이 안 그려진 채로 지나갔다. 이미지를 그 자리에서 처음 요청하니까, 캐릭터가 이미 그 지점을 통과한 뒤에야 도착한다. 랜드마크 하나 보여주려고 만든 구간을 빈 하늘로 지나가는 것이다.

그래서 시작 화면을 프리로더로 만들었다. 게임에서 쓰는 이미지를 전부 미리 받고, 다 받은 뒤에만 PRESS TO START를 띄운다. 시작 버튼이 보인다는 게 곧 준비가 끝났다는 신호다.

전체 이미지 목록 수집
게임 자산 + 인트로 일러스트를 한 배열로
동시 로드 · 진행률 집계
실패해도 진행률은 올린다. 한 장 때문에 못 들어가면 안 되니까
표시 진행률 보정
역행 금지(단조 증가) + 최소 노출 900ms
PRESS TO START 노출
전부 로드된 뒤에만
8초 안전망
네트워크가 느리면 강제로 진행

진행률 표시에 손이 제일 많이 갔다. 캐시가 있으면 순식간에 100%가 되어 화면이 깜빡이고, 없으면 뚝뚝 끊긴다. 그래서 실제 진행률과 시간 기반 진행률 중 느린 쪽을 따라가게 하고, 한 번 올라간 숫자는 절대 되돌아가지 않게 했다. 로딩 바가 뒤로 가면 사람은 뭔가 잘못됐다고 느낀다.

로드에 실패한 이미지도 진행률은 올린다. 소품 한 장 못 받았다고 게임 자체를 못 들어가는 게 더 나쁘다.

실제 지도를 한 줄로 펴기

이 게임의 세계는 1차원이다. 모든 것이 "몇 m 지점"으로만 존재한다. 랜드마크도, 정류장도, 주울 글자도, 길가 벤치도 전부 같은 축 위에 예약된다.

그래서 배치가 곧 설계가 된다. 큰 조형물이 200m를 차지하면 그 앞에는 낮은 소품만 놓는다. 벤치에 앉아서 조형물을 보는 그림이 되어야 하니까. 다음 동네의 역은 구간이 바뀌기 직전에 둔다. 역이 보이면 곧 넘어간다는 신호가 되도록.

문래동. 낮은 상가가 줄지어 이어지는 길, 이른 아침.
문래동. 낮은 상가가 줄지어 이어지는 길, 이른 아침.
광화문. 세종대왕상과 경복궁, 뒤로 북악산.
광화문. 세종대왕상과 경복궁, 뒤로 북악산.

배경은 세 겹이고 하늘은 거리에 따라 바뀐다. 문래동은 이른 아침, 광화문은 낮, 청계천은 노을, 판교는 밤이다. 4,000m를 달리는 동안 하루가 지나간다. 하늘을 갈아치울 때는 200m를 걸쳐 서서히 섞는다. 한 프레임에 바꾸면 조명이 튀기 때문이다.

판교 진입. 마지막 구간은 밤이다.
판교 진입. 마지막 구간은 밤이다.

글자는 생기는 자리와 줍는 자리가 다르다

제일 오래 붙잡은 버그다.

글자를 "1,500m 지점"에 놓으면 1,500m에서 주워질 것 같지만 아니다. 글자는 화면 오른쪽 바깥에서 생성되어 캐릭터 쪽으로 흘러오기 때문에, 실제로 주워지는 건 40~65m 뒤다. 이걸 모르고 구간 끝에 딱 맞춰 놓으면, 마지막 글자가 다음 구간으로 넘어가면서 사라진다.

두 가지로 못 박았다. 배치 좌표는 수집 지점 기준으로 역산하고, 각 구간의 마지막 글자는 구간 끝에서 최소 150m 안쪽에 둔다. 그 뒤로 글자를 놓치는 일이 없어졌다.

받는 사람이 글자 하나를 못 먹어서 "HAPPY BIRTHDA"로 끝나면 그건 버그가 아니라 사고다.

HAPPY 5글자와 BIRTHDAY 8글자를 다 모으면 피버타임이 열린다.
HAPPY 5글자와 BIRTHDAY 8글자를 다 모으면 피버타임이 열린다.

13글자를 다 모으면 피버타임이 발동한다. 이때 쏟아지는 케이크는 무작위가 아니라 받는 사람 이름 모양으로 떨어진다. 이런 건 아무도 눈치 못 챌 수도 있는데, 그래도 넣었다.

4,623
줄 · 게임 본체 한 파일
4
구간 · 문래동에서 판교까지
3
패럴랙스 레이어
2
빌드 · 두 사람

건물을 직접 만들 수 있다는 것

배경과 랜드마크는 전부 생성형 AI로 만들었다. 이게 이 프로젝트에서 제일 즐거웠던 부분이다.

보통은 있는 에셋에 맞춰 기획을 접는다. 쓸 만한 건물 스프라이트가 없으면 그 구간을 포기하거나 비슷한 걸로 때운다. 여기서는 반대였다. 필요한 건물을 이름으로 부르면 나왔다.

이 기획에는 이 방법 말고 답이 없었다. 코스가 같이 갔던 곳으로 짜여 있으니 필요한 건 "적당한 도시 건물"이 아니라 바로 그 건물이다. 에셋 스토어에 파는 건물 세트에는 우리가 갔던 그 건물이 없다. 있는 것 중에 고르는 방식이었다면 이 게임은 그냥 아무 도시를 달리는 러너가 됐을 것이다.

에셋이 기획을 제한하지 않는 경험은 이때가 처음이었다.

두 번째 요청이 들어왔다

만들어서 준 뒤에 반응이 좋았고, 그걸 본 다른 친구가 자기 것도 만들어 달라고 했다.

그래서 두 번째를 만들었다. 서울역에서 출발해 여의도 한강, 잠실, 롯데월드로 이어지는 5,000m다. 코스가 통째로 다르고, 캐릭터 스프라이트는 비례부터 달라서 렌더 분기가 필요했다.

두 번째 빌드. 같은 엔진인데 63빌딩과 한강철교를 달린다.
두 번째 빌드. 같은 엔진인데 63빌딩과 한강철교를 달린다.

그런데 게임 로직은 하나였다. 좌표 예약, 글자 배치, 패럴랙스, 프리로드가 전부 그대로 돌았다. 두 번째를 만들 때 한 일은 사실상 지도를 새로 적는 것뿐이었다.

재사용을 설계했다고 주장하는 것과, 실제로 두 번 돌려보는 것은 다르다. 그리고 두 번째가 남이 부탁해서 만든 것이라는 게, 첫 번째가 잘 됐다는 제일 확실한 증거였다.

기술 스택

  • React + Vite · styled-components · react-router
  • 렌더: Canvas 2D · requestAnimationFrame 루프
  • 오디오: Web Audio API (모바일 첫 재생 지연 회피)
  • 배포: Vercel · 가로 모드 전용 (세로면 회전 안내)

주소는 안 걸어둔다

이건 선물이라서, 받는 사람의 것이다. 여기 있는 건 화면과 만드는 과정까지고 주소는 적지 않는다.