Skip to content

Instantly share code, notes, and snippets.

@lifthrasiir
Last active Sep 23, 2021
Embed
What would you like to do?
Roadroller Postmortem (Korean translation, original: https://medium.com/js13kgames/roadroller-postmortem-f24691b0d54b)

로드롤러 개발 후기

이 글은 js13kGames 블로그에 올린 Roadroller Postmortem 글의 한국어 번역입니다.

올해의 js13kGames는 저에게 특별한 이벤트가 되었습니다만, 그렇다고 해서 딱히 뭐 수상한 건 아니고요(글을 쓰는 시점에서는 아직 수상자가 발표되지조차 않았습니다). 사실 지금까지 js13kGames에 한 번도 게임을 낸 적이 없는데 어쩌다 보니 이렇게 js13kGames 블로그에 글을 쓰게 되었습니다. 게임은 만들지 않았지만 전 이번에는 Roadroller라는 도구를 만들었고, 여러 사람들께서 써 주셔서 많은 감사의 말씀을 받았습니다. Roadroller가 있기까지는 수많은 우여곡절이 있었고 이 쓸데 없이 긴 글에서 그 이야기를 풀어 보려 합니다.

Roadroller 온라인 데모 스크린샷

배경

본론에 들어가기 앞서, 두 종류의 취미 프로그래밍에 대해서 설명할 필요가 있겠습니다. 하나는 코드 골프이고(숏코딩이라고도 부릅니다) 다른 하나는 데모신입니다. 코드 골프에서는 주어진 프로그램을 최대한 적은 타수로 구현해야 하는데 마치 진짜 골프의 홀과 비슷합니다. 데모신에서는 데모라고 부르는 다른 의존성을 가지지 않은 시청각적 프로그램을 만들게 되는데 종종 크기 제한이 있는 경우가 있습니다. 어느 쪽이든 크기를 줄이기 위해 진짜 뭐든 할 수 있다는 점에서는 유사하지만, 데모가 뭘 만들지 정하는 데 있어서는 훨씬 자유도가 큽니다. 또한 코드 골프와는 달리 데모신은 게임 프로그래밍에 상당한 영향을 주었기에, js13kGames 같은 크기 제한이 있는 게임잼들이 데모신과 멀게나마 연관이 있는 것 또한 놀라울 일이 아닐 것입니다.

둘 다 십수년간 관심을 가지고 있긴 했지만 저는 굳이 따지면 코드 골프를 하는 사람이었습니다. codegolf.com이나 아나키 골프 같은 웹사이트에서 특히 활발하게 활동했고요, 어떤 코드는 충분히 꼬여 있어서 IOCCC에서 수상하기까지 했습니다. 근년간에는 자바스크립트 쪽을 많이 팠고, 에릭 쇰브릭(Erik Sombroek) 씨와 힘을 합쳐 AquaPop1K라는, 자바스크립트 1024바이트로 구현한 《》 게임으로 제 1회 JS1024 골프 대회에서 수상하기도 했습니다.

AquaPop1K 스크린샷

여기까지 말하고 보면 이미 js13kGames에 뭐라도 하나 냈을 것 같은 느낌이 드는데, 지금껏 했던 것들 대부분에 공통점이 있다면 제가 딱히 뭘 만들지 선택하지 않았다는 게 있습니다. 예를 들어서 AquaPop1K의 첫 버전은 제가 만든 게 아니라, 처음 보고 나서 재밌는 게임인데 딱히 크기를 최적화하지 않은 걸 보고 팀에 참여한 것이었습니다. 당연히 《팡》 같은 게임들이야 알고 있었지만 제가 그 게임을 보지 않았더라면 직접 만들 생각은 꿈에도 하지 못했을 겁니다. 제가 딱히 데모신에서 뭘 해 보려고 해 보지 않은 이유기도 한데, 오랫동안 이걸 좀 바꿔 보려고 했었습니다. 뭔가 이미 있는 코드나 아이디어를 바탕으로 하지 않고 완전히 새로운 걸 스스로 만들어 보려고 했던 건데, 정작 뭘 만들어야 할진 모르는 채로 시간이 흘렀습니다.

시간이 지나 2021년이 되었고 저는 회사에서 번아웃으로 머리를 싸매고 있었습니다. 회사에서도 그렇지만 집에서조차 아무 것도 집중할 수 없었기에, 좀 요양을 하려고 8월에 2주간 휴가를 신청했습니다. (스포일러: 딱히 도움이 되지 않았습니다... 혹시 관심이 있으시면 제 이력서를 참고해 주세요 :-) 그러고 나서 보니 올해 JS1024는 완전 놓쳐 버렸지만, 휴가 기간이 js13kGames 2021의 시작과 겹쳐 있는 걸 깨닫고 뭔가 해 보려고 했지요. 지난 7년간 게임 업계에 있긴 했습니다만 제가 주로 한 일은 다른 개발자를 위한 도구와 엔진을 만드는 것이었고 만약 만들게 된다면 제가 스스로 만든 사실상 첫 게임이 되는 것이었습니다. 그러고서 7월 말 즈음에 다가오는 이벤트를 대비하여 준비를 좀 하기로 했을 때 뭔가를 깨달았습니다.

압축

데모가 꼭 크기 제한이 있는 건 아니지만, 크기 제한이 있는 데모가 좀 더 인정받는 면은 있습니다. 흔한 크기 제한으로는 64 KB, 4 KB 및 1 KB가 있는데, 컴퓨팅 파워가 폭발하고 데모의 시청각적 기준이 수직 상승하면서 데모를 특수한 도구로 압축하는 게 보편화되었습니다. fr-08: .the .product은 제가 처음으로 본 데모 중 하나이자 어쩌면 역사상 가장 영향력 있는 데모일지도 모르겠는데, 이게 내부적으로 UPX를 고쳐서 썼다고 알려져 있습니다. .kkrieger가 나올 즈음에는 완전히 새로운 도구를 만들어 쓰고 있었는데 이게 후에 kkrunchy가 되었고, 이를 시작으로 다양한 도구가 나오기 시작했는데 대표적으로 CrinklerSquishy가 있습니다.

거기에 비교하면 자바스크립트 패커는 원시 수준이나 다름 없었습니다. 제가 아는 바 그간 최선의 방법은 이른바 PNG 부트스트랩이라고도 부르는 PNG 기반의 압축 방법이었는데(JsExe 등), HTML 파일로도 읽을 수 있는 PNG 파일을 만들어서, 열면 자기 자신을 HTML 캔버스에 읽어 들인 뒤에 코드를 재구성해서 eval을 호출하는 식이었습니다. AquaPop1K에서 사용했던 RegPack이라는 도구도 있습니다만, 이건 느린데다가 PNG 부트스트랩에 비해서 압축률도 떨어지기 때문에 JS1024 환경에서 사용할 수 있었다면 그냥 PNG 부트스트랩을 썼을 겁니다. js13kGames의 경우 13 KB 제한은 제출한 ZIP 파일의 크기로 계산되기 때문에, PNG 부트스트랩을 써 봤자 얻을 수 있는 이득이 거의 없어서 그냥 ZIP 파일이 최선이었습니다.

그런데 생각해 보면, PNG와 ZIP 모두 DEFLATE라 하는 30년 묵은 알고리즘에 기반해 있기 때문에 LZMA, ZstandardBrotli 같은 현대적인 알고리즘과는 비교할 수조차 없습니다. 이미 있는 DEFLATE 스트림을 좀 더 작게 만드는 비싼 알고리즘들도 존재하지만 DEFLATE의 제한 자체를 넘어설 수는 없습니다. PNG 부트스트랩이 그간 여전히 최선이었던 가장 큰 이유는 압축 해제에 소요되는 코드 크기였는데요, 데모 등에서 사용하려면 압축이 자동으로 해제되어야 하기에 압축된 파일에 압축 해제 코드가 반드시 포함되어야 합니다. 게다가 그 압축 해제 코드는 이진 기계어가 아니라 텍스트로 된 자바스크립트 코드여야 하니 문제가 크죠. 그에 비하면 PNG 부트스트랩은 브라우저가 이미 구현한 알고리즘을 그냥 갖다 쓸 수 있기 때문에 추가 비용이 고작 200바이트 정도에 불과합니다. DEFLATE를 대체할 가능성이 그나마 존재하는 알고리즘으로는 WOFF2 글꼴 포맷에서 사용하는 Brotli가 있지만, 꽤 고민해 봤는데도 WOFF2에 임의의 데이터를 넣거나 추출할 방법을 아직 찾지 못했습니다. 따라서 현상을 개선하려면 압축 해제에 소모되는 코드 크기를 커버해 줄 정도로 압축률이 높아야만 합니다.

golf.horse 스크린샷

그리고 여기서 코드 골프가 유용한 지점이 등장합니다. golf.horse라는 웹사이트가 있는데, 주어진 텍스트를 출력하는 가장 작은 프로그램을 겨루는 곳입니다. golf.horse를 알고 나서 꽤나 열심히 달려서 모든 과제에 대해 가장 작은 프로그램을 만드는 데 성공했었는데(가명으로), 큰 과제에 대해서는 앞서 언급한 Crinkler와 유사한 알고리즘을 사용했었습니다. 그리고 그 경험으로부터 해당 알고리즘을 구현하는 데 필요한 코드가 대략 600바이트 정도 밖에 되지 않는다는 것도 알고 있었습니다. 혹시나 싶어서 시험삼아 js13kGames 2020에 나온 몇몇 게임에다가 압축을 돌려 봤는데, 놀랍게도 일부 게임에 대해서 무려 1킬로바이트 작은 결과가 나오는 것이었습니다.

바로 이게 가능성이 있다는 걸 깨달았지만, 실제로 js13kGames에 쓸모가 있는지는 다소 의문이 들었습니다. 어지간한 코드 골프 대회랑 비교하면 13 KB는 사치 수준이라, 기본적으로 미리 잘 계획을 세우면 원하는 건 아무 거나 만들 수 있습니다. 달리 말하면 좀 품을 팔면 비교적 수월하게 1~2 KB 정도 줄이는 방법이 있을 수 있다는 얘기기도 한데, 그럼 일반적인 압축 프로그램이 크게 쓸모가 없겠죠. 확신이 없는 상태에서 7월 30일에 JS1024 디스코드에 초기 결과를 올려 보았는데, 반응이 아주 좋아서 진짜로 도구를 만들어 보기로 했습니다. 특히 격려를 해 주시고 초기 버전의 테스트와 소중한 피드백까지 주신 Frank Force 씨께 큰 빚을 졌습니다.

양파 깎기

8월 2일에 트위터에 프로젝트를 정식으로 발표했습니다. 그리고 프로토타입을 진짜 도구로 만드는 데까지 꼬박 2주가 걸렸습니다. 원래 의도는 js13kGames가 시작하기 직전에 압축 프로그램을 끝을 본 뒤 휴가 내내 게임을 만들려던 것이었기에 스케줄이 꽤 아슬아슬했습니다. 도구의 이름은 로드롤러(Roadroller)라 정했는데, npm에 해당 이름이 없었기에 선택한 것이었지만 모두가 죠죠 드립을 치더군요.

Roadroller의 첫 버전은 8월 13일에 공개되었습니다. 이 시점에서 《Edge Not Found》는 (가장 좋은 ZIP 재압축기를 사용했을 때 기준으로) 12385바이트에서 11234바이트까지 줄어든 상태였는데, Brotli로 같은 코드를 압축하면 11358바이트가 나오고 여기에는 압축 해제 코드가 아예 포함되지 않았다는 걸 생각하면 이미 굉장한 성공이었습니다. 아, 참고로 저는 테스트 과정에서 《Edge Not Found》를 샘플로 주로 사용했는데요, 그 자체로 재밌는 게임이기도 하고 일반적인 js13kGames 게임의 모든 요소를 담고 있기 때문이기도 했습니다.


2019년 테마는 BACK이었고 2021년 테마는 SPACE라니, 당연히 키보드 말하는 거겠지요?

첫 버전을 내고 나서 js13kGames에 낼 게임을 만들려고 했는데, 너무 아이디어가 많아서 뭘 만들지 정할 수 없었습니다. 개중 하나로는 위에 나온 스페이스바 모양의 캐릭터를 가지고 쿼터뷰 플랫포머를 만들어 보려던 게 있었는데, 꽤 그럴듯해 보였지만 세부 메커닉을 만들려다 보니 다양한 문제가 있었습니다. 그 와중에 Roadroller 작업을 계속 하다 보니 게임 작업하는 것보다 Roadroller 작업하는 게 훨씬 생산성이 높다는 걸 알게 되었고, 그리하여 게임은 때려 치고 휴가 내내 Roadroller 버전을 4개 더 내게 되었습니다. 매 버전마다 수십에서 수백 바이트씩 개선이 있었는데, 주로 압축에 큰 영향을 주는 새 파라미터를 찾아 냈기 때문입니다. 더는 어렵다고 생각했는데 새 파라미터를 찾아 낼 때마다 어떻게 이게 가능하지 싶더군요.


Roadroller 버전들과 기존 압축 프로그램을 비교하는 벤치마크. 최적화 과정은 랜덤하기 때문에 크기가 살짝 왔다 갔다 하는 건 예상 범위 안입니다.

Roadroller를 몇몇 아는 사람만 쓰는 건 공평하지 않다고 생각해서 여기 저기에 Roadroller를 홍보하고 다녔는데, 초기 반응은 미적지근했지만 8월 24일에 js13kGames 슬랙에다가 2.0.0의 추정 벤치마크를 올렸더니 갑자기 모든 사람들이 Roadroller에 대해 묻더군요. 이해가 가는 것이, 이 시점에서 《Edge Not Found》는 10600바이트 정도까지 압축되었기에 Roadroller를 쓰면 사실상 모든 게임이 1~2킬로바이트 정도를 공짜로 얻을 수 있었습니다. 농담이긴 하지만 js13kGames를 js15kGames로 만들었다고 사과까지 했다니까요.

감사

이제 게임 제출 기간은 끝났는데 제출된 게임들을 보니 대단하네요. Roadroller는 2.1.0까지 나왔고 감사스럽게도 35개 게임에서 쓰고 있다고 알고 있습니다(최선을 다해서 확인했습니다만 누락이 있다면 미리 죄송하다는 말씀을...):

앞서 번아웃에 시달리고 있었다고 언급했는데요, 지난 몇 달간 어떤 취미 프로젝트에서도 진전을 볼 수 없어서 농담이 아니라 너무 번아웃된 나머지 아무 것도 할 에너지가 남아 있지 않다고 생각했었습니다. Roadroller의 성공은 좋은 반증이 되었습니다. 딱히 번아웃에서 회복하는 데 도움이 된 건 아니지만 적어도 자존감을 채우는 데는 도움이 되었거든요. 그러한 이유로 js13kGames에 관련되신 모든 분께 깊은 감사를 드리며, 위의 목록은 제 감사의 표시로 받아 들여 주시면 감사드리겠습니다.

미래

Roadroller가 강력한 도구로 드러나긴 했지만 여전히 갈 길이 멉니다. 꼭 구현하려고 했지만 아직 하지 못 한 것 중 하나로 다중 입력 지원이 있는데요, Roadroller는 정확히 한 개의 자바스크립트 코드를 입력으로 받지만 진짜 게임에는 HTML도 있고 CSS도 있고 이미지도 있지요. 이상적으로는 최적 압축을 위해 모든 걸 하나의 자바스크립트 코드로 만들어야 하는데, 이게 그다지 간단한 일이 아니라 나중에는 이걸 위한 가이드까지 써야 했습니다. 다중 입력이 지원되면 이런 작업이 대부분 사라질 겁니다. 그리고 Roadroller를 만들다 보니 많은 분들께서 Webpack이나 Rollup 같은 기존 번들러로 게임을 빌드한다는 걸 알게 되었는데, 다중 입력 지원이 되면 이런 도구와의 연동도 한결 쉬워질 것입니다.

그리고 뚱딴지 같은 소리로 들릴 수 있지만 어쩌면 압축을 더 할 수 있을 지도 모릅니다. Roadroller와 하이엔드 압축 알고리즘과 비교하면 이 점이 분명해지는데요, 하이엔드라 함은 아주 느리고 엄청난 양의 메모리를 잡아 먹지만 대신 압축률이 대단히 높은 것을 가리킵니다. cmix가 대표적인데, 최신 버전(19) 기준으로 《Edge Not Found》는 8554바이트라는 입이 떡 벌어질만한 크기까지 줄어 듭니다. 물론 압축 해제에만 1분이 넘게 걸리고 메모리를 12 GB씩 먹기 때문에 전혀 써 먹을 수 없긴 하지만, 얼마까지 줄일 수 있는지 대강 감이 오는 숫자지요. 앞서 Crinkler나 Squishy 같은 데모 패커를 언급했었는데, Roadroller가 Crinkler보다 조금 더 강력한 알고리즘을 쓴다고 생각합니다만 Squishy의 복잡한 모델과는 비교할 수 없습니다. Roadroller를 만들면서 압축 알고리즘에 대해 많은 걸 배웠는데 당연히 앞으로도 더 배우고 어쩌면 써 먹을 게 나올지 모르죠.

Roadroller와 같은 패커가 수동 크기 최적화를 대체할 수 없다는 점을 강조하며 이 글을 마치고 싶습니다. Q1K3으로 유명한 Dominic Szablewski 씨는 Roadroller 덕분에 두번째 레벨을 끼워 넣을 수 있었다고 언급하셨는데요, 달리 말하면 Roadroller 없이도 첫번째 레벨까지는 13 KB에 들어갔다는 얘기겠죠. Roadroller는 있는 게임에 더 많은 컨텐츠를 넣는 데는 도움이 될 수 있겠지만 자동으로 게임의 크기를 최적화해 주지는 않습니다. 그러니 Roadroller는 데드라인 직전에 도움이 되는 도구라고 생각하는 게 좋겠습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment