Skip to content

Latest commit

 

History

History
334 lines (231 loc) · 15.2 KB

getting-started-local.md

File metadata and controls

334 lines (231 loc) · 15.2 KB

Build your first multi-user virtual reality experience (on the web)

Hello friends! This tutorial will show you how to write your very first multi-user virtual reality experience on the web.

You might be thinking: wait you can do VR on the web? And the answer is yes! Using the WebVR standard and an awesome library called A-Frame, VR on the web is actually really easy and evolving rapidly. Your second question is then: wait to do multi-user won't I have to know all about servers and complicated networking protocols? Answer: not anymore! The recently released Networked-Aframe (NAF for short) has you covered. NAF hides away a lot of the networking complexity, allowing you to write multi-user VR apps entirely in HTML.

Let's dive in:

Required Tools

We'll need the following tools:

  • NodeJS
  • NPM

If they aren't already installed, follow these quick tutorials to get started: NodeJS for Windows or NodeJS for MacOS. NPM will be installed by default when you install NodeJS via these methods.

Install Dependencies

Create a new folder called naf-tutorial and open it.

Now let's setup the required dependencies. Create a file called package.json and add the following text:

{
  "name": "naf-tutorial",
  "version": "1.0.0",
  "description": "My first multi-user virtual reality",
  "scripts": {
    "start": "node ./server/server.js"
  },
  "author": "YOUR_NAME",
  "dependencies": {
    "networked-aframe": "^0.3.0"
  }
}

This file is how NPM knows which dependencies to install and will let us use some handy shortcuts later on.

Now let's install the dependencies. Open up the command line prompt of your choosing and navigate to your new naf-tutorial folder. Then run:

npm install

You'll notice a new folder called node_modules which contains all the dependencies for networked-aframe.

Setup the Server

Let's copy the example server that comes with networked-aframe into our project. From your project's root, run the respective command for your OS:

# MacOS
cp -r ./node_modules/networked-aframe/server/ ./server/

# Windows
robocopy .\node_modules\networked-aframe\server\ .\server\ /e

You'll now see another new folder: server/. Inside it you'll see the static folder which contains the networked-aframe examples. You'll also see server.js which is the the server for managing the network connections and hosting the static files.

Let's run the server and start playing with the examples. From your project's root run:

node ./server/server.js

You'll notice the package.json also has a shortcut to start the server that you can use by running:

npm start

Now that the server's running you can fire up your favorite web browser (as long as its the latest Chrome or Firefox) and enter this URL:

http://localhost:8080

You should see an index page that shows a list of the included examples.

To see the networking in action, open up two browser windows and go to the same example on both. Click and hold with the mouse to move your avatar's head around. The other browser should see your avatar's movements.

If at any point in this tutorial you don't see the same results, or you run into an unknown error, don't hesitate from posting a question on the networked-aframe issues page. I'm sure other people will run into the same issues and greatly appreciate the conversation your question will spark.

Create an Example from Scratch

So we've seen how to get your own local server up and running with the default examples. Let's have a go at making our own example from scratch.

In the naf-tutorial/server/static/ folder, create a new file and name it my-example.html.

Here's the template we'll start with:

<html>
  <head>
    <script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js"></script>
    <script src="easyrtc/easyrtc.js"></script>
    <script src="https://unpkg.com/networked-aframe/dist/networked-aframe.min.js"></script>
  </head>
  <body>
    <a-scene></a-scene>
  </body>
</html>

We can see that all the NAF dependencies are included; aframe, socket.io, EasyRTC and Networked-Aframe itself. Copy this template into my-example.html. To check the dependencies are setup correctly, start the server by running npm start and then head to localhost:8080/my-example.html. You should see a blank white page and no Javascript errors in the Developer Console. There'll probably be a bunch of mumbo jumbo in the dev console showing the current versions of each library.

First up we need to add the networked-scene component to the A-Frame a-scene tag.

Modify the <a-scene> tag like so:

<a-scene networked-scene="
  app: myApp;
  room: room1;
  debug: true;
