프로젝트 소개를 위한 인터랙티브 Studio
프로젝트를 링크 목록으로 소개하는 대신 직접 둘러볼 수 있는 공간을 만들어보고 싶었습니다. Studio는 집 안을 돌아다니며 사물을 누르고 기능을 여는 별도 사이트로 시작했습니다.
프로필이나 메인 홈페이지의 내용을 그대로 옮기기보다 조작할 수 있는 화면을 만드는 데 목적을 뒀습니다. 다만 3D 화면을 사용하지 않고도 기능에 접근할 수 있도록 가벼운 모드도 함께 구성했습니다.
방과 사물에 연결한 기능
공간은 거실과 작업방, 주방, 휴식 공간으로 나눴습니다. 벽과 가구의 충돌을 처리하고 가까이 가거나 사물을 눌렀을 때 해당 기능이 열리도록 했습니다.
3D 이동은 소개 방식의 일부이지 모든 기능을 사용하기 위한 필수 조건으로 두지는 않았습니다. 화면을 가볍게 보고 싶은 경우나 3D 조작이 어려운 경우에는 별도 모드로 접근할 수 있게 했습니다.
게임 구성과 공통 설정
게임기는 여러 게임을 실행하는 화면으로 확장해 최종적으로 12개를 넣었습니다. 블록 게임에서는 줄을 지운 뒤 남은 조각이 떨어지고, 그 결과 추가 줄이 지워질 수 있는 규칙을 적용했습니다.
공간과 게임은 확인할 동작도 달랐습니다.
| 구성 | 확인할 내용 |
|---|---|
| 3D 공간 | 이동·충돌·사물 상호작용 |
| 게임 | 입력과 상태 전환, 종료·재시작 |
| 공통 UI | 메뉴·버튼·모달 테마 |
| 가벼운 모드 | 3D 없이 기능 접근 |
| 소리 설정 | 공통 효과음과 저장된 선택 |
게임별로 소리 설정을 각각 만들기보다 공통 설정을 두고 브라우저에 저장했습니다. 공간의 낮과 밤 조명도 선택할 수 있도록 했습니다.
두 화면에서 공유하는 이동 규칙
현재 소스는 3D 장면을 그리는 src/room.js, 가벼운 모드의 src/flat-house.js, 공통 공간 규칙의 src/house-world.js를 나눕니다. 보기 방식마다 벽과 가구의 통과 여부를 따로 정하면 같은 장소인데 한쪽에서는 지나갈 수 있는 문제가 생길 수 있습니다.
공통 파일에는 방과 장애물의 좌표, 서 있을 수 있는 위치, 이동 계산이 있습니다. 아래는 실제 movePlayer 함수의 형태를 읽기 쉽게 정리한 코드입니다. canStand는 같은 모듈에서 방 밖으로 벗어나거나 장애물과 충돌하는지 검사합니다.
export function movePlayer(position, dx, dz) {
const steps = Math.max(1, Math.ceil(Math.hypot(dx, dz) / 0.08));
let { x, z } = position;
for (let i = 0; i < steps; i++) {
if (canStand(x + dx / steps, z)) x += dx / steps;
if (canStand(x, z + dz / steps)) z += dz / steps;
}
return { x, z };
}프레임 사이에 이동 거리가 길어지면 시작점과 끝점만 확인해서 얇은 벽을 건너갈 수 있습니다. 이 코드는 이동을 작은 단계로 나누고 각 단계에서 확인합니다. x축과 z축을 따로 검사하므로 한 방향이 막혀도 다른 방향의 이동은 남을 수 있습니다.
장면에서 가구 위치를 바꿨다면 충돌 정보도 함께 맞춰야 합니다. 눈에는 통로가 있는데 투명한 벽에 막히는 문제는 렌더링 성능보다 좌표 데이터가 어긋난 문제일 수 있습니다. 실제 소스도 보이는 가구와 이동 충돌이 같은 공간 정보를 사용하도록 구분해 두었습니다.
게임 규칙과 화면의 분리
게임은 src/games.js에서 목록을 관리하고, 개별 React 화면과 규칙 계산을 나눴습니다. 예를 들어 블록 게임의 UI는 BlockGame.jsx, 규칙은 block-game.js에 있습니다. 화면 효과가 없어도 줄 삭제와 조각 낙하, 연쇄 처리를 확인할 수 있어야 했습니다.
| 변경 | 먼저 확인할 부분 |
|---|---|
| 벽·가구 위치 | 공간 좌표와 충돌 영역 |
| 이동 입력 | 공통 이동 계산과 조작 화면 |
| 게임 규칙 | 상태를 바꾸는 계산 함수 |
| 메뉴·모달 | 공통 컴포넌트와 테마 |
| 소리·조명 | 저장된 사용자 선택과 기본값 |
저장소에서 실행하는 기본 흐름은 아래와 같습니다. 소스를 준비한 개발 환경에서 사용하는 명령입니다.
npm ci
npm run dev
npm run test:unit
npm run build
npm run previewREADME에는 브라우저·SEO 테스트도 따로 있습니다. 이 글을 보강하면서 그 앱의 전체 테스트를 다시 실행한 것은 아니며, 위 명령은 실제 저장소의 사용법을 정리한 것입니다. 블로그에서는 해당 소스의 역할과 코드 예시를 대조했습니다. 운영 화면은 Studio 에서 둘러볼 수 있습니다.
공통 테마와 3D 장면
메뉴와 버튼, 모달에는 공통 디자인 패키지를 사용했습니다. 공통 패키지가 다루는 주변 UI와 3D 배경·캐릭터는 구분했습니다.
이렇게 나누면 메뉴 스타일을 맞추는 변경과 공간 자체를 수정하는 작업을 따로 볼 수 있습니다. 공통 테마를 적용했다고 3D 장면까지 같은 방식으로 바뀌는 것은 아닙니다.
배포와 주소 구성
배포 전에는 게임과 화면 테스트, 정적 검색·공유 정보를 확인했습니다. 운영에 올라간 JS와 CSS가 빌드 결과와 같은지도 확인했습니다.
방과 게임은 화면 안의 기능이며 각각 독립된 주소는 아닙니다. 따라서 사이트맵에는 메인 주소만 넣었습니다. 기능을 배포하는 작업과 검색엔진에 제출하거나 색인 상태를 확인하는 작업도 따로 진행했습니다.
최종적으로는 3D 작업실과 3D 없이 접근하는 가벼운 모드를 함께 제공하게 됐습니다. 이동 규칙과 주변 UI는 공유하고, 게임의 규칙 계산은 화면 효과와 나눴습니다. 배포한 파일도 확인했지만, 방과 게임마다 별도 URL을 제공하는 구성은 아닙니다. Studio는 프로젝트 설명을 읽는 홈페이지와 달리 직접 둘러보고 조작하는 공간으로 남겼습니다.