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

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

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

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

2D 게임 틀에 three.js 를 끼워 넣기

2026년 10월 30일 · 기술

지난 글에서 three.js 를 고르고 사이트에 넣은 과정을 적었다. 이번에는 그 라이브러리를 실제 게임에 붙이면서 겪은 일을 적는다. 첫 3D 게임은 상자 층을 쌓는 타워크레인이고, 이어서 모델 파일을 쓰는 러시 3D 를 만들었다.

이 사이트의 게임은 공통 도구 묶음(GameKit)으로 만든다. 캔버스를 만들고 화면 크기에 맞추는 일, 게임 루프, 일시정지, 점수 표시 같은 것을 게임마다 새로 짜지 않고 이 도구가 맡는다. three.js 도 이 틀 안에서 돌아가게 하는 것이 목표였다. 엔진 대신 라이브러리를 고른 이유가 바로 이것이었다.

첫 번째로 걸린 것은 캔버스였다. 캔버스는 getContext('2d') 로 2D 그리기 도구를 얻거나, getContext('webgl2') 로 WebGL 그리기 도구를 얻는다. 그런데 한 캔버스에서 한 번 2D 도구를 얻으면 같은 캔버스에서 WebGL 을 쓸 수 없다. 공통 도구는 캔버스를 만들 때 바로 2D 도구를 얻어 두고 있었다. 그래서 캔버스를 만들 때 WebGL 용이라고 알려 주는 선택지를 추가하고, 이때는 2D 도구를 얻지 않고 three.js 의 렌더러가 캔버스를 쓰게 했다.

const { cv, px, view } = k.canvas({ w: 360, h: 600, bleed: true, gl: true }); // no 2D context
const renderer = k.gl(new THREE.WebGLRenderer({ canvas: cv, antialias: true }));
renderer.setPixelRatio(px);
view.onResize = () => {
  renderer.setSize(view.w, view.h, false);
  camera.aspect = view.w / view.h;
  camera.updateProjectionMatrix();
};

게임 루프는 바꿀 것이 거의 없었다. three.js 는 그리기만 하므로 공통 루프의 그리기 차례에서 renderer.render(scene, camera) 를 한 번 부르면 된다. 일시정지, 게임 시간, 상단 점수, 결과 창은 2D 게임과 똑같이 동작한다. 오늘의 도전처럼 모두가 같은 판을 받아야 하는 곳에서는 three.js 에 들어 있는 난수 함수를 쓰지 않고 사이트가 주는 시드 난수를 쓴다.

다시 하기를 거듭하면 판이 사라졌다

아이패드와 아이폰의 사파리에서 다시 하기를 여러 번 누르면 예전 판의 그림이 사라지는 일이 생겼다. 사파리는 한 페이지가 동시에 열어 둘 수 있는 WebGL 컨텍스트 수가 적다. 판을 새로 시작할 때마다 새 캔버스와 새 컨텍스트를 만들고 예전 것을 놓지 않으니, 한도에 닿으면 브라우저가 오래된 컨텍스트부터 잃게 만들어 그 판의 그림이 사라진 것이다.

그래서 다시 하기와 게임 닫기 때 공통 도구가 WebGL 컨텍스트를 직접 놓게 했다. 렌더러를 k.gl(renderer) 로 알려 두면 공통 도구가 닫을 때 dispose() 까지 대신 부른다. three.js 는 도형, 재질, 텍스처를 그래픽 메모리에 올려 두는데, 이것은 자바스크립트의 쓰레기 수거로 저절로 치워지지 않는다. 판 도중에 버리는 도형이나 재질은 게임이 직접 dispose() 한다. 놓치기 쉬워서 확인 방법도 만들었다. ?debug=fps 를 켜면 둘째 줄에 그래픽 메모리에 올라간 도형과 텍스처 수가 나온다. 판을 거듭할수록 이 숫자가 늘기만 하면 어딘가 dispose 를 빠뜨린 것이다.

휴대폰은 삼각형보다 드로우콜에서 먼저 느려진다

3D 장면이 얼마나 무거운지는 보통 삼각형 수로 말한다. 그런데 휴대폰에서는 삼각형보다 드로우콜이 먼저 문제가 된다. 드로우콜은 그래픽 칩에 "이것을 그려라"라고 한 번 시키는 일이다. 재질이나 도형이 다르면 따로 시켜야 해서, 작은 물체가 많으면 삼각형이 적어도 드로우콜이 늘어난다. 그래서 ?debug=fps 둘째 줄에 삼각형 수와 함께 드로우콜 수도 띄웠다.

타워크레인에서 숫자를 보니 60층을 쌓은 끝 장면이 506 드로우콜이었다. 층 하나를 이루는 상자가 면 묶음 6개로 나뉘어 있어서 층마다 여러 번 그리고 있었다. 이 면 묶음을 옆면과 윗아랫면 2개로 합치자 같은 장면이 186 으로 줄었다. 단색 부분은 여러 색을 작은 그림 한 장에 모은 공용 팔레트 텍스처를 써서 재질 하나로 묶었고, 러시 3D 의 동전이나 길 블록처럼 같은 모양이 여러 번 나오는 것은 InstancedMesh 하나로 한 번에 그린다.

이렇게 맞춘 뒤 실제 기기에서 쟀다. 타워크레인의 60층 끝 장면은 실제 아이패드에서 120fps(캔버스 2048×2468, 184 드로우콜), 실제 아이폰에서 60fps(캔버스 640×1106, 195 드로우콜)였다. 앞서 캔버스 2D 에서 아이패드가 11fps 를 냈던 일을 떠올리면 안심이 되는 숫자였다.

다음 글에서는 모델 파일을 불러오면서 겪은 일을 적는다. 텍스처가 하얗게 나온 일과, 다시 묶은 모델 파일이 캐시에 남는 문제다.

참고한 문서

이 글과 관련된 게임

타워크레인타워크레인이 매달고 오가는 빌딩 층을 아래층 위에 딱 맞춰 떨어뜨리세요. 삐져나온 부분은 잘려 나가요. 하늘 끝까지 몇 층 쌓을 수 있을까요? 해 보기 러시 3D하늘에 떠 있는 섬들을 잇는 세 줄 블록 길을 달려요. 줄을 바꾸고, 뛰고, 숙여서 장애물과 끊긴 길을 피하세요. 얼마나 멀리 갈 수 있을까요? 해 보기

다른 글

홈 화면에 추가하기

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

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