"></a-scene>

Now when you open two tabs at localhost:8080/my-example.html you should see the following logs, indicating a data channel has been established between the two clients:

To see the full list of options your app with other options check out the documentation on the networked-scene component.

Create an Avatar

NAF uses a template system to make it easy to define objects to sync across all users. Lets sync our avatar's head and add a template that other clients will see in place of our head.

Templates are added in A-Frame's <a-assets> tag. Let's add the assets tag with a simple avatar at the top of the a-scene tag:

<a-assets>
  <script id="avatar-template" type="text/html">
    <a-entity class="avatar">
      <a-sphere class="head"
        color="#5985ff"
        scale="0.45 0.5 0.4"
      ></a-sphere>
      <a-entity class="face"
        position="0 0.05 0"
      >
        <a-sphere class="eye"
          color="#efefef"
          position="0.16 0.1 -0.35"
          scale="0.12 0.12 0.12"
        >
          <a-sphere class="pupil"
            color="#000"
            position="0 0 -1"
            scale="0.2 0.2 0.2"
          ></a-sphere>
        </a-sphere>
        <a-sphere class="eye"
          color="#efefef"
          position="-0.16 0.1 -0.35"
          scale="0.12 0.12 0.12"
        >
          <a-sphere class="pupil"
            color="#000"
            position="0 0 -1"
            scale="0.2 0.2 0.2"
          ></a-sphere>
        </a-sphere>
      </a-entity>
    </a-entity>
  </script>
</a-assets>

<a-entity id="player" networked="template:#avatar-template;showLocalTemplate:false;" camera position="0 1.3 0" wasd-controls look-controls></a-entity>

Refresh both browser tabs and move with the arrow keys. Once connected you should see an avatar appear for the other user.

The avatars appear in the exact same position: '0 1.3 0' which is not a great experience when in social VR. Virtual Personal Space is a thing! So next step is to choose a better spot to spawn the avatar. It's tricky to know where other users are before you've spawned the avatar (since you don't know yet when you've connected to all users), however we can improve the experience a lot by choosing a random point on a circle, and praying to RNGesus that the random number generator doesn't spawn you close so other users. If you're looking for better support for spawn collision avoidance let me know and we'll work something out.

Adds this script to your <head> tag:

<script>
AFRAME.registerComponent('spawn-in-circle', {
  schema: {
    radius: {type: 'number', default: 1}
  },

  init: function() {
    var el = this.el;
    var center = el.getAttribute('position');

    var angleRad = this.getRandomAngleInRadians();
    var circlePoint = this.randomPointOnCircle(this.data.radius, angleRad);
    var worldPoint = {x: circlePoint.x + center.x, y: center.y, z: circlePoint.y + center.z};
    el.setAttribute('position', worldPoint);

    var angleDeg = angleRad * 180 / Math.PI;
    var angleToCenter = -1 * angleDeg + 90;
    var rotationStr = '0 ' + angleToCenter + ' 0';
    el.setAttribute('rotation', rotationStr);
  },

  getRandomAngleInRadians: function() {
    return Math.random()*Math.PI*2;
  },

  randomPointOnCircle: function (radius, angleRad) {
    x = Math.cos(angleRad)*radius;
    y = Math.sin(angleRad)*radius;
    return {x: x, y: y};
  }
});
</script>

Then add the spawn-in-circle component to your player, like this:

<a-entity id="player" networked="template:#avatar-template;showLocalTemplate:false;" camera position="0 1.3 0" spawn-in-circle="radius:3;" wasd-controls look-controls></a-entity>

Refresh those browsers and your scene should be starting to take shape (albeit with a completely white environment).

Environment

To spruce up your world add the following HTML tags:

<!-- Add to bottom of the a-assets tag -->
<img id="grid" src="https://img.gs/bbdkhfbzkk/stretch/https://i.imgur.com/25P1geh.png" crossorigin="anonymous">
<img id="sky" src="https://img.gs/bbdkhfbzkk/2048x2048,stretch/http://i.imgur.com/WqlqEkq.jpg" crossorigin="anonymous" />

