뒤로

거실 아케이드

브라우저에서 실제로 플레이할 수 있는 레트로 카트리지 아케이드. 콘솔, CRT 모니터, 컨트롤러가 놓인 3D 데스크를 돌려 보고 전원을 켜 카트리지 라이브러리로 들어간 뒤 Snake, Breakout, Blocks 중 하나를 고르세요. 설치도, 저장 파일도 없는 세 가지 오리지널 미니게임 — 오직 어느 오후 한나절과 한 판 더.

제작자: Theodore
라이브 체험
거실 아케이드라이브 체험

프로젝트 소개

Playroom은 브라우저에서 돌아가는 거실 아케이드입니다. 페이지를 열면 1989년 스타일의 게임 데스크 — CRT 모니터, 콘솔, 컨트롤러, 이미 끼워진 Snake 카트리지 — 가 나타나 드래그로 돌려 보며 살필 수 있습니다. 콘솔이나 POWER ON을 클릭하면 카메라가 화면으로 다가가 브라운관을 통과해 카트리지 세 개가 놓인 라이브러리에 내려앉습니다.

세 게임 모두 Playroom 오리지널입니다. Snake는 리듬에 맞춰 연속으로 먹는 게임, Breakout은 세 단계의 벽돌 배치와 파워업, Blocks는 착지 고스트와 일곱 조각 한 세트의 가방으로 쌓기를 예측 가능하게 만듭니다. 계정도, 업데이트도, 저장 파일도 없습니다 — 최고 기록은 브라우저에 남고, 음악과 효과음은 버튼 하나로 끕니다.

프로젝트 전체가 의존하는 것은 Three.js와 Vite뿐입니다. 백엔드도 환경 변수도 없고, 여섯 개의 GLB 하드웨어 모델과 Tripo 페이스 마크 데칼 한 장이 에셋 목록의 전부입니다. WebGL을 사용할 수 없을 때는 텍스트 메뉴가 이어받아 게임을 그대로 온전히 플레이할 수 있습니다.

체험 포인트

  1. 드래그하여 CRT 모니터, 콘솔, 컨트롤러, 카트리지가 놓인 거실 세트 전체를 돌려 본 뒤 콘솔이나 POWER ON을 클릭하세요. 카메라가 화면을 향해 다가가 CRT를 통과해 카트리지 라이브러리로 들어갑니다.
  2. 카트리지 세 개, 오리지널 게임 세 가지. Snake는 5초 안에 연속으로 먹어 5× 체인을 쌓고 황금 과일은 50점. Breakout은 세 단계의 벽돌 배치에 W(넓은 패들)와 3(트리플 볼) 파워업. Blocks는 일곱 조각 한 세트의 가방, 착지 고스트, 연속 제거 보너스.
  3. 데스크톱에서는 방향키 / WASD와 스페이스, P로 일시정지, ESC로 종료, 모바일에서는 터치 컨트롤. 최고 기록은 오직 내 브라우저 안에만 남습니다.
  4. 게임마다 고유한 칩튠 루프와 효과음이 있고, 메뉴에서는 CC BY 4.0 트랙 “Trip Of Your Smile”이 흐릅니다. WebGL을 사용할 수 없으면 텍스트 버튼으로 대체되어 그대로 플레이할 수 있습니다.

기술 분석

  • 3D 에셋 · 런타임에 다시 바인딩되는 여섯 개의 GLB 하드웨어

    CRT 모니터, 콘솔, 컨트롤러, 카트리지 세 개는 여섯 개의 GLB 파일로 제공됩니다(Three.js GLTFExporter로 내보낸, 각 128–384 삼각형의 로우폴리). GLTFLoader가 이들을 병렬로 불러오고 각 인스턴스는 딥 클론입니다. 내보내기 시 캡처용으로 적용된 중심 정렬 오프셋을 0으로 되돌리고, 머티리얼은 인스턴스마다 복제하며, 모든 메시가 그림자를 드리우고 받습니다. 카트리지의 라벨 판은 내보낸 로컬 오프셋으로 찾아 cartridge-label로 이름 붙이고 Canvas로 그린 커버 텍스처로 다시 매핑합니다. 픽셀 마스코트는 이름 붙은 노드 — mascot-body, 양팔, 안테나, Tripo 페이스 마크를 실은 로고 데칼 — 로 리깅하며, 평면 데칼이 선명하게 유지되도록 톤 매핑과 깊이 쓰기를 비활성화했습니다.

    에셋 레이어에는 교체 슬롯도 마련되어 있습니다. public/models/manifest.json의 controller 필드를 임의의 GLB로 가리키면 fitProp이 바운딩 박스를 기준으로 폭 2.54 단위로 스케일하고, 바닥에 맞춰 중앙 정렬한 뒤 원래 컨트롤러의 위치를 넘겨받습니다. 직접 만든(예를 들어 Tripo로 생성한) 컨트롤러를 코드 한 줄 건드리지 않고 끼워 넣을 수 있습니다.

  • 화면과 라벨 · UI로 쓰는 Canvas 텍스처

    씬은 외부 텍스처 맵을 하나도 불러오지 않습니다. CRT 화면은 1024×700 CanvasTexture로, 대기 화면(PLAYROOM SYSTEM / PLAYER ONE / PRESS START)을 2D 컨텍스트에 프레임마다 그려 map과 emissiveMap 양쪽에 써서 브라운관이 빛나게 합니다. 카트리지 커버는 각각 640×610 캔버스로, 카탈로그에 있는 해당 게임의 색상, 잉크색, 번호, 태그라인으로 픽셀 아트와 타이틀 레터링까지 즉석에서 그립니다.

    전원을 켜면 화면 라이프사이클 컨트롤러가 머티리얼을 데이터 스트림 텍스처로 바꾸고, 카메라는 화면 중심을 축으로 한 구면 궤도를 따라 브라운관 정면으로 돌아와 다가가다가 유리를 통과하는 순간 라이브러리로 컷 전환합니다. prefers-reduced-motion 환경에서는 이 움직임 전체를 생략하고 바로 전환합니다.

  • 인터랙션 · 포인터 제스처, 레이캐스트 피킹, 시네마틱 궤도

    데스크 뷰에서는 하나의 포인터 시퀀스가 궤도 드래그이자 클릭입니다. 제스처 컨트롤러가 누른 순간의 theta / phi를 기록하고, 임계값을 넘는 이동은 드래그로, 그 외는 클릭으로 처리합니다. 히트 판정은 Raycaster로 하며, 데스크에서는 세트 전체가 전원 버튼 역할을 하고 라이브러리에서는 부모 체인을 따라 올라가 gameId를 가진 노드를 찾습니다. 손을 떼어도 궤도는 그 자리에서 멈추지 않고, 시네마틱 컨트롤러가 수동 오프셋을 느린 자동 흔들림으로 되돌려 섞습니다.

    카트리지에 호버하거나 포커스하면 0.24만큼 떠오르고 살짝 롤하며 앞으로 기울면서 픽셀 마스코트가 그 게임 전용 포즈로 바뀌고, 각 카트리지는 서로 다른 사인 위상으로 위아래로 흔들립니다. 렌더 루프의 모든 보간은 프레임 속도와 무관한 이징 1 − e^(−5·dt)를 쓰기 때문에 느린 프레임에서도 튀지 않습니다.

  • 게임과 사운드 · 독립 2D 세션과 합성 사운드

    세 게임은 순수 로직 모듈(create / step / input)이고, 세션 레이어가 이를 독립된 2D 캔버스에 그립니다 — Snake와 Breakout은 480×480, Blocks는 300×540. requestAnimationFrame은 실행 중일 때만 예약되므로 일시정지는 말 그대로 프레임을 멈추고, 스텝당 250ms 상한이 탭 전환 뒤의 관통을 막습니다. 게임 이벤트(먹기, 벽돌, 줄, 파워업, 레벨)는 파티클 효과 레이어로 흘러가고 우선순위에 따라 하나의 사운드 큐로 합쳐집니다.

    모든 사운드는 Web Audio 오실레이터로 실시간 합성됩니다. 게임마다 16스텝 칩튠 멜로디(82–120 BPM)와 주파수 글라이드에 엔벨로프를 더해 만든 스무 가지 남짓의 큐를 최대 64보이스로 냅니다. 메뉴 트랙은 CC BY 4.0 녹음으로 다운로드에 실패하면 합성 루프로 대체되고, 탭이 숨겨지면 소리가 꺼지며 오디오 컨텍스트는 첫 클릭 뒤에만 잠금이 풀립니다. 최고 기록은 localStorage에 저장하고 저장소가 거부되면 메모리로 대체합니다 — 저장 실패가 게임을 중단시키는 일은 없습니다.