Build a Virtual World with User Input

Milecia

Working with virtual reality (VR) has typically involved building apps and worlds using tools like Unity and Unreal Engine and headsets. You don't need to have all of these things if you know some basic HTML and JavaScript. You can build and test your own virtual world directly in the browser without a headset.

We're going to build a simple VR app using the A-frame library. Any apps you build with this can be developed in the browser and used on any of the popular headsets. This project is going to be different from a lot of other projects because we aren't going to use a JavaScript framework. This will just be an HTML file and a JavaScript file.

Make the VR world

This little VR world will consist of a few boxes falling from the sky that let you change the material for a robot figure. Let's start by creating a new folder called virtual-world. Inside this folder, add a file called world.html and another file called change-materials.js. Open the world.html file and add the following code.

1<html>
2 <head>
3 <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
4 </head>
5 <body>
6 <a-scene background="color: #7BFAFA"> </a-scene>
7 </body>
8</html>

This sets up the HTML file to use A-frame and we have a scene in the <body> to get things started. The way we use A-frame elements is very similar to working with regular HTML elements. They all have attributes we can add to get different behaviors and looks. In <a-scene>, there's a background attribute that sets the color for the entire scene or world.

Now we need a way to look around and interact with the world, so we'll add a camera for the user.

Add the camera and marker

Knowing where you are in a virtual world is the only way you can move around and do things. That's why the camera placement and having an indicator for where the user is looking are important. Add the following code just above the closing <a-scene> tag.

1<!-- set the camera for the user -->
2<a-camera look-controls id="world-camera" position="0 3 0">
3 <a-entity
4 cursor="fuse: true; fuseTimeout: 500"
5 position="0 0 -1"
6 geometry="primitive: ring; radiusInner: 0.01; radiusOuter: 0.03"
7 material="color: black; shader: flat"
8 id="marker"
9 ></a-entity>
10</a-camera>

This tells A-frame that the camera is being used for the look-controls that let us do things in the world. It also has a cursor as part of the camera so the user can always see where their focus is. Since we have the scene and the camera in place now, we need to put something in the world. So let's start with the boxes falling out of the sky.

Add material selection boxes

There are going to be a couple of boxes that let us decide whether the robot should be made out of wood or metal. We'll use some images we have on Cloudinary as the basis for the materials. Add the following code inside of the <a-scene> tags.

1<!-- assets for the scene -->
2<a-assets>
3 <img
4 id="woodTexture"
5 src="https://res.cloudinary.com/milecia/image/upload/v1653756834/wood_vuxmb7.jpg"
6 crossorigin="anonymous"
7 />
8 <img
9 id="metalTexture"
10 src="https://res.cloudinary.com/milecia/image/upload/v1653776620/metal_ehet1g.jpg"
11 crossorigin="anonymous"
12 />
13</a-assets>

Any time you want to add new textures, audio, or even 3D objects to your world, you'll have to add them as assets at the top of your scene. Here, we have 2 images as part of our assets to define the metal and wood materials for the robot. Now we can create the boxes that show off those materials. Add the following code below the assets.

1<!-- boxes that drop in -->
2<a-box
3 id="woodBox"
4 position="-1 6 -3"
5 rotation="0 45 0"
6 src="#woodTexture"
7 animation__position="property: object3D.position.y; to: 1;"
8></a-box>
9<a-box
10 id="metalBox"
11 position="2 6 -3"
12 src="#metalTexture"
13 animation__position="property: object3D.position.y; to: 1;"
14></a-box>
15<a-plane
16 position="0 0 -5"
17 rotation="-90 0 0"
18 width="60"
19 height="60"
20 color="#7C992B"
21></a-plane>

We've just added 2 boxes with src attributes that reference the image assets we pulled from Cloudinary. They both have the animation__position attribute which is what defines how they fall to the ground. Take note that we've created IDs for these boxes because we'll be adding click events to them.

Finally, there's a plane that defines the ground the entire world is based on. Now if you open the world.html file in a browser, you should see something like this.

Build the robot

We've built an environment for things to exist in and we have a way to update textures to wood or metal. Now it's finally time to build the robot. Below the <a-plane> tag, add the following code.

