В прошлой статье я описал, с чего начинать разработку VR в браузере. Для этого мы взяли популярный пример “Hello World” для A-Frame и немного его расширили, добавив окружение и возможность перемещаться в этом окружении с помощью VR-контроллеров, а также телепортироваться и т.д.
В текущей же статье мы пойдем гораздо дальше. Здесь мы добавим JS-код, который позволит выбирать объекты VR-сцены и активировать для них действия. Весь этот процесс будет также включать в себя крутую анимацию с крякающей уткой. 3D модель этой утки будет вращаться перед нами по заданной траектории. Когда кряканье этой птицы будет становиться слишком раздражающим, вы сможете подстрелить ее лазерным указателем. Реальные животные в этой статье не пострадают.
Настройка
Что ж, начнем. Чтобы облегчить вам чтение этой статьи, а мне ее написание, я собираюсь дать вам кучу кода сразу наперед. Сперва он может вас несколько запутать, но далее я этот код объясню, и вы сможете его изменять, чтобы видеть, как это влияет на результат.
Итак, вот этот код:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Advanced Hello World A-Frame WebXR</title>
<meta name="description" content="Продвинутый пример "Hello World", WebXR! A-frame предлагает действительно стоящий пример "Hello World", но обычно вам также требуется возможность выбора контроллера и окружение, позволяющее перемещение. Используя этот пример, вы получите вариант, который будет работать для большинства гарнитур и контроллеров".>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="gray-translucent" />
<!-- *** ИЗМЕНИТЕ ЭТИ КОМПОНЕНТЫ НА СВОЕМ СЕРВЕРЕ *** -->
<script src="aframe-master/dist/aframe-v1.0.4.min.js"></script>
<script src="aframe-environment-component-master/dist/aframe-environment-component.min.js"></script>
<script src="aframe-extras-master/dist/aframe-extras.min.js"></script>
<script src="aframe-teleport-controls-master/dist/aframe-teleport-controls.js"></script>
<script src="superframe-master/components/thumb-controls/dist/aframe-thumb-controls-component.min.js"></script>
<!-- These are added to provide for 3D text and tracked movement of the duck 3D model -->
<script src="superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>
<script src="aframe-alongpath-component-master/dist/aframe-alongpath-component.min.js" ></script>
<script src="aframe-curve-component-master/dist/aframe-curve-component.min.js"></script>
<!-- для создания Navmesh (сетки навигации)... Также раскомментируйте a-scene inspector-plugin-recast
<script src="https://recast-api.donmccurdy.com/aframe-inspector-plugin-recast.js"></script>
-->
<!-- JavaScript-код ниже обеспечивает выбор и возможность выполнения действий с объектами, средой и звуком -->
<script type="text/javascript">
// Аудио для утки
var squawk = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/duck.mp3');
// Функция, срабатывающая по клику
function fire_laser() {
// Утка издает звук
squawk.play();
// Утка исчезает
document.getElementById('movingDuck').setAttribute('visible', 'false');
}function doCylinder() {
// Возвращаем видимость утки и куба, снова делаем сферу красной
document.getElementById('movingDuck').setAttribute('visible',
true);
document.getElementById('cube').setAttribute('visible', true);
document.getElementById('sphere').setAttribute('color', 'red');
}function doCube() {
// Делаем сферу зеленой, временно убираем куб
document.getElementById('sphere').setAttribute('color', 'green');
document.getElementById('cube').setAttribute('visible', false);
}
// Компонент, срабатывающий по клику
AFRAME.registerComponent('click-listener', {
init: function () {this.el.addEventListener('click', function (evt) {
// Удаляем из сцены объект, по которому кликнули
//this.setAttribute('visible', false);
});
}
});
// Убираем блокировку звука в Google Chrome https://stackoverflow.com/questions/47921013/play-sound-on-click-in-a-frame?answertab=active#tab-top
AFRAME.registerComponent('audiohandler', {
init:function() {
let playing = false;
let audio = document.querySelector("#playAudio");
this.el.addEventListener('click', () => {if(!playing) {
audio.play();
} else {
audio.pause();
audio.currentTime = 0;
}
playing = !playing;
});
}
})
</script>
</head>
<body>
<button id="playButton" type="button">Play Music</button>
<!-- Музыка из .mp3 файла с открытым для использования произведением Моцарта. Если решите ее заменить, убедитесь, что используете либо открытое произведение, либо свое. -->
<audio id="playAudio" autoplay loop>
<source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/MozartALittleNightMusic.mp3" type="audio/mpeg">
</audio>
<!-- используется для создания Navmesh (сетки навигации)... смотрите javascript-компонент над <a-scene inspector-plugin-recast> https://github.com/donmccurdy/aframe-inspector-plugin-recast-->
<a-scene background="color: #FAFAFA">
<a-assets>
<!-- Шрифт -->
<a-asset-item id="optimerBoldFont" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item>
<!-- 3D GltF модель утки -->
<a-asset-item id="duck" src="assets/gltf/Duck.glb"></a-asset-item>
</a-assets>
<!-- nav-mesh: не дает нам проходить сквозь сферу, куб и цилиндр -->
<a-entity id="navmesh-Hello" gltf-model="assets/gltf/AdvHelloWorldnavmesh.gltf" visible="false" nav-mesh=""></a-entity>
<!-- Базовое передвижение и телепортация -->
<a-entity id="cameraRig" movement-controls="constrainToNavMesh: true;" navigator="cameraRig: #cameraRig; cameraHead: #head; collisionEntities: .collision; ignoreEntities: .clickable" position="0 0 0" rotation="0 0 0">
<!-- камера-->
<a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>
<!-- Левый контроллер -->
<a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: left" oculus-touch-controls="hand: left" windows-motion-controls="hand: left" teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; button: trigger; type: line; curveShootingSpeed: 18; collisionEntities: #navmesh-Hello; landingMaxAngle: 60" visible="true"></a-entity>
<!-- Правый контроллер -->
<a-entity class="rightController" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: right" oculus-touch-controls="hand: right" windows-motion-controls="hand: right" laser-controls raycaster="showLine: true; far: 10; interval: 0; objects: .clickable, a-link;" line="color: lawngreen; opacity: 0.5" visible="true"></a-entity>
</a-entity>
<!-- Стандартные объекты Hello World, измененные для реагирования на клики и аудио -->
<a-box id="cube" class="clickable" position="-1 0.66921 -3" rotation="0 45 0" color="#4CC3D9" visible="true" shadow onclick="doCube();" click-listener></a-box>
<a-sphere id="sphere" class="clickable" position="0 1.44508 -5" radius="1.25" color="#EF2D5E" shadow audiohandler></a-sphere>
<a-cylinder id="cylinder" class="clickable" position="1 0.8993 -3" radius="0.5" height="1.5" color="#FFC65D" shadow onclick="doCylinder();" click-listener></a-cylinder>
<a-plane position="0 0.08958 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow="recieve: true" ></a-plane >
<!-- 3D текст -->
<a-entity position="-3.44 2.801 -5.127" text-geometry="value: Advanced" material="color: #EC4DF4"></a-entity>
<a-entity position="-0.05 2.793 -5.090" text-geometry="value: Hello World; font: #optimerBoldFont" material="color: #F94DA2"></a-entity>
<a-entity id="movingDuck" class="clickable" gltf-model="#duck" alongpath="curve:#track;loop:true;dur:14000;rotate:true" position="0 1.6 -5" shadow="receive:false" scale="1 1 1" animation__rotate="property: rotation; dur: 2000; easing: linear; loop: true; to: 0 360 0" shadow onclick="fire_laser();" cursor-listener></a-entity>
<!-- Трек для перемещения утки -->
<a-curve id="track" >
<a-curve-point position="0 1 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="5 1 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="7 1 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="5 1 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="0 1 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="-6 1 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="-8 1 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="-6 1 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="0 1 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
</a-curve>
<!-- Делаем линию траектории красной -->
<a-draw-curve curveref="#track" material="shader: line; color: red;"></a-draw-curve>
<!-- Окружение -->
<a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b;" shadow="recieve: true"></a-entity>
<!-- Освещение сцены, отбрасывающее тени -->
<a-entity light="intensity: 0.6; castShadow: true; shadowCameraLeft: -10; shadowCameraBottom: -10; shadowCameraRight: 10; shadowCameraTop: 10; shadowCameraVisible: true" position="9.9649 17.32329 13.93447"></a-entity>
</a-scene>
</body>
</html>
Вы можете опробовать этот код на моем сервере, открыв в новой вкладке следующую ссылку:Advanced Hello World A-Frame WebXR
Advanced Hello World, WebXR! A-frame provides a hello world that is really remarkable, however you usually need to have…rocketvirtual.com
Для тех, кто использует ноутбук или настольный ПК: вы можете перемещаться по сцене при помощи клавиш WASD и разворачиваться перетаскиванием центра экрана при зажатой левой кнопке мыши.
Если же вы зайдете в VR-гарнитуре, нажав кнопку VR в правом нижнем углу экрана, то погрузитесь в полноценную VR-среду, где вам будет доступен обзор на все 360 градусов. В том или ином случае вы увидите примерно следующее:
Чтобы упростить этот процесс, я буду описывать код, ссылаясь на соответствующие его строки. Поэтому скопируйте и вставьте приведенный выше код в свой редактор, активировав нумерацию строк. При желании вы также можете загрузить бесплатный редактор Sublime.
В том или ином случае полученный код должен выглядеть примерно так (все 131 строки):
Строки с 13 по 17 и с 19 по 21 являются JS-библиотеками компонентов A-Frame, которые используются для реализации ряда особенностей VR в браузере. Если не загрузить их, то код просто не заработает. Итак, если у вас есть доступ к интернет-серверу через https:// (это необходимо), эти файлы скопируются с GitHub к вам на сервер и будут иметь соответствующие ссылки.
ЛИБО
Если у вас нет доступа к серверу, но при этом вы можете установить на локальную машину Node.js, тогда следуйте перечисленным здесь инструкциям для настройки системы на порт localhost:2000. Помимо этого, убедитесь, что используете код, приведенный ниже, а не тот, что был выше, потому что в этом инструкция client/ расположена в нужных местах, что позволит симулировать сервер локально при помощи localhost.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Advanced Hello World A-Frame WebXR</title>
<meta name="description" content="Продвинутый пример "Hello World", WebXR! A-frame предлагает действительно стоящий пример "Hello World", но обычно вам также требуется возможность выбора контроллера и окружение, позволяющее перемещение. Используя этот пример, вы получите вариант, который будет работать для большинства гарнитур и контроллеров.">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="gray-translucent" />
<!-- *** ИЗМЕНИТЕ ЭТИ КОМПОНЕНТЫ НА СВОЕМ СЕРВЕРЕ *** -->
<script src="client/aframe-master/dist/aframe-v1.0.4.min.js"></script>
<script src="client/aframe-environment-component-master/dist/aframe-environment-component.min.js"></script>
<script src="client/aframe-extras-master/dist/aframe-extras.min.js"></script>
<script src="client/aframe-teleport-controls-master/dist/aframe-teleport-controls.js"></script>
<script src="client/superframe-master/components/thumb-controls/dist/aframe-thumb-controls-component.min.js"></script>
<!-- These are added to provide for 3D text and tracked movement of the duck 3D model -->
<script src="client/superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>
<script src="client/aframe-alongpath-component-master/dist/aframe-alongpath-component.min.js" ></script>
<script src="client/aframe-curve-component-master/dist/aframe-curve-component.min.js"></script>
<!-- для создания Navmesh... Также раскомментируйте a-scene inspector-plugin-recast
<script src="https://recast-api.donmccurdy.com/aframe-inspector-plugin-recast.js"></script>
-->
<!-- JavaScript-код ниже обеспечивает возможность выбирать объекты, окружение и аудио, а также производить с ними действия -->
<script type="text/javascript">
// Озвучивание утки
var squawk = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/duck.mp3');
// Функция для выполнения при клике
function fire_laser() {
// Утка издает квакающий звук
squawk.play();
// Утка исчезает
document.getElementById('movingDuck').setAttribute('visible', 'false');
}
function doCylinder() {
// Возвращаем видимость Duck и Cube, снова делаем Sphere красной
document.getElementById('movingDuck').setAttribute('visible', true);
document.getElementById('cube').setAttribute('visible', true);
document.getElementById('sphere').setAttribute('color', 'red');
}
function doCube() {
// Делаем сферу зеленой, временно убираем куб
document.getElementById('sphere').setAttribute('color', 'green');
document.getElementById('cube').setAttribute('visible', false);
}
// Компонент, выполняющийся по клику
AFRAME.registerComponent('click-listener', {
init: function () {
this.el.addEventListener('click', function (evt) {
// Удаляем из сцены объект, по которому кликнули
//this.setAttribute('visible', false);
});
}
});
// Устраняет блокировку звука в Google Chrome https://stackoverflow.com/questions/47921013/play-sound-on-click-in-a-frame?answertab=active#tab-top
AFRAME.registerComponent('audiohandler', {
init:function() {
let playing = false;
let audio = document.querySelector("#playAudio");
this.el.addEventListener('click', () => {
if(!playing) {
audio.play();
} else {
audio.pause();
audio.currentTime = 0;
}
playing = !playing;
});
}
})
</script>
</head>
<body>
<button id="playButton" type="button">Play Music</button>
<!-- Музыка из .mp3 файла с открытым для использования произведением Моцарта. Если решите ее заменить, убедитесь, что используете либо открытое произведение, либо свое. -->
<audio id="playAudio" autoplay loop>
<source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/MozartALittleNightMusic.mp3" type="audio/mpeg">
</audio>
<!-- используется для создания Navmesh... смотрите javascript-компонент над <a-scene inspector-plugin-recast> https://github.com/donmccurdy/aframe-inspector-plugin-recast-->
<a-scene background="color: #FAFAFA">
<a-assets>
<!-- Шрифт -->
<a-asset-item id="optimerBoldFont" src="client/assets/fonts/optimer_bold.typeface.json"></a-asset-item>
<!-- 3D GltF модель утки -->
<a-asset-item id="duck" src="client/assets/gltf/Duck.glb"></a-asset-item>
</a-assets>
<!-- nav-mesh: не дает нам проходить сквозь сферу, куб и цилиндр -->
<a-entity id="navmesh-Hello" gltf-model="client/assets/gltf/AdvHelloWorldnavmesh.gltf" visible="false" nav-mesh=""></a-entity>
<!-- Базовое перемещение и телепортация -->
<a-entity id="cameraRig" movement-controls="constrainToNavMesh: true;" navigator="cameraRig: #cameraRig; cameraHead: #head; collisionEntities: .collision; ignoreEntities: .clickable" position="0 0 0" rotation="0 0 0">
<!-- камера-->
<a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>
<!-- Левый контроллер -->
<a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: left" oculus-touch-controls="hand: left" windows-motion-controls="hand: left" teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; button: trigger; type: line; curveShootingSpeed: 18; collisionEntities: #navmesh-Hello; landingMaxAngle: 60" visible="true"></a-entity>
<!-- Правый контроллер -->
<a-entity class="rightController" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: right" oculus-touch-controls="hand: right" windows-motion-controls="hand: right" laser-controls raycaster="showLine: true; far: 10; interval: 0; objects: .clickable, a-link;" line="color: lawngreen; opacity: 0.5" visible="true"></a-entity>
</a-entity>
<!-- Стандартные объекты Hello World, измененные для реагирования на клик и аудио -->
<a-box id="cube" class="clickable" position="-1 0.66921 -3" rotation="0 45 0" color="#4CC3D9" visible="true" shadow onclick="doCube();" click-listener></a-box>
<a-sphere id="sphere" class="clickable" position="0 1.44508 -5" radius="1.25" color="#EF2D5E" shadow audiohandler></a-sphere>
<a-cylinder id="cylinder" class="clickable" position="1 0.8993 -3" radius="0.5" height="1.5" color="#FFC65D" shadow onclick="doCylinder();" click-listener></a-cylinder>
<a-plane position="0 0.08958 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow="recieve: true" ></a-plane >
<!-- 3D текст -->
<a-entity position="-3.44 2.801 -5.127" text-geometry="value: Advanced" material="color: #EC4DF4"></a-entity>
<a-entity position="-0.05 2.793 -5.090" text-geometry="value: Hello World; font: #optimerBoldFont" material="color: #F94DA2"></a-entity>
<a-entity id="movingDuck" class="clickable" gltf-model="#duck" alongpath="curve:#track;loop:true;dur:14000;rotate:true" position="0 1.6 -5" shadow="receive:false" scale="1 1 1" animation__rotate="property: rotation; dur: 2000; easing: linear; loop: true; to: 0 360 0" shadow onclick="fire_laser();" cursor-listener></a-entity>
<!-- Трек для перемещения утки -->
<a-curve id="track" >
<a-curve-point position="0 1 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="5 1 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="7 1 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="5 1 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="0 1 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="-6 1 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="-8 1 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="-6 1 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
<a-curve-point position="0 1 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="true"></a-curve-point>
</a-curve>
<!-- Красная линия трека -->
<a-draw-curve curveref="#track" material="shader: line; color: red;"></a-draw-curve>
<!-- Окружение -->
<a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b;" shadow="recieve: true"></a-entity>
<!-- Освещение сцены, отбрасывающее тени -->
<a-entity light="intensity: 0.6; castShadow: true; shadowCameraLeft: -10; shadowCameraBottom: -10; shadowCameraRight: 10; shadowCameraTop: 10; shadowCameraVisible: true" position="9.9649 17.32329 13.93447"></a-entity>
</a-scene>
</body>
</html>
Эта версия для Node.js отличается только строчками 13–17, 19–21, 87, 89 и 92, где расположена инструкция “/client”.
Необходимые библиотеки A-Frame компонентов
Скопируйте следующие библиотеки компонентов A-Frame с указанных ресурсов GitHub на свой сервер или в директорию Node.js-клиента локальной машины:
n5ro/aframe-extras
Add-ons and helpers for A-Frame VR.github.com
Я понимаю, что с настройками пришлось повозиться, но поверьте мне, оно того стоило, и в итоге вы получите среду разработки для VR.
Что ж, давайте продолжим.
Я подготовил для вас несколько заготовок: звук кряканья утки (строка 28), 3d модель утки (.glb файл в формате glTF — строка 89), 3D шрифт (строка 87), разрешенную для использования музыку (строка 81) и сетку навигации (строка 92), c которой мы познакомимся чуть позже. Все эти заготовки находятся в указанном ниже zip-файле со структурой директории assets, соответствующей нашему примеру кода.
https://rocketvirtual.com/A-Frame_WebXR/assets.zip
Файлы следует разместить на сервере или в Node.js-директории /client. Измените строки 28, 81, 87, 89 и 92, указав правильный путь к серверу или локальному клиенту Node. Считаю нужным сообщить, что если вы не хотите включать эти файлы и изменять пути в коде, то они все равно должны работать с моего сервера. Однако их скорость будет уже не той, как если бы они размещались локально. Кроме того, они могут стать труднодоступными, если статья вдруг станет популярной.
Теперь мы завершили настройку и можем переходить к более подробному описанию кода.
Траектория движения утки
В некоторых играх или симуляторах элементы могут следовать замкнутому или повторяющемуся треку. aframe-alongpath-component
позволяет сущностям следовать предопределенным путям.
Строки 112–122 определяют кривую пути при помощи пространственных значений x, y, z. В id=”track”
, представляющий имя трека, прописаны девять точек кривой. Обратите также внимание, что у каждой из этих точек есть значение visible=true
. Давайте изменим все эти значения на visible=”false”
, в следствии чего точки исчезнут.
Заметьте, что на строке 124 мы нарисовали красную кривую линию вдоль curveref=”#track”
. Если мы вдруг решим удалить эту строчку кода, то красная линия трека, по которой следует утка, исчезнет, но при этом утка продолжит следовать невидимой траектории, потому что определена она в строке 110.
Обратите внимание на атрибут строки 110:
alongpath=”curve:#track;loop:true;dur:14000;rotate:true”
Это означает, что утка gltf-model=”#duck”
будет бесконечно повторять траекторию во время вращения. Остальная часть вращения определена в animation__rotate= attribute
. Создание анимации может представлять определенную сложность, поэтому в коде мы используем ее простой пример.
Ну и наконец обратите внимание, что утка загружается в виде заготовки между тегами <a-sssets>
и </a-assets>
на строке 89:
<a-asset-item id=”duck” src=”client/assets/gltf/Duck.glb”></a-asset-item>
Она представляет собой особый тип 3D модели в формате glTF. Рассмотрение этой темы также выходит за рамки текущей статьи.
Выбор элементов в режиме VR
Возвращаясь к строке 110, обратите внимание на следующие атрибуты:
<a-entity id=”movingDuck” class=”clickable” gltf-model=”#duck” alongpath=”curve:#track;loop:true;dur:14000;rotate:true” position=”0 1.6 -5" shadow=”receive:false” scale=”1 1 1" animation__rotate=”property: rotation; dur: 2000; easing: linear; loop: true; to: 0 360 0" shadow onclick=”fire_laser();” cursor-listener></a-entity>
class=”clickable”
означает, что по утке можно кликнуть лазерным указателем.
shadow=”receive:false”
означает, что на утку не должны отбрасываться тени. Это должно снизить графическую нагрузку, но в нашем скромном примере мы этого не заметим.
shadow
означает, что утка может отбрасывать тень, скажем, на землю, что вполне естественно.
onclick=”fire_laser();”
означает, что при выборе утки будет выполнена JS-функция на строке 30.
cursor-listener
означает, что утка ожидает, прослушивая событие своего выбора. Это часть AFRAME.registerComponent(‘click-listener’,…
. Зарегистрированные компоненты в итоге находятся там, где надо, но сами при этом несколько сложнее, чем хотелось бы на данном этапе.
Итак, мы достаточно подробно рассмотрели утку и ее движение по треку. Но что же все-таки происходит, когда мы по ней кликаем? Выполняется функция fire_laser()
, расположенная на строках 30–35.
function fire_laser() {
// Утка издает крякающий звук
squawk.play();
// Утка исчезает
document.getElementById('movingDuck').setAttribute('visible', 'false');
}
Мы проигрываем звук утки, загруженный в squawk
на строке 28, после чего делаем кое-что интересное, а именно заставляем утку исчезнуть, установив атрибут visible
в строке 110 на false
.
Утка продолжает вращаться по треку, но для нас она теперь невидима, и графическому процессору не нужно ее прорисовывать. Я не уверен, сработает ли выбор raycaster
, если мы все-таки случайно по ней кликнем. Думаю, что нет.
Продолжает ли вообще alongpath
перемещать утку? Да это и не важно, ведь на строке 105 мы снова сделаем ее видимой. Для этого нужно будет кликнуть по цилиндру, который выполняет функцию doCylinder()
, прописанную на строках 36–42.
<a-cylinder id="cylinder" class="clickable" position="1 0.8993 -3" radius="0.5" height="1.5" color="#FFC65D" shadow onclick="doCylinder();" click-listener></a-cylinder>
И сновав дело вступают class=”clickable”
, click-listener
и onclick
, которые, работая в тандеме, запускают функцию.
function doCylinder() {
// Возвращаем видимость утке и кубу, снова делаем сферу красной
document.getElementById('movingDuck').setAttribute('visible', true);
document.getElementById('cube').setAttribute('visible', true);
document.getElementById('sphere').setAttribute('color', 'red');
}
Управлять атрибутами через DOM технически менее производительно, чем при помощи зарегистрированного компонента A-FRAME. Здесь же я делаю так, потому что этот пример опирается на уже известные нам как веб-разработчикам понятия, и производительность не является проблемой.
Освещение и тени
Давайте теперь рассмотрим Shadow Camera (англ.), которой мы управляем на строке 128.
<!-- Освещение сцены, отбрасывающее тени -->
<a-entity light="intensity: 0.6; castShadow: true; shadowCameraLeft: -10; shadowCameraBottom: -10; shadowCameraRight: 10; shadowCameraTop: 10; shadowCameraVisible: true" position="9.9649 17.32329 13.93447"></a-entity>
Обратите внимание, что shadowCameraVisible: true
. Если изменить значение на false
, то оранжевые линии камеры исчезнут. Я сделал их видимыми, чтобы продемонстрировать, что они существуют, и как можно с их помощью контролировать расположение теней на сцене. Вы заметили, что ни один из фиолетовых грибов не отбрасывает тень?
Чтобы это исправить, мы добавили тени в окружение и расширили область охвата камеры. Теперь грибы начали отбрасывать тени.
<!-- Окружение -->
<a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b; shadow: true" shadow="recieve: true"></a-entity>
<!-- Освещение сцены, отбрасывающее тени -->
<a-entity light="intensity: 0.6; castShadow: true; shadowCameraLeft: -50; shadowCameraBottom: -50; shadowCameraRight: 50; shadowCameraTop: 50; shadowCameraVisible: false" position="9.9649 17.32329 13.93447"></a-entity>
Я вижу одну ошибку теней при перемещении утки по треку. Где-то в сцене есть и другие камеры теней, которые отбрасывают двойную тень от других источников освещения. Мы не будем заострять на этом внимание, потому что нужно двигаться дальше.
Сетка навигации (navmesh)
Эта сетка помогает перемещаться в VR-режиме. Она представляет один из способов ограничения управления движением и телепортации в рамках игровой зоны. Без нее вы могли бы проходить прямо сквозь сферу, куб или цилиндр, что в реальной жизни, конечно же, невозможно.
Создать эту сетку не так-то просто. потребуется кое-что добавить, изменить и даже удалить.
<!-- для создания навигационной сетки... Также раскомментируйте a-scene inspector-plugin-recast -->
<script src="https://recast-api.donmccurdy.com/aframe-inspector-plugin-recast.js"></script><a-scene background="color: #FAFAFA" aframe-inspector-plugin-recast>
Следуйте инструкциям Дона МакКурди по использованию плагина Inspector. Войдите в Inspector, нажав Ctrl-Alt-I и выберите в верхнем левом углу <a-scene>. Вы должны увидеть плагин, подготовленный под ваши параметры. Я удалил большую часть кода, который был лишним для нашей сетки навигации, например 3D текст на строках 108 и 109. Кроме этого, в разделе ENVIRONMENT я изменил cellSize
на 0.11 и уменьшил dressingAmount
до 1, чтобы в итоге все заработало.
Когда у вас будет готов файл navmesh.gltf
(переименованный мной в AdvHelloWorldnavmesh.gltf
), вы сможете использовать его в исходном коде. Смотрите строку 92.
<!-- nav-mesh: не дает нам проходить сквозь сферу, куб и цилиндр -->
<a-entity id="navmesh-Hello" gltf-model="assets/gltf/AdvHelloWorldnavmesh.gltf" visible="false" nav-mesh=""></a-entity>
<!-- Базовое перемещение и телепортация -->
<a-entity id="cameraRig" movement-controls="constrainToNavMesh: true;" navigator="cameraRig: #cameraRig; cameraHead: #head; collisionEntities: .collision; ignoreEntities: .clickable" position="0 0 0" rotation="0 0 0">
<!-- Камера-->
<a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>
<!-- Левый контроллер -->
<a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: left" oculus-touch-controls="hand: left" windows-motion-controls="hand: left" teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; button: trigger; type: line; curveShootingSpeed: 18; collisionEntities: #navmesh-Hello; landingMaxAngle: 60" visible="true"></a-entity
Я подготовил такой файл под названием AdvHelloWorldnavmesh.gltf
с id=”navmesh-Hello”
. На строке 94 вы увидите атрибут movement-controls=”constrainToNavMesh: true;”
. Он активирует навигационную сетку. В завершении нужно добавить в управление телепортом на строке 98 collisionEntities: #navmesh-Hello;
.
Теперь все должно заработать и у вас не получится проходить сквозь сферу, куб и цилиндр. Можете попробовать.
Однако вы можете заметить, что после того, как вы выбираете куб, и он исчезает, вы все равно не можете проходить через занимаемое им пространство и телепортироваться в него. Что ж, как говорится, ничто не идеально. Чтобы это исправить, понадобиться перебрать разные сетки.
На этом наш продвинутый пример Hello World для A-Frame завершен. Надеюсь, что вам понравилась как сама статья, так и простота богатых VR-возможностей, предлагаемых A-Frame.
Удачи в VR-программировании!
Читайте также:
- Эффективное или частное хранение данных с помощью JavaScript WeakMaps
- Создавайте веб-приложения, которые умеют слушать! ?
- Множества ES6 в JavaScript. Зачем?
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Michael McAnally: Advanced Hello World for A-Frame