Важное обновление кода:



Я написал предыдущую статью, в которой описал, с чего начать с VR в браузере. Это взяло популярный A-Frame Hello World и немного расширило его, добавив среду и возможность перемещаться в этой среде с помощью контроллеров VR, телепорта и т. Д.

Теперь эта статья пойдет намного дальше. Мы собираемся включить JavaScript, который может выбирать и активировать действия с объектами в нашей VR-сцене. Это будет включать в себя классную анимацию с крякающей уткой! 3D-модель утки будет петлять по дорожке вокруг нас. Когда утка станет слишком надоедать, вы можете выстрелить в нее из лазерной указки. В этой статье не пострадают никакие настоящие животные.

Настройка

Давай начнем . . . Чтобы сделать эту статью более удобной для чтения и написания, я заранее дам вам много кода. Сначала это может немного сбить с толку, но я объясню этот код, и вы можете изменить его, чтобы увидеть, что происходит.

Хорошо, вот код Advance Hello World:

<!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="Advanced Hello World, WebXR! A-frame provides a hello world that is really remarkable, however you usually need to have controller selection and environment with movement.  With this example you get what works across the most headsets and controllers.">
    <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" />
<!-- *** CHANGE THESE TO COMPONENTS ON YOUR SERVER *** -->
    <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>
<!-- for creating Navmesh... Also uncomment a-scene inspector-plugin-recast
    <script src="https://recast-api.donmccurdy.com/aframe-inspector-plugin-recast.js"></script>
    -->
<!-- The JavaScript below provides for selection and action on objects, environment and audio -->
    <script type="text/javascript">
// Audio squawk for the duck
      var squawk = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/duck.mp3');
// Function to execute onclick
      function fire_laser() {
// Make Duck Quack sound
        squawk.play();
//Disappear Duck
        document.getElementById('movingDuck').setAttribute('visible', 'false');
      }
function doCylinder() {
        
        // Bring back visibility of Duck and Cube, turn Sphere red again
        document.getElementById('movingDuck').setAttribute('visible', true);
        document.getElementById('cube').setAttribute('visible', true);
        document.getElementById('sphere').setAttribute('color', 'red');
      }
function doCube() {
        
        // Make Sphere green, disappear Cube for now
        document.getElementById('sphere').setAttribute('color', 'green');
        document.getElementById('cube').setAttribute('visible', false);      
      }
// Component to do on click.
      AFRAME.registerComponent('click-listener', {
        init: function () {
this.el.addEventListener('click', function (evt) {
// remove clicked object from view
                //this.setAttribute('visible', false);
      
            });
        }
      });
//   Solves Google Chrome mute of audio 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>
    <!-- The music sound of the following open source classical Mozart .mp3 file.  If you replace, make sure it's open source or you have your own rights. -->
    <audio id="playAudio" autoplay loop>
        <source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/MozartALittleNightMusic.mp3" type="audio/mpeg">
    </audio>
<!-- used for creating Navmesh... see javascript component above <a-scene inspector-plugin-recast>  https://github.com/donmccurdy/aframe-inspector-plugin-recast-->
<a-scene background="color: #FAFAFA">
<a-assets>
            <!-- Our font -->
            <a-asset-item id="optimerBoldFont" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item>
<!-- Duck 3D GltF model -->
            <a-asset-item id="duck" src="assets/gltf/Duck.glb"></a-asset-item>
      </a-assets>
<!-- nav-mesh: protecting us from running thru sphere, cube and cylinder  -->
      <a-entity id="navmesh-Hello" gltf-model="assets/gltf/AdvHelloWorldnavmesh.gltf" visible="false" nav-mesh=""></a-entity>
<!-- Basic movement and teleportation  -->
      <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">
        <!-- camera -->
        <a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>
              <!-- Left Controller  -->
              <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>
              <!-- Right Controller  -->
              <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>
<!-- Normal Hello World objects modified to respond to onclick and audio  -->
      <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 >
<!-- Some 3D Text -->
      <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 track for our Duck to follow  --> 
      <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>
<!-- The track line in red  --> 
      <a-draw-curve curveref="#track" material="shader: line; color: red;"></a-draw-curve>
<!-- Our Environment -->
      <a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b;" shadow="recieve: true"></a-entity>
<!-- A light in the scene casting shadows -->
      <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>