1<!-- robot right leg -->
2<a-box
3 class="robot"
4 position="2 1.25 -5"
5 height="2.5"
6 width="0.75"
7 depth="0.75"
8 color="#bcc9b5"
9 shadow
10></a-box>
11<!-- robot left leg -->
12<a-box
13 class="robot"
14 position="0.25 1.25 -5"
15 height="2.5"
16 width="0.75"
17 depth="0.75"
18 color="#bcc9b5"
19 shadow
20></a-box>
21<!-- robot waist -->
22<a-box
23 class="robot"
24 position="1.15 2.5 -5"
25 height="0.5"
26 width="2.5"
27 depth="0.75"
28 color="#bcc9b5"
29 shadow
30></a-box>
31<!-- robot body -->
32<a-box
33 class="robot"
34 position="1 3.75 -5"
35 height="3"
36 width="1"
37 depth="1"
38 color="#bcc9b5"
39 shadow
40></a-box>
41<!-- robot head -->
42<a-box
43 class="robot"
44 position="1 5.75 -5"
45 height="1.25"
46 width="1.75"
47 depth="1.25"
48 color="#bcc9b5"
49 shadow
50></a-box>
51<!-- base of robot right arm -->
52<a-cylinder
53 class="robot"
54 position="2.25 4.25 -5"
55 radius="0.35"
56 height="2"
57 color="#bcc9b5"
58 segments-radial="6"
59 rotation="0 0 90"
60></a-cylinder>
61<!-- second part of robot right arm -->
62<a-cylinder
63 class="robot"
64 position="3.8 3.75 -5"
65 radius="0.35"
66 height="2"
67 color="#bcc9b5"
68 segments-radial="6"
69 rotation="0 0 45"
70></a-cylinder>
71<!-- base of robot left arm -->
72<a-cylinder
73 class="robot"
74 position="-0.25 4.25 -5"
75 radius="0.35"
76 height="2"
77 color="#bcc9b5"
78 segments-radial="6"
79 rotation="0 0 90"
80></a-cylinder>
81<!-- second part of robot left arm -->
82<a-cylinder
83 class="robot"
84 position="-1.8 5 -5"
85 radius="0.35"
86 height="2"
87 color="#bcc9b5"
88 segments-radial="6"
89 rotation="0 0 45"
90></a-cylinder>

We're using a lot of A-frame primitive shapes to make this robot so you can have a lot of fun with this later. You might also consider making a 3D object in Blender or you might try some of the free 3D objects here or here. We'll keep it pretty basic here with just the built-in A-frame primitives.

All of the shapes have essentially the same attributes, just changed to handle the positioning of robot parts correctly. Once you have all of the robot code in place, you should see something like this when you refresh the page.

All that's left is implementing the functionality to update the robot's material when one of the boxes is selected.

Add an A-frame component

We'll update the robot's material through an A-frame component. Open the change-materials.js file and add the following code.

1AFRAME.registerComponent("change-materials", {
2 init: () => {
3 const woodBox = document.querySelector("#woodBox");
4 const metalBox = document.querySelector("#metalBox");
5 const robotBody = document.querySelectorAll(".robot");
6
7 woodBox.addEventListener("click", () => {
8 robotBody.forEach((part) => {
9 part.setAttribute("src", "#woodTexture");
10 });
11 });
12
13 metalBox.addEventListener("click", () => {
14 robotBody.forEach((part) => {
15 part.setAttribute("src", "#metalTexture");
16 });
17 });
18 },
19});

This creates a new A-frame component called change-materials that we can reference on elements in the scene. In the init function, we're getting the elements in the scene based on their IDs and class. This is much like we would do in a regular web app. Next, we add some click event listeners to the metal and wood boxes that will update the src for each part of the robot's body.

We do need to add references to this change-materials component in the HTML file. Add the following import to the <head> tag.

1<script src="change-materials.js"></script>

Now we have the A-frame component accessible to the scene. We'll need to add this as an attribute to the wood and metal boxes. Update those elements like this.

1<a-box
2 id="woodBox"
3 position="-1 6 -3"
4 rotation="0 45 0"
5 src="#woodTexture"
6 animation__position="property: object3D.position.y; to: 1;"
7 change-materials
8></a-box>
9<a-box
10 id="metalBox"
11 position="2 6 -3"
12 src="#metalTexture"
13 animation__position="property: object3D.position.y; to: 1;"
14 change-materials
15></a-box>

The only difference is that now these boxes have the change-materials attribute. This is how A-frame works with components so now these elements will have the correct event listeners. If you reload the page and move the marker over one of the boxes, your robot should look like one of these.

That's all! Now you can add more assets or more user interactions. If you have a VR headset, I'd encourage you to try it out there as well!

Finished Code

You can find the code for this project in the virtual-world folder of this repo or you can check it out in this Code Sandbox.

Conclusion

Working with VR takes a number of different skills which is part of the fun of it. From building assets, deciding on textures, and creating a whole experience for users, you can go in a lot of different directions. This is another creative way to practice your JavaScript or test out a new career path.

Milecia

Software Team Lead

Milecia is a senior software engineer, international tech speaker, and mad scientist that works with hardware and software. She will try to make anything with JavaScript first.