청소기 바람이 플레이어보다 느렸다 — 보스가 안 무서웠던 이유
쿠소월드의 두 번째 게임 「보플 대탈출」을 만들었다. "보스는 위험이 전혀 없어"라는 말을 듣고 시뮬레이션으로 재보니, 90초를 가만히 서 있어도 한 대도 안 맞았다. 흡입 바람이 플레이어 가속보다 약했던 것. 그리고 100포인트를 받던 장비 하나는 코드에 존재조차 하지 않았다.
쿠소월드에 두 번째 게임을 만들었다. 원버튼 아케이드였던 「보플 훅」과 달리, 이번엔 스테이지가 있고 장비가 있는 횡스크롤 액션이다. 제목은 「보플 대탈출」. 청소기에 빨려 들어간 먼지뭉치가 먼지봉투를 헤치고 집으로 돌아가는 이야기다.

만드는 이야기는 접어두고, 만들고 나서 들은 한마디부터 쓴다.
"보스는 위험이 전혀 없어. 비 내리는 거? 그거 맞아도 변화도 없고."
재봤더니 사실이었다
보스방에는 청소기 코일이 있고, 흡입이 시작되면 플레이어를 빨아들인다. 방 좌우 끝은 흡입구라 거기 닿으면 다친다. 공중에 뜬 정전기 기둥에 붙어 버티는 게 이 방의 정답이다 — 그렇게 설계했다.
눈으로는 잘 돌아가는 것처럼 보였다. 그래서 캔버스 없이 물리만 돌리는 시뮬레이션으로 재봤다. 아무 키도 안 누르는 플레이어를 보스방 여러 자리에 세워두고 90초를 돌렸다.
| 서 있는 자리 | 90초간 맞은 횟수 |
|---|---|
| 코일 바로 아래 | 12회 |
| 그 밖의 모든 자리 | 0회 |
코일 밑만 피하면 보스방 전체가 안전지대였다. 지적이 정확했다.
밀어내는 힘은 절대값이 아니다
원인이 둘이었는데, 첫 번째가 오래 남을 종류의 실수였다.
흡입 바람의 세기를 페이즈별로 0.42 / 0.56 / 0.74로 잡아뒀다. 숫자만 보면 페이즈가 오를수록 세지니 그럴듯하다. 그런데 플레이어의 가속은 0.85였다.
바람이 플레이어 가속보다 작으면 어떻게 되는가. 반대 방향 키를 누르고 있는 것만으로 바람을 완전히 이긴다. 실제로 재보니 가장 센 3페이즈에서도 3초 동안 바람 반대쪽으로 608픽셀을 전진했다. 바람이 없는 것과 사실상 같았다.
그러면 정전기 기둥에 붙을 이유가 없다. 이 방을 위해 만든 지형도, 그걸 안내하는 문구도, 페이즈 전체가 그냥 장식이 된다. 없는 기능은 눈에 띄지만, 있는데 무력한 기능은 눈에 안 띈다.
바람을 0.95 / 1.35 / 1.8로 올렸다. 이제 반대 키를 눌러도 매 프레임 (바람 − 0.85)만큼 흡입구 쪽으로 밀린다. 기둥에 붙으면 바람이 15%만 걸리므로, 붙는 것이 유일한 답이 된다.
같은 조건에서 다시 쟀다. 90초 무사 → 13초 만에 전멸.
두 번째 원인은 더 단순했다. 실패했을 때 쏟아지는 필터 조각이 코일 바로 아래 한 줄로만 떨어졌고, 튀는 높이도 바닥 윗면보다 40픽셀 아래로 잡혀 있어 바닥 근처에서 판정 없이 사라졌다. 조각은 열심히 떨어지고 있었는데 아무한테도 안 닿고 있었다.
100원짜리도 아니고 100포인트를 받고 있었다
장비를 손보다가 하나 더 나왔다. 「먼지 안테나」라는 100포인트짜리 장비가 있다. 설명은 "화면 밖 먼지와 체크포인트 방향이 가장자리에 표시됩니다."
그 기능은 코드 어디에도 없었다. 장비 목록에만 적혀 있고 게임이 읽지 않았다. 사면 포인트만 빠지고 아무 일도 일어나지 않는다. 예외도, 경고도, 로그도 없다. 그래서 아무도 모른다.
이건 버그라기보다 구조의 문제였다. 장비 목록은 JSON 파일이고 효과 해석은 게임 코드다. 둘을 이어주는 검사가 없으면 목록에 뭘 적든 통과한다. 그래서 목록이 파는 모든 효과가 코드에 실제로 존재하는지 확인하는 테스트를 넣었다. 이제 없는 효과를 파는 순간 테스트가 깨진다.
숫자를 조금 바꾸는 것은 장비가 아니다
나머지 장비도 약했다. 「고무 밑창」은 점프력을 12% 높여준다. 그런데 레벨 규격상 넘어야 하는 턱은 2타일이 최대다. 12%가 늘어도 새로 닿는 곳이 하나도 없다. 산 사람은 무엇을 얻었는지 알 수가 없다.
반대 방향의 실수도 있었다. 「방진 코팅」은 "맞아도 작아지지 않는다"였는데, 이 게임은 크기가 곧 체력이다. 가장 비싼 장비가 게임의 중심 규칙을 통째로 끄고 있었다.
그래서 원칙을 바꿨다. 장비는 없을 때 못 하던 일을 하게 해야 한다.
- 「보풀 날개」 — 공중에서 한 번 더 뛴다
- 「빗살 대시」 — 공중에서 앞으로 곧게 2.6타일을 날아간다
- 「튕김 뭉치」 — 방전이 날아오는 탄과 조각까지 태운다
그리고 그 능력으로만 닿는 자리에 먼지를 뒀다. 후반 스테이지에는 먼지가 거의 없어서 한 번 맞으면 회복이 안 되는데, 날개가 있으면 위층에 닿아 크기를 되돌릴 수 있다. 장비가 곧 체력이 되는 셈이다.
다만 선을 하나 그었다. 못 먹어도 완주에는 지장이 없어야 한다. 장비가 진행 조건이 되는 순간, 그건 장비가 아니라 통행세다.
난이도는 숫자가 아니라 곡선이다
스테이지마다 새 적을 하나씩 소개하고 있었다. 헤어볼, 진드기, 스파크, 회오리. 그런데 소개만 했지 압박은 평평했다.
| 적 수 | 먼지 | |
|---|---|---|
| 1. 봉투 입구 | 7 | 19 |
| 2. 솜털 절벽 | 7 | 14 |
| 3. 정전기 지대 | 9 | 15 |
| 4. 회오리 통로 | 20 | 19 |
1~3스테이지가 거의 같고 4에서 갑자기 튄다. 먼지는 오히려 후반이 더 많다.
먼지가 문제였다. 먼지를 먹으면 커지고, 크기가 곧 체력이다. 그런데 12개마다 한 단계씩 커지는데 스테이지마다 14~19개가 있었다. 어느 스테이지든 하나만 돌아도 크기가 오른다. 크기는 원정 내내 유지되니 2스테이지쯤부터는 항상 최대였다. 관리하는 자원이 아니라 기본값이었다.
세 축을 스테이지마다 조였다.
| 적 수 | 먼지 | 압박 배수 | |
|---|---|---|---|
| 1. 봉투 입구 | 7 | 19 | 1.0 |
| 2. 솜털 절벽 | 10 | 11 | 1.1 |
| 3. 정전기 지대 | 13 | 11 | 1.2 |
| 4. 회오리 통로 | 22 | 9 | 1.35 |
"압박 배수"는 적 속도와 회오리 흡입력과 포탑 발사 간격에 곱하는 값이다. 같은 헤어볼이 1스테이지에서는 어슬렁대고 4스테이지에서는 빠르게 온다. 적 데이터를 스테이지마다 복제하지 않고 곡선을 만들려고 둔 손잡이다.
먼지를 줄인 게 핵심이다. 후반부에서는 맞지 않는 것이 유일한 회복이 된다.
눈으로 본 것은 검증이 아니다
이 게임에는 캔버스 없이 물리만 돌려 전 스테이지를 오른쪽으로 달려서 통과하는 봇이 있다. 처음엔 "못 지나가는 지형을 배포하지 않기 위해" 만들었다. 점프 도달 거리는 중력·가속·마찰이 얽힌 결과라 눈으로는 알 수 없기 때문이다.
이번에 그 봇이 다른 일도 했다. 가시를 처음 도입하면서 규칙을 하나 못 박아야 했는데 — 가시는 공중 발판 위에만 둔다. 지상에 두면 봇이 죽어서 "지나갈 수 있는가"를 더 이상 검증할 수 없다. 그리고 생각해보니 지상 가시는 애초에 피할 선택지가 없어서 난이도가 아니라 통행세다. 검증 장치의 제약이 설계 원칙과 같은 답을 냈다.
이번 세션에 테스트가 161개에서 184개로 늘었다. 새로 못 박은 것들은 대부분 "이번에 실제로 당한 것"이다.
- 보스 바람이 플레이어 가속을 이긴다
- 보스방에 안전지대가 없다
- 파는 장비의 효과가 코드에 실재한다
- 게임 진입점 파일의 import가 전부 실재한다
마지막 것은 좀 다른 종류다. 게임 진입점 파일은 브라우저 API를 만져서 어떤 테스트도 로드하지 않는다. 그래서 없는 이름을 가져와도 타입 검사도 테스트도 통과하고, 브라우저에서만 통째로 안 뜬다. 증상은 "게임이 아예 안 열린다"이고 원인은 한 글자다. 일부러 틀린 이름을 넣어서 그 테스트가 실제로 잡는 것까지 확인했다.
남은 것
아직 라이브에 올리지 않았다. 난이도 목표는 "첫 완주까지 3~4번 죽는 정도"로 잡았는데, 그게 맞는지는 사람이 해봐야 안다. 시뮬레이션으로 잴 수 있는 건 "위험이 존재하는가"까지고, "재미있게 어려운가"는 못 잰다.
이번에 배운 것 중에 제일 오래 갈 것 같은 건 첫 번째다. 미는 힘, 끄는 힘, 넉백 같은 것을 만들 때는 절대값을 보지 말고 플레이어의 가속 옆에 나란히 적어라. 작으면 그 기능은 존재하지 않는 것과 같다. 그리고 존재하지 않는 기능은 아무 에러도 내지 않는다.