Вы можете попробовать этот код здесь, на моем сервере, открыв ссылку в новой вкладке ниже:



Те, у кого есть ноутбук или настольный компьютер, могут перемещаться по сцене с помощью клавиш WASD на клавиатуре и поворачиваться, перетаскивая в центр экрана левой кнопкой мыши.

Если вы войдете в сцену с гарнитурой VR, нажав кнопку VR в правом нижнем углу экрана, вы погрузитесь в 360-градусную среду VR! В любом случае вы увидите что-то вроде этого:

Чтобы упростить эту задачу, я буду использовать номера строк для описания частей кода. Так что скопируйте и вставьте приведенный выше код в любой редактор по вашему выбору с включенными номерами строк ИЛИ скачайте бесплатный из Интернета под названием Sublime здесь.

В любом случае у вас должен получиться код, выглядящий примерно так (все 131 строка):

Строки 13–17 и 19–21 - это библиотеки компонентов JavaScript A-Frame, используемые для достижения различных аспектов виртуальной реальности в браузере. Если они не загружаются, ничего не работает! Теперь, если у вас есть доступ к серверу в Интернете с https: // (обязательно), вы скопируете эти файлы из GitHub на свой сервер и укажете их соответственно.

OR

Поскольку это расширенная статья, если у вас нет доступа к серверу, но вы можете установить 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="Advanced Hello World, WebXR! A-frame provides a hello world that is really remarkable, however you usually need to have controller selection and environment with movement.  With this example you get what works across the most headsets and controllers.">
    <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" />
<!-- *** CHANGE THESE TO COMPONENTS ON YOUR SERVER *** -->
    <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>
<!-- for creating Navmesh... Also uncomment a-scene inspector-plugin-recast
    <script src="https://recast-api.donmccurdy.com/aframe-inspector-plugin-recast.js"></script>
    -->
<!-- The JavaScript below provides for selection and action on objects, environment and audio -->
    <script type="text/javascript">
// Audio squawk for the duck
      var squawk = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/duck.mp3');
// Function to execute onclick
      function fire_laser() {
// Make Duck Quack sound
        squawk.play();
//Disappear Duck
        document.getElementById('movingDuck').setAttribute('visible', 'false');
      }
function doCylinder() {
        
        // Bring back visibility of Duck and Cube, turn Sphere red again
        document.getElementById('movingDuck').setAttribute('visible', true);
        document.getElementById('cube').setAttribute('visible', true);
        document.getElementById('sphere').setAttribute('color', 'red');
      }
function doCube() {
        
        // Make Sphere green, disappear Cube for now
        document.getElementById('sphere').setAttribute('color', 'green');
        document.getElementById('cube').setAttribute('visible', false);      
      }
// Component to do on click.
      AFRAME.registerComponent('click-listener', {
        init: function () {
this.el.addEventListener('click', function (evt) {
// remove clicked object from view
                //this.setAttribute('visible', false);
      
            });
        }
      });
//   Solves Google Chrome mute of audio 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>
    <!-- The music sound of the following open source classical Mozart .mp3 file.  If you replace, make sure it's open source or you have your own rights. -->
    <audio id="playAudio" autoplay loop>
        <source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/MozartALittleNightMusic.mp3" type="audio/mpeg">
    </audio>
<!-- used for creating Navmesh... see javascript component above <a-scene inspector-plugin-recast>  https://github.com/donmccurdy/aframe-inspector-plugin-recast-->
<a-scene background="color: #FAFAFA">
<a-assets>
            <!-- Our font -->
            <a-asset-item id="optimerBoldFont" src="client/assets/fonts/optimer_bold.typeface.json"></a-asset-item>
<!-- Duck 3D GltF model -->
            <a-asset-item id="duck" src="client/assets/gltf/Duck.glb"></a-asset-item>
      </a-assets>
<!-- nav-mesh: protecting us from running thru sphere, cube and cylinder  -->
      <a-entity id="navmesh-Hello" gltf-model="client/assets/gltf/AdvHelloWorldnavmesh.gltf" visible="false" nav-mesh=""></a-entity>
<!-- Basic movement and teleportation  -->
      <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">
        <!-- camera -->
        <a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>
              <!-- Left Controller  -->
              <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>
              <!-- Right Controller  -->
              <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>
