← 프로젝트 회고 · Frontend
RETROSPECTIVE · 회고

레거시 캔버스 게임을 '동작은 그대로' 다시 쓰기: 멜론게임 리팩터링

Frontend

2021년에 만든 멜론게임(합이 10이 되게 드래그해서 지우는 게임)을 다시 만들었다. 조건은 하나였다. 랭킹이 쌓여 있는 게임이라 판정·점수·시간은 한 치도 달라지면 안 된다.

문제: 규칙이 그리기 코드 속에 섞여 있음

예전 코드는 캔버스 3장(배경·보이는 판·숨긴 판)을 겹쳐 쓰고, 모듈 전역 변수로 상태를 들고, 이벤트 핸들러 안에서 좌표 계산과 판정과 그리기를 같이 했다. 게임을 시작할 때마다 리스너를 또 붙여서 판을 거듭할수록 핸들러가 쌓이는 버그도 있었다.

먼저 규칙만 뽑아내기

화면을 건드리기 전에 판정 규칙을 순수 함수로 옮겼다. 옮기면서 예전 동작을 그대로 재현해야 하는 미묘한 부분이 보였다.

  1. 칸 번호는 Math.trunc(좌표 / 40)이다. Math.floor와 달리 판 왼쪽 바깥 -39px도 0번 칸이 된다. 판 바깥에서 드래그를 시작해도 첫 칸이 잡히던 손맛이라 그대로 뒀다.
  2. 선택은 드래그 사각형의 두 꼭짓점이 걸친 칸 전체(양 끝 포함)다.
  3. 시간은 130에서 100ms마다 0.11씩 줄고 0 밑으로 가면 끝난다. 약 118초인데, '2분'이라는 숫자로 바꾸지 않고 같은 setTimeout 틱을 유지했다.
export function rangeOf(ax: number, ay: number, bx: number, by: number): Range {
return {
c0: Math.trunc(Math.min(ax, bx) / CELL),
r0: Math.trunc(Math.min(ay, by) / CELL),
c1: Math.trunc(Math.max(ax, bx) / CELL),
r1: Math.trunc(Math.max(ay, by) / CELL),
};
}

화면은 새로

남은 멜론은 판이 바뀔 때만 오프스크린 캔버스에 그려 두고, 매 프레임에는 그 판 한 장과 선택 표시, 떨어지는 멜론 같은 연출만 그린다. 덕분에 터지는 애니메이션을 넣어도 프레임마다 252개를 다시 그리지 않는다. 2048px짜리 멜론 PNG를 256px로 줄인 것만으로 이미지 용량이 5.8MB에서 0.4MB가 됐다.

디자인은 처음에 어두운 톤으로 '세련되게' 갔다가 눈이 아프다는 피드백을 받고 파스텔 톤으로 다시 갔다. 연두색 멜론 위의 흰 숫자도 잘 안 보여서, 멜론 이미지의 숫자 픽셀만 골라(굵은 영역만 남기는 모폴로지 연산) 진한 초록으로 바꿨다.

배운 점

  1. '동작은 그대로'를 지키려면 리팩터링보다 먼저 지금 동작을 문장과 코드로 고정해야 한다. 경계값(trunc, 틱 간격)은 버그처럼 보여도 사용자에게는 손맛이다.
  2. 규칙을 화면에서 떼어내면 화면은 마음껏 바꿀 수 있다. 연출을 두 번 갈아엎는 동안 판정 코드는 한 줄도 안 바뀌었다.
  3. 예쁜 것과 오래 봐도 편한 것은 다르다. 게임 화면은 대비와 가독성이 먼저다.
Gunmo Lee
레거시 캔버스 게임을 '동작은 그대로' 다시 쓰기: 멜론게임 리팩터링 | Gunmo's Dev Life