<!-- Add to bottom of a-scene tag -->
<a-entity position="0 0 0"
  geometry="primitive: plane; width: 10000; height: 10000;" rotation="-90 0 0"
  material="src: #grid; repeat: 10000 10000; transparent: true; metalness:0.6; roughness: 0.4; sphericalEnvMap: #sky;"></a-entity>

<a-entity light="color: #ccccff; intensity: 1; type: ambient;" visible=""></a-entity>
<a-entity light="color: #ffaaff; intensity: 1.5" position="5 5 5"></a-entity>

<a-sky src="#sky" rotation="0 -90 0"></a-sky>

Things should be looking a little more Tron-like now.

At this point your example will be very close to the included basic.html example, so if you've run into any problems check out the basic example source code html & js for a working reference.

Advanced Features

Now you have a basic networked WebVR scene up and running. But there's a lot more that Networked-Aframe can do. The next section will take a look at some of the advanced features that give you a lot more control over your experience.

Hand Controllers

To add hand controllers follow the tracked controllers example.

WebRTC

By default NAF uses WebSockets to send packets to other users. This follows a classic client-server architecture and uses the TCP network protocol. If you'd prefer to use a peer-to-peer architecture and would like to use the UDP network protocol you should use WebRTC. In order to enable it in NAF set the webrtc property of the networked-scene component to true. This also allows for voice chat (see below).

Voice Chat / Audio Streaming

NAF has built in voice chat when you're using WebRTC. Change webrtc and webrtcAudio properties of the networked-scene component to true and your users will be able to speak to each other. This is a little hard to test locally because the audio feedback will destroy your ears, so try it with headphones and you'll hear your voice being echoed back to you without the feedback. Note: in order for audio streaming to work on a hosted server you'll need to be using HTTPS. I'm planning on writing a follow-up tutorial to this one that will explain how to deploy NAF to a live server, including how to setup HTTPS really easily using Certbot.

Syncing Custom Components

Components are synchronized by checking the data property provided by A-Frame on a network 'tick'. How quickly this tick happens can be defined in the NAF Options, but the default is 15 times per second. On each tick the data property is checked against its previous value, and if it changed it's sent over the network to the other users.

So how do we choose which components to sync? By default, the position and rotation components are synced but NAF lets you specify any component that you wish to sync, included child components found in the deep depths of your templates.

To define synced components we introduce the concept of a NAF schema. These schemas are basic Javascript objects that either define just the component's name if the component is at the root level of the entity, or they define a CSS selector and the component name if the component is on a child entity.

Here's an example schema that syncs the position and rotation of the root entity, while syncing the color of the avatar's head:

NAF.schemas.add({
  template: '#avatar-template',
  components: [
    'position',
    'rotation',
    {
      selector: '.head',
      component: 'material',
      property: 'color'
    }
  ]
});

In the basic.html example this is used in combination with ngokevin's Randomizer component to give each avatar's head a synced randomized color.

To add the randomizer component include this script in the <head> section:

<script src="https://unpkg.com/aframe-randomizer-components@^3.0.1/dist/aframe-randomizer-components.min.js"></script>

and change the avatar template's head tag to use the random-color component:

<a-sphere class="head"
  scale="0.45 0.5 0.4"
  random-color
></a-sphere>

Deleting entities

In order to delete a network entity the user that created the entity can simply delete the HTML element from the scene using regular DOM APIs. NAF then handles syncing the deletion under the hood. Currently only the user who created the entity can delete it, but this will be relaxed in the future when support for non-owner deletion is added to NAF.

The shooter.html example shows an example where the bullets are deleted after 5 seconds of being spawned.

That's all folks!

And there you have it. Your very own multi-user virtual reality experience running in a web browser!

Check out the Networked-Aframe documentation for more features and help. And again, be sure to post any questions on the GitHub Issues page or feel free to message me directly on twitter.

I would love love love you to send me cool examples you've made, and I'm looking to include more default examples with credit to the author, so let me know what you make!

@HaydenLee37