<!-- Normal Hello World objects modified to respond to onclick and audio  -->
      <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 >
<!-- Some 3D Text -->
      <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 track for our Duck to follow  --> 
      <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>
<!-- The track line in red  --> 
      <a-draw-curve curveref="#track" material="shader: line; color: red;"></a-draw-curve>
<!-- Our Environment -->
      <a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b;" shadow="recieve: true"></a-entity>
<!-- A light in the scene casting shadows -->
      <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 /».

Обязательные библиотеки компонентов JavaScript A-Frame

Получите библиотеки компонентов A-Frame из этих расположений GitHub и скопируйте их на свой сервер или в клиентский каталог Node.js локальных компьютеров:















Я знаю, что пришлось много настраивать, но поверьте, оно того стоит, и в итоге вы получите среду разработки для VR!

Итак, продолжим.

Теперь позвольте мне предоставить вам несколько активов. Звук утиного крика (кряк), строка 28, трехмерная модель утки (файл .glb в формате glTF), строка 89, трехмерный шрифт, строка 87, немного музыки с открытым исходным кодом, строка 81, и навигационная сетка, строка 92, которую мы войдет позже. Все они находятся в zip-файле ниже, в котором есть структура каталогов ресурсов, которая соответствует нашему примеру кода.

Https://rocketvirtual.com/A-Frame_WebXR/assets.zip

Файлы должны быть размещены на вашем сервере или в каталоге client / Node.js. Измените строки 28, 81, 87, 89 и 92, чтобы они соответствовали правильному пути для вашего сервера или настройки узла. Я должен сказать вам, что если вы не хотите включать эти файлы и изменять пути в коде, они все равно должны сойти с моего сервера в порядке. Однако они не будут такими быстрыми, как локальные, или они могут сильно пострадать, если эта статья станет очень популярной. На что я надеюсь!

Теперь мы, наконец, закончили со всей необходимой настройкой и можем перейти к дополнительным объяснениям кода.

Как насчет того, чтобы сначала поговорить о следе утки?

В некоторых симуляторах или играх все идет по циклической или повторяющейся дорожке. Компонент aframe-togetherpath позволяет объектам следовать заранее определенным путям.

Линии 112–122 определяют кривую вдоль пути своими значениями положения x, y, z. На пути id = "track" есть девять точек кривой. Это название трека на линии 112. Также обратите внимание, что каждая точка кривой имеет visible = "true". Сделаем их все видимыми = "ложь". Это приведет к исчезновению точек кривой.

Также обратите внимание на линию 124, у нас есть красная кривая, проведенная вдоль кривой curveref = "# track". Если бы мы удалили эту строку кода, изогнутая дорожка красной линией, по которой следует утка, также исчезла бы, но утка все равно следовала бы по невидимой дорожке, потому что она определяется кодом в строке 110.

Обратите внимание на атрибут в строке 110:

alongpath=”curve:#track;loop:true;dur:14000;rotate:true”

Это означает, что утка gltf-model = ”# duck” будет бесконечно повторять траекторию во время вращения. Остальная часть поворота определяется в атрибуте animation__rotate =. Утка ходит по кругу, как поездка в чайной чашке в парке развлечений, за исключением того, что там, вероятно, также меняются положения целых треков. Анимация может быть сложной задачей, поэтому мы просто представляем в этом коде простой пример.

Наконец, обратите внимание, что утка была загружена как актив между тегами ‹a-assets› и ‹/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 - означает, что утка не может получать тени, отбрасываемые на себя, это может уменьшить накладные расходы на графическую обработку, но, вероятно, не в нашем маленьком примере.

Тень означает, что утка может отбрасывать тень, скажем, на землю, что вполне естественно.

onclick = ”fire_laser ();” -значает, что при выборе утки будет выполнена эта функция JavaScript в строке 30.

