HTML6 Games설치없는 무료 웹 게임 / devlog

설치 없이, 지금 바로 플레이

브라우저에서 즐기는 무료 미니게임 모음. 로그인하면 기록이 계정에 저장되어 어느 기기에서든 이어서 볼 수 있어요.

게임 목록
게임 목록
모든 개발 이야기

3D 라이브러리를 고르고 빌드 없는 사이트에 넣기까지

2026년 10월 25일 · 기술

이 사이트의 게임은 대부분 브라우저의 <canvas> 에 직접 그리는 2D 게임이다. 3D 게임을 넣을 계획이 생겨서, 어떤 엔진이나 라이브러리를 쓸지 먼저 조사했다. 처음 질문은 "웹 게임 엔진에는 무엇이 있고 우리 사이트에 쓸 수 있는가"였다.

조사한 후보는 이렇다. Phaser 는 물리, 타일맵, 소리까지 들어 있는 2D 종합 엔진이고, PixiJS 는 그리기만 맡는 2D 라이브러리다. KAPLAY, LittleJS, Excalibur 는 가벼운 2D 엔진이다. 3D 에는 Three.js 와 Babylon.js 가 있다. Three.js 는 그리기만 하고 물리는 따로 붙여야 하고, Babylon.js 는 물리, 충돌, 장면 관리까지 한 묶음으로 들어 있다. Godot, Unity 같은 편집기형 엔진은 웹으로 내보내는 방식이다. 이 중 Godot 는 내보낸 파일이 수십 MB(wasm)이고 별도의 제작과 빌드 단계가 생겨서, 소스 파일이 그대로 사이트가 되는 이 사이트와 맞지 않았다. Unity 는 수년 전에 써 본 적이 있는데 너무 무거웠고 웹으로의 변환도 완전하지 않았다. 지금은 보완되었는지 모르겠지만, 그때의 기억으로 우선 제외했다.

물리 엔진도 따로 조사했다. Phaser 는 가벼운 Arcade Physics 와 안에 함께 들어 있는 Matter.js, 따로 받는 Box2D 중에서 고른다. 그리고 Phaser 는 2D 전용이라 3D 장면, 카메라, 조명이 없다. 3D 를 넣을 계획이 있으니, Phaser 를 고르면 3D 에는 어차피 Three.js 같은 것을 또 붙여야 했다.

가장 궁금했던 것은 용량이었다. 3D 계획이 있는 만큼 Phaser 와 Three.js 만 최신판 파일을 직접 받아 크기를 쟀다(Phaser 4.2.1, Three.js r186).

비교 기준은 이 사이트다. 지금 게임 하나의 코드는 평균 약 12KB 이고 44개를 합쳐 약 550KB 다. 생성 그림을 쓰는 게임은 그림 합계를 300KB 아래로 맞춘다. Phaser 하나만 받아도 지금 게임 44개 코드 합계의 절반이 넘는다. Three.js 는 그림 예산인 300KB 보다 작다. 게다가 한 번 받으면 브라우저 캐시에 남아서 같은 라이브러리를 쓰는 두 번째 게임부터는 거의 무료다.

그런데 이 조사에서 가장 오래 고민한 질문은 용량이 아니었다. 지금까지 브라우저 문제를 많이 겪었는데, 엔진을 쓰면 그런 문제가 없지 않을까, 지금 쓰는 캔버스 기반 도구로 문제가 없을까 하는 것이었다. 겪은 문제를 하나씩 나눠 봤다.

화면 쪽 문제는 엔진을 바꿔도 같다. 판이 화면 밖으로 넘치는 것, 상단 표시가 줄바꿈되는 것, 아이폰의 글자 선택이나 길게 누르기 메뉴, 결과 카드가 잘리는 것은 모두 엔진이 그리는 영역 바깥, 즉 사이트 화면의 문제였다. 판정이 그림과 어긋나는 것이나 풀 수 없는 퍼즐은 게임 설계 문제다. 숨겨진 창에서 애니메이션 프레임이 멈추는 것은 Phaser 도 똑같다.

엔진을 쓰면 오히려 새로 생길 문제도 있었다. 엔진마다 자기 루프, 탭 전환 시 멈춤 처리, 화면 크기 맞추기, 키 입력을 따로 가지고 있어서, 사이트가 가진 일시정지와 화면 자리 정하기와 누가 주인인지 부딪힌다. 모두가 같은 판을 받는 오늘의 도전도 엔진 안의 난수와 사이트의 시드를 맞춰야 한다. 그래서 2D 는 지금의 캔버스 도구를 유지하고, 3D 만 Three.js 를 쓰기로 했다. Three.js 는 그리기만 하기 때문에 사이트의 루프나 일시정지와 부딪히지 않는다. 마음에 걸렸던 아이패드의 캔버스 2D 성능은 앞 글에서 적은 것처럼 원인을 찾아 고쳤다.

먼저 간단한 3D 게임을 Three.js 로 만들어 보기로 했고, 그 첫 게임이 타워크레인이다.

설치하기

