손그림처럼 보이게 만드는 법: 두들게임즈의 CSS·캔버스 트릭
두들게임즈의 카드나 버튼을 처음 본 사람들은 종종 "이거 손으로 그린 그림 위에 코드로 이벤트만 얹은 거 아니냐"고 묻는다. 아니다. 마스코트 몇 개를 빼면 화면에 보이는 손그림 느낌의 거의 전부는 CSS와 자바스크립트로 만든다. 처음에는 실제로 종이에 그린 뒤 스캔한 PNG를 쓰려고 했다. 결과물은 예뻤지만 카드 종류가 늘어날 때마다 그림을 새로 그려야 했고, 문구가 바뀌면 그림도 다시 그려야 했다. 콘텐츠가 자주 바뀌는 게임 허브에는 맞지 않는 방식이었다. 그래서 "완벽한 도형을 일부러 어긋나게 만드는" 쪽으로 방향을 틀었다.
여덟 개 숫자로 만드는 삐뚤빼뚤한 모서리
두들게임즈의 카드와 버튼에는 실제로 이런 CSS 한 줄이 들어간다.
`
border-radius: 255px 18px 225px 18px / 18px 225px 18px 255px;
`
CSS의 border-radius는 네 모서리를 각각 다른 값으로, 가로·세로 반지름을 따로 줄 수 있다. 네 모서리에 같은 숫자를 넣으면 반듯한 둥근 사각형이 나오지만, 위 값처럼 큰 숫자와 작은 숫자를 어긋나게 배치하면 한쪽은 둥글고 한쪽은 각진, 마치 손으로 대충 그린 말풍선 같은 윤곽이 나온다. 사이트의 모든 카드·버튼·안내창이 이 값을 기본으로 쓰고, 크기에 따라 숫자만 비율로 조절한다. 이미지 파일이 하나도 없기 때문에 로딩 비용은 사실상 0에 가깝다.
카드마다 다른 각도로 눕히기
허브 화면의 게임 카드 6장은 전부 살짝 기울어져 있다. 홀수 번째 카드는 시계 방향으로, 짝수 번째 카드는 반시계 방향으로 0.7도씩 돌아간다.
`
.card:nth-child(odd){transform:rotate(.7deg)}
.card:nth-child(even){transform:rotate(-.7deg)}
`
0.7도는 눈으로 재기엔 너무 작은 각도지만, 카드가 나란히 있을 때는 "누가 아무렇게나 늘어놓은 낙서 카드 뭉치" 같은 인상을 준다. 버튼을 누르면 카드가 오른쪽 아래로 2px 이동하면서 그림자가 얇아지는데, 종이 카드가 눌려서 바닥에 붙는 느낌을 흉내 낸 것이다. 그림자에는 블러를 전혀 쓰지 않는다. box-shadow: 4px 4px 0 rgba(58,50,38,.2)처럼 흐림 반경을 0으로 두면 인쇄물을 오려 붙인 듯한 딱딱한 그림자가 나오는데, 이 쪽이 손그림 톤과 더 잘 어울렸다.
흔들림에도 규칙이 필요하다
버튼과 카드는 정적인 CSS로 충분하지만, 캔버스로 그리는 실제 게임 그래픽(두들타워의 블록 테두리, 결과 카드의 낙서 선 등)은 매 프레임 다시 그려진다. 여기서 실수를 한 번 했다. 처음에는 테두리를 그릴 때마다 Math.random()으로 점을 살짝 흔들었는데, 화면이 정지해 있어야 할 때도 테두리가 미세하게 계속 떨려서 "손그림"이 아니라 "노이즈"처럼 보였다.
해결책은 흔들림의 씨앗(seed)을 매번 새로 뽑지 않고, 그릴 대상의 위치에서 고정된 값을 뽑아내는 것이었다. 두들타워 코드에는 mulberry(Math.round(cx*13+cy*7+i))처럼, 원의 중심 좌표와 인덱스를 조합해 씨앗을 만드는 부분이 있다. 같은 위치에 있는 도형은 다시 그려도 항상 같은 방식으로 흔들리기 때문에, 화면이 멈춰 있으면 테두리도 멈춰 있다. 대신 도형마다 씨앗이 다르니 모든 원이 조금씩 다르게 삐뚤어진다. 무작위이지만 안정적인, 손이 떨리면서도 매번 같은 그림을 그린 사람의 손 같은 결과가 나온다.
폰트는 새로 만들지 않았다
손글씨 폰트는 'Segoe Print','Chalkboard SE','Comic Sans MS',cursive 순서로 지정한다. 커스텀 폰트를 웹에 올리지 않고 운영체제에 이미 깔린 손글씨 계열 폰트를 순서대로 시도하게 한 것이다. 윈도우에서는 Segoe Print가, macOS에서는 Chalkboard SE가 잡히고, 둘 다 없으면 Comic Sans MS나 브라우저 기본 필기체로 떨어진다. 정직하게 말하면 이 방식의 단점은 기기마다 폰트가 살짝 다르게 보인다는 것이다. 대신 폰트 파일을 내려받는 시간이 없고 라이선스 문제도 없다. 두들게임즈처럼 가벼운 게 정체성인 사이트에는 이 쪽이 더 맞는 선택이라고 판단했다.
배경의 옅은 파란 줄무늬(repeating-linear-gradient(transparent 0 33px, rgba(92,158,220,.12) 33px 34px))도 같은 맥락이다. 이미지 대신 CSS 그라디언트로 공책 줄을 흉내 냈고, 33px 간격은 실제 노트 줄 간격을 화면에서 어림잡아 맞춘 값이다. 결국 두들게임즈의 손그림 느낌은 그림 실력이 아니라, "완벽하게 하지 않기로 한 지점"을 코드로 정확하게 고정한 결과에 가깝다.
여섯 개 게임, 하나의 위젯이 같은 문법을 쓰는 이유
로그인 버튼, 랭킹 모달, 하트 잔액 칩처럼 여섯 개 게임 화면 구석에 공통으로 떠 있는 UI는 게임 코드와 별개인 공용 스크립트 파일 하나가 모든 게임 페이지에 주입한다. 이 공용 위젯도 카드·버튼과 똑같은 문법을 그대로 재사용한다. 예를 들어 하트 잔액 칩의 테두리 반지름은 카드에 쓰는 값과 대칭만 다를 뿐 같은 '어긋난 모서리' 패턴이고, 회전값도 다른 버튼들과 같은 범위(±2도 안팎) 안에 있다. 그림자 역시 흐림 반경 0의 딱딱한 그림자를 그대로 쓴다. 게임 여섯 개가 서로 다른 저장소에서 독립적으로 빌드되는데도 로그인 모달이나 하트 표시가 이질감 없이 붙는 이유는, 이 숫자들을 게임마다 새로 정하지 않고 공용 스크립트 하나에 박아두고 모든 화면이 그 값을 그대로 가져다 쓰기 때문이다. 디자인 시스템이라고 부를 만한 별도 문서는 없지만, 이 공용 스크립트의 CSS 문자열 자체가 사실상 두들게임즈의 살아 있는 스타일 가이드 역할을 하는 셈이다.
마스코트만 예외인 이유
카드나 배경과 달리 얼굴이 있는 마스코트 캐릭터 몇 개는 이모지와 CSS 도형을 섞어서 만든다. 표정이 있는 캐릭터를 매번 좌표 기반 낙서 선으로 그리려면 계산량이 훨씬 늘어나는 데다, 이모지 자체가 이미 손그림보다 더 즉각적으로 감정을 전달하기 때문에 굳이 같은 규칙을 적용하지 않았다. 완벽하게 통일된 규칙보다 "어디까지 자동화하고 어디는 그대로 둘지"를 구분한 판단이 더 중요했다는 뜻이다.