cursor-listener - означает, что утка ждет, ожидая выбора. Это часть AFRAME.registerComponent (прослушиватель кликов, ... Зарегистрированные компоненты являются событием - обычно там, где мы хотим быть, но более сложными, чем нам нужно быть сейчас.

Ну, это в значительной степени объясняет утку и след, по которому она следует. А как насчет выбора утки, что происходит, когда мы это делаем? fire_laser () запускается. Строки 30–35.

function fire_laser() {
// Make Duck Quack sound
        squawk.play();
//Disappear Duck
        document.getElementById('movingDuck').setAttribute('visible', 'false');
      }

Мы проигрываем звук шарлатана, загруженный в squawk в строке 28. Затем мы делаем что-то интересное, мы заставляем утку исчезать, снова устанавливая для атрибута visible в строке 110 значение false.

Утка-невидимка продолжает кружить по дорожке, но мы ее больше не видим, графическому процессору не нужно ее рисовать. Я не уверен, выполняется ли выделение raycaster, если мы случайно нажмем на него? Я подозреваю, что нет.

Кто знает, продолжает ли утка двигаться по тропинке? И мне все равно, потому что мы снова сделаем его видимым в строке 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() {
        
        // Bring back visibility of Duck and Cube, turn Sphere red again
        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 light in the scene casting shadows -->
      <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. Если мы сделаем это ложным, оранжевые линии камеры исчезнут из поля зрения. Хорошо, потому что я поставил его, видимым для нас; просто чтобы доказать, что он существует и как им можно управлять, чтобы контролировать, где вы хотите, чтобы тени появлялись на сцене. Вы заметили, что ни один из фиолетовых грибов не отбрасывает тени?

Чтобы исправить это, мы изменили среду, чтобы иметь тени, а теневую камеру сделать немного шире, чтобы покрыть не только центр ландшафта. Получаем грибы с тенями!

<!-- Our Environment -->
      <a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b; shadow: true" shadow="recieve: true"></a-entity>
      <!-- A light in the scene casting shadows -->
      <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>

Я вижу одну теневую ошибку, когда утка движется по дорожке. Где-то в сцене есть другие теневые камеры, которые должны отбрасывать двойные тени от других источников света. Мы не будем об этом беспокоиться, потому что мы должны двигаться дальше.

Сетка навигации

Сетка навигации помогает ориентироваться в виртуальной реальности. Это один из способов ограничения управления движением и телепортации в игровой зоне. Без него вы могли бы пройти прямо в середину Сферы, Куба или Цилиндра; то, что вы не можете сделать в реальной жизни.

Создать его немного сложно. Вам нужно будет добавить, изменить и удалить некоторые вещи.

<!-- for creating Navmesh... Also uncomment 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>


Следуйте инструкциям плагина Don McCurdy’s Inspector. Войдите в Инспектор с помощью клавиш ctrl-alt-i, выберите ‹a-scene› в левом верхнем углу. Вы должны увидеть плагин, готовый для ваших параметров. Я удалил много кода, который не был объектом, который нам нужен в нашей сгенерированной навигационной сетке, например, трехмерный текст в строках 108 и 109. Я изменил cellSize на 0.11 и уменьшил dressingAmount до 1 в ENVIRONMENT, чтобы заставить его работать .

Если у вас есть файл navmesh.gltf (который я переименовал в AdvHelloWorldnavmesh.gltf), вы можете использовать его в исходном коде. См. Строку 92.

<!-- nav-mesh: protecting us from running thru sphere, cube and cylinder  -->
      <a-entity id="navmesh-Hello" gltf-model="assets/gltf/AdvHelloWorldnavmesh.gltf" visible="false" nav-mesh=""></a-entity>
<!-- Basic movement and teleportation  -->
      <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">
        <!-- camera -->
        <a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>
              <!-- Left Controller  -->
              <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 вы увидите атрибут motion-controls = ”constrainToNavMesh: true;”. Это включает navmesh. Наконец, вам нужно добавить collisionEntities: # navmesh-Hello; к средствам управления телепортом в строке 98.

Это должно сработать; вы не сможете пройти через сферу, куб и цилиндр. Попытайся.

Однако вы можете заметить, что как только вы исчезнете куб, выбрав его, он исчезнет, ​​станет невидимым, но вы по-прежнему не можете перемещаться через это пространство или телепортироваться в него. Ну, как говорится, ничего идеального нет. Чтобы исправить это, вам придется поменять местами разные навигационные сетки.

На этом мы завершаем наш Advanced Hello World для A-Frame. Надеюсь, вам понравилось это и простая виртуальная мощь A-Frame. Если вас интересуют другие примеры кода, посетите мой блог VR и подпишитесь на мою рассылку.

Удачного VR-кодирования!



ОБНОВЛЕНИЕ: статью об анимации можно найти здесь:

Парад планет в VR