Three.js 공식 설치 문서는 npm 으로 설치하고 빌드 도구를 쓰는 방식을 대부분의 사용자에게 권장한다. 빌드 없이 쓰려면 CDN 주소를 알려 주는 import map 을 HTML 에 넣어야 하는데, 문서도 CDN 이 잠시 멈추면 내 사이트도 멈추고, 서로 다른 출처의 파일을 섞으면 앱이 깨질 수 있다고 경고한다. 이 사이트는 빌드가 없고, 보안 설정(CSP)이 인라인 스크립트를 막아 import map 을 못 쓰고, 외부 CDN 에 의존하고 싶지도 않았다.

그래서 라이브러리 파일을 저장소 안에 직접 두기로 했다. 가져오는 과정은 스크립트 하나로 만들었다.

node tools/vendor-three.mjs 0.186.1

스크립트는 npm 에서 지정한 버전의 패키지를 받아 풀고, 그 안의 three.module.js 와 three.core.js 를 esbuild 로 줄여 games/lib/vendor/three/ 에 둔다. 두 파일은 같은 폴더에 있어야 한다. three.module.js 가 ./three.core.js 를 불러오기 때문이다. 라이선스 주석과 LICENSE 파일도 함께 둔다. 줄인 결과는 두 파일 합쳐 약 750KB 이고 gzip 으로 전송하면 약 195KB 다. 3D 게임을 열 때만 받는다.

모델 파일(glb)을 불러오는 부품(GLTFLoader 등)은 패키지의 examples/jsm 에 있고, 이 파일들은 'three' 라는 이름으로 라이브러리를 불러온다. 브라우저는 import map 없이는 이 이름이 어디를 가리키는지 모른다. 그래서 스크립트가 부품 파일을 가져올 때 이 부분을 three.module.js 로 가는 상대 경로로 바꿔 둔다.

// the browser can't resolve the bare 'three' specifier without an import map,
// so point each addon at the local copy with a relative path
code.replace(/from 'three';/g, `from '${'../'.repeat(depth)}three.module.js';`);

스크립트는 부품 파일에서 이 부분을 찾지 못하면 멈추고, Three.js 의 구조가 바뀌었는지 확인하라고 알려 준다. 버전을 올릴 때 이 치환이 조용히 어긋나는 것을 막기 위해서다.

설치에서 가장 크게 걸린 것이 이 import 문제였다. 코드에 from 'three' 처럼 경로 없이 이름만 적어 불러오는 방식을 bare import 라고 부른다. npm 이나 빌드 도구는 이 이름을 node_modules 폴더에서 찾아 주지만, 브라우저에는 그런 폴더가 없다. 브라우저는 /, ./, ../ 로 시작하는 주소나 완전한 주소만 이해하고, 이름만 적혀 있으면 어디서 가져올지 알 수 없다며 오류를 낸다. Three.js 의 본체는 서로를 ./three.core.js 처럼 상대 경로로 불러서 문제가 없는데, 모델 부품은 본체를 이름으로 불러서 이 오류에 걸린다.

해결 방법은 세 가지가 있었다. 공식 문서가 알려 주는 import map 은 HTML 에 <script type="importmap"> 으로 이름과 주소를 짝지어 두는 방식인데, 이것은 인라인 스크립트라서 이 사이트가 막아 둔 보안 설정(CSP)과 부딪힌다. 빌드 도구로 묶으면 이름을 알아서 풀어 주지만 사이트에 빌드 단계를 만들어야 한다. 마지막 방법이 부품 파일을 가져올 때 그 한 줄을 직접 고쳐 쓰는 것이었다. 이 방법은 빌드 단계도, 보안 설정 변경도 필요 없다. 대신 Three.js 를 올릴 때마다 치환이 맞는지 확인해야 해서 앞의 스크립트가 못 찾으면 멈추도록 했다.

게임에서는 라이브러리를 직접 import 하지 않는다. 게임 파일은 일반 스크립트라서 그럴 수 없고, 대신 게임을 등록할 때 쓸 라이브러리 이름만 적는다. 게임을 열 때 사이트가 import() 로 라이브러리를 받아 두고 게임 코드에 api.lib.three 로 넘겨 준다.

libs: ['three'],   // the portal import()s the library before mount
mount(stage, api) {
  const T = api.lib.three;
  const renderer = new T.WebGLRenderer({ canvas });
  // ...
}

다음 글에서는 이 라이브러리를 실제 게임에 적용한 내용을 적는다. 렌더러를 게임 루프에 연결하는 방법, 게임을 닫을 때 정리하는 일, 모델 파일 불러오기에서 겪은 일을 이어서 쓴다.

참고한 문서

이 글과 관련된 게임

타워크레인타워크레인이 매달고 오가는 빌딩 층을 아래층 위에 딱 맞춰 떨어뜨리세요. 삐져나온 부분은 잘려 나가요. 하늘 끝까지 몇 층 쌓을 수 있을까요? 해 보기

다른 글

홈 화면에 추가하기

  1. 화면 아래(아이패드는 위)의 공유 버튼을 누르세요. 네모에서 화살표가 위로 나온 모양이에요.
  2. 목록을 내려 "홈 화면에 추가"를 누르세요.
  3. 오른쪽 위 "추가"를 누르면 홈 화면에 HTML6 Games 아이콘이 생겨요.

Safari 에서만 추가할 수 있어요. 홈 화면에서 열면 주소창 없이 앱처럼 보이고, 인터넷이 끊겨도 열립니다.