레슨 7 / 8·2개 토픽
실전: VR 갤러리 만들기
인터랙티브 VR 갤러리 프로젝트
지금까지 배운 에셋 관리, 모델, 커스텀 컴포넌트, 애니메이션, 인터랙션을 모두 결합하여 VR 갤러리를 만들어 봅니다. 갤러리에는 이미지 패널, 클릭 네비게이션, 텔레포테이션 이동, 정보 패널이 포함됩니다. 이 프로젝트를 완성하면 A-Frame의 핵심 기능을 실전에서 활용하는 방법을 체득할 수 있습니다.
html
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script>
// ── 텔레포테이션 컴포넌트 ──
AFRAME.registerComponent('teleport-on-click', {
init: function () {
this.el.addEventListener('click', (evt) => {
const camera = document.querySelector('[camera]');
const point = evt.detail.intersection.point;
camera.setAttribute('position', {
x: point.x,
y: 1.6,
z: point.z,
});
});
},
});
// ── 정보 패널 토글 컴포넌트 ──
AFRAME.registerComponent('info-panel', {
schema: {
title: { type: 'string', default: '' },
description: { type: 'string', default: '' },
panelId: { type: 'string', default: '' },
},
init: function () {
this.el.addEventListener('click', () => {
const panel = document.querySelector('#' + this.data.panelId);
if (!panel) return;
const visible = panel.getAttribute('visible');
panel.setAttribute('visible', !visible);
});
},
});
</script>
</head>
<body>
<a-scene fog="type: linear; color: #1a1a2e; near: 10; far: 30">
<a-assets>
<img id="art1" src="gallery/painting1.jpg" />
<img id="art2" src="gallery/painting2.jpg" />
<img id="art3" src="gallery/painting3.jpg" />
<img id="art4" src="gallery/painting4.jpg" />
<img id="floor-tex" src="gallery/marble.jpg" />
<img id="wall-tex" src="gallery/wall.jpg" />
</a-assets>
<!-- 하늘 -->
<a-sky color="#1a1a2e"></a-sky>
<!-- 조명 -->
<a-light type="ambient" color="#404060" intensity="0.3"></a-light>
<a-light type="point" color="#ffffff" intensity="1" position="0 4 0"></a-light>
<a-light type="point" color="#ffe0b0" intensity="0.8" position="-4 3 -4"></a-light>
<a-light type="point" color="#ffe0b0" intensity="0.8" position="4 3 -4"></a-light>
<!-- 바닥 (텔레포테이션 가능) -->
<a-plane
src="#floor-tex"
rotation="-90 0 0"
width="20"
height="20"
repeat="5 5"
class="clickable"
teleport-on-click
></a-plane>
<!-- 벽면 -->
<a-box src="#wall-tex" position="0 2 -8" width="16" height="4" depth="0.2"></a-box>
<!-- 갤러리 이미지 패널 -->
<a-image
src="#art1"
position="-4 2 -7.8"
width="2.5"
height="2"
class="clickable"
info-panel="title: 작품 1; description: 첫 번째 작품입니다; panelId: info1"
animation__hover="property: scale; to: 1.05 1.05 1; dur: 200; startEvents: mouseenter"
animation__unhover="property: scale; to: 1 1 1; dur: 200; startEvents: mouseleave"
></a-image>
<a-image
src="#art2"
position="-1 2 -7.8"
width="2.5"
height="2"
class="clickable"
info-panel="title: 작품 2; description: 두 번째 작품입니다; panelId: info2"
animation__hover="property: scale; to: 1.05 1.05 1; dur: 200; startEvents: mouseenter"
animation__unhover="property: scale; to: 1 1 1; dur: 200; startEvents: mouseleave"
></a-image>
<a-image
src="#art3"
position="2 2 -7.8"
width="2.5"
height="2"
class="clickable"
info-panel="title: 작품 3; description: 세 번째 작품입니다; panelId: info3"
animation__hover="property: scale; to: 1.05 1.05 1; dur: 200; startEvents: mouseenter"
animation__unhover="property: scale; to: 1 1 1; dur: 200; startEvents: mouseleave"
></a-image>
<a-image
src="#art4"
position="5 2 -7.8"
width="2.5"
height="2"
class="clickable"
info-panel="title: 작품 4; description: 네 번째 작품입니다; panelId: info4"
animation__hover="property: scale; to: 1.05 1.05 1; dur: 200; startEvents: mouseenter"
animation__unhover="property: scale; to: 1 1 1; dur: 200; startEvents: mouseleave"
></a-image>
<!-- 정보 패널 (기본 숨김) -->
<a-entity id="info1" visible="false" position="-4 3.5 -7.5">
<a-plane color="#222" width="2.5" height="0.6" opacity="0.9"></a-plane>
<a-text value="작품 1: 첫 번째 작품입니다" align="center" color="#fff" width="2" position="0 0 0.01"></a-text>
</a-entity>
<a-entity id="info2" visible="false" position="-1 3.5 -7.5">
<a-plane color="#222" width="2.5" height="0.6" opacity="0.9"></a-plane>
<a-text value="작품 2: 두 번째 작품입니다" align="center" color="#fff" width="2" position="0 0 0.01"></a-text>
</a-entity>
<a-entity id="info3" visible="false" position="2 3.5 -7.5">
<a-plane color="#222" width="2.5" height="0.6" opacity="0.9"></a-plane>
<a-text value="작품 3: 세 번째 작품입니다" align="center" color="#fff" width="2" position="0 0 0.01"></a-text>
</a-entity>
<a-entity id="info4" visible="false" position="5 3.5 -7.5">
<a-plane color="#222" width="2.5" height="0.6" opacity="0.9"></a-plane>
<a-text value="작품 4: 네 번째 작품입니다" align="center" color="#fff" width="2" position="0 0 0.01"></a-text>
</a-entity>
<!-- 카메라 + 커서 -->
<a-entity camera look-controls wasd-controls="acceleration: 20" position="0 1.6 0">
<a-entity
cursor="rayOrigin: mouse"
raycaster="objects: .clickable"
></a-entity>
</a-entity>
</a-scene>
</body>
</html>갤러리 확장 아이디어
- •텔레포테이션 — 바닥 클릭 시 해당 위치로 카메라 이동
- •정보 패널 — 작품 클릭 시 제목/설명 토글 표시
- •호버 애니메이션 — 마우스 오버 시 이미지 약간 확대
- •안개 효과 — 갤러리 깊이감과 분위기 연출
- •에셋 사전 로딩 —
로 모든 이미지를 미리 로딩 - •여러 방 구성 — 텔레포트 포인트로 연결된 다중 방 구현
- •오디오 가이드 — 작품별 오디오 설명 재생 (sound 컴포넌트)
- •VR 컨트롤러 — laser-controls로 VR 기기 지원 추가
💡
실전 프로젝트에서는 이미지 최적화가 중요합니다. 텍스처는 2의 거듭제곱(512x512, 1024x1024 등) 크기로 준비하면 GPU 메모리 효율이 좋습니다. WebP 포맷을 사용하면 파일 크기를 줄이면서 품질을 유지할 수 있습니다. 갤러리에 작품이 많으면 LOD(Level of Detail)나 지연 로딩을 고려하세요.