Minecraft in WebVR with HTML Using A-Frame

Share this:

I'm Kevin Ngo, a virtual reality web developer on the Mozilla VR team and a core developer of A-Frame. Today, we'll go over how to build a room scale WebVR Minecraft demo that works on HTC Vive, Oculus Rift, Samsung GearVR, Google Cardboard, desktop, and mobile. The demo will be built with A-Frame in just 11 HTML elements!

A-Frame

A few years ago, Mozilla invented and pioneered WebVR, a JavaScript API for creating immersive VR experiences in your browser, in an experimental build of Firefox. Since then, WebVR has gained wide support from other companies such as Google, Microsoft, Samsung, and Oculus. And it's now set to release enabled by default in Firefox within a few months!

Why WebVR? The Web brings openness to VR; on the Web, content isn't controlled by gatekeepers, and users aren't kept in walled gardens. The Web also brings connectedness to VR; on the Web, we'll be able to traverse from world to world just as we today jump from page to page with links. With WebGL having matured with and new specifications such as Web Assembly and Service Workers, the Web is ready for VR.

Mozilla VR team created A-Frame to kickstart the WebVR ecosystem, giving web developers the power to build 3D and VR worlds.

A-Frame Homepage

A-Frame is a web framework for building virtual reality experiences. A-Frame is based on HTML and the Entity-Component pattern. HTML is the most accessible language in all of computing, making it possible for anyone to get started. Below is a complete 3D and VR scene in just HTML that runs across multiple VR platforms as well as desktop and mobile:

That's it! Specifying just one line of HTML (<a-scene>) will take care of all the 3D and VR boilerplate: canvas, scene, renderer, render loop, camera, and lights. Then we add objects via child elements to the scene. No build steps, just a copy-and-paste-friendly HTML file.

And we can dynamically query and manipulate A-Frame's HTML the same as we would with standard JavaScript and DOM APIs (e.g., querySelector, getAttribute, addEventListener, setAttribute):

And since it's just HTML and JavaScript, A-Frame works with many existing frameworks and libraries:

Works with d3, Vue, React, Redux, jQuery, Angular

Although A-Frame's HTML looks easy, A-Frame's API is much more powerful than simply making 3D declarative. A-Frame is an entity-component-system (ECS) framework. Notably used by Unity, ECS is a pattern popular in game development. The concept follows:

All objects in the scene are entities, an empty object that doesn't do anything on its own, analogous to an empty <div>. In A-Frame, entities are represented in the DOM as an element.

Then we plug components into those entities to provide appearance, behavior, and functionality. In A-Frame, components are registered in JavaScript and can be made to do anything. They have full access to three.js and DOM APIs. Components can be attached to entities in HTML after they are registered.

The advantage of ECS is that it's composable; we can mix and match these reusable components to build more complex 3D objects. A-Frame takes it up a notch and makes it declarative and part of the DOM as we'll see in the Minecraft example.

Example Skeleton

Now to our demo. We'll be building a basic VR voxel builder. The voxel builder will be primarily for room scale VR with positional tracking and tracked controllers (e.g., HTC Vive, Oculus Rift + Touch). We'll also make it work on desktop and mobile, but the experience won't be as compelling.

Adding a Ground

<a-plane> and <a-circle> are basic primitives that are commonly used for adding a ground. We'll be using <a-cylinder> to better work with the raycasters our controllers will be using. The cylinder will have a radius of 30 meters to match the radius of the sky we'll add later. Note that A-Frame units are in meters to match the real-world units returned from the WebVR API.

The texture of the ground we'll be using is hosted at `https://cdn.aframe.io/a-painter/images/floor.jpg`. We'll add the texture to our assets, and create a thin cylinder entity pointing to that texture:

Preloading Assets

Specifying a URL via the src attribute will load the texture at runtime.

Since network requests can negatively impact render performance, we can preload the texture such that the scene doesn't start rendering until its assets have been fetched. We can do this using the asset management system.

We place <a-assets> into our <a-scene>, place assets (e.g., images, videos, models, sounds) into <a-assets>, and point to them from our entities via a selector (e.g., #myTexture).

Let's move our ground texture to <a-assets> to be preloaded using an <img> element:

Adding a Background

Let's add a 360° background to our <a-scene> with the <a-sky> element. <a-sky> is a large 3D sphere with a material mapped on the inside. Just like a normal image, <a-sky> can take an image path with src. This ultimately lets us do immersive 360° images with one line of HTML. As an exercise later, try using some 360° images from Flickr's
equirectangular pool.

We could add a plain color background (e.g., <a-sky color="#333"></a-sky>) or a gradient, but let's add a textured background with an image. The image we're using is hosted at `https://cdn.aframe.io/a-painter/images/sky.jpg`. The image texture we are using covers semi-sphere so we'll chop our sphere in half with theta-length="90", and we'll give our sphere a radius of 30 meters to match the ground:

Adding Voxels

Voxels in our VR application will be like <a-box> but attached with a few custom A-Frame components. But first let's go over the entity-component pattern. Let's see how the easy-to-use primitives such as <a-box> are composed under the hood.

This section will later do a deeper dive into the implementation of a couple A-Frame components. In practice though, we'd often get to use components via HTML already written by A-Frame community developers rather than building them from scratch.

Entity-Component Pattern

Every single object in an A-Frame scene is <a-entity>, which by itself doesn't do anything, like an empty <div>. We plug in components (not to be confused with Web or React Components) to that entity to provide with appearance, behavior, and logic.

For a box, we attach and configure A-Frame's basic geometry and material components. Components are represented as HTML attributes, and component properties are defined like CSS styles by default. Here's what <a-box> looks like decomposed to its fundamental components. <a-box> wraps the components:

The benefit of components is that they are composable. We can mix and match from a bunch of existing components to construct different types of objects.

In 3D development, the possible types of objects we construct are infinite in number and complexity, and we need an easy way of defining new types of objects rather than through traditional inheritance. Contrast this to the 2D web where we develop with a small pool of fixed HTML elements and plop them into a hierarchy.

Random Color Component

Components in A-Frame are defined in JavaScript, and they have full access to three.js and DOM APIs; they can do anything. We define all of our objects as a bundle of components.

We'll put the pattern to action by writing an A-Frame component to set a random color on our box. Components are registered with AFRAME.registerComponent. We can define a schema, (the component's data) and lifecycle handler methods (the component's logic). For the random color component, we'll won't be setting a schema since it won't be configurable. But we will define the init handler, which is called exactly once when the component is attached:

After the component is registered, we can attach this component straight from HTML. All code written within A-Frame's framework is extending HTML, and those extensions can be used on other objects and in other scenes. The beautiful thing is that a developer could write a component that adds physics to an object, and then someone that doesn't even know JavaScript could add
physics to their scene!

Take our box entity from earlier, we attach the random-color HTML attribute to plug in the random-color component. We'll save the component as a JS file and include it before the scene:

Components can be plugged into on any entity without having to create or extend a class like we'd have to in traditional inheritance. If we wanted to attach it to say, <a-sphere> or <a-obj-model>, we could!

if we wanted to share this component for other people to use, we could too. We curate from many handy components from the ecosystem at the A-Frame Registry, similar to the Unity Asset Store. If we developed our application using components, all our code is inherently modular and reusable!

Snap Component

We'll have a snap component to snap our boxes to a grid so they aren't overlapping. We won't get into the details of how this component is implemented, but you can check out the snap component's source code (20 lines of JavaScript).

We attach the snap component to our box so that it snaps to every half meter, also with an offset to center the box:

We'll be using hand-controls which abstracts and works with both Vive and Rift controls, providing models of basic hand. We'll make the left hand responsible for teleporting around and the right hand responsible for spawning and placing blocks.

Adding Teleportation to the Left Hand

We'll plug in teleportation capabilities to the left hand such that we hold a button to show an arc coming out of the controller, and let go of the button to teleport to the end of the arc. Before, we wrote our own A-Frame components.

But we can also use open source components already made from the community and just use them straight from HTML!

For teleportation, there's a teleport-controls component by @fernandojsg. Following the README, we add the component via a <script> tag and just set the teleport-controls component on the controller on the entity:

Then we'll configure the teleport-controls component to use an arc type of teleportation. By default, teleport-controls will only teleport on the ground, but we can specify with collisionEntities to teleport on the blocks and the ground using selectors. These properties are part of the API that the teleport-controls component was created with:

That's it! One script tag and one HTML attribute and we can teleport. For more cool components, check out the A-Frame Registry.

Adding Voxel Spawner to the Right Hand

In WebVR, the ability to click on objects isn't built-in as it is in 2D applications. We have to provide that ourselves. Fortunately, A-Frame has many components to handle interaction. A common method for cursor-like clicking in VR is to use a raycaster, a laser that shoots out and returns objects that it intersects with. Then we implement the cursor states by listening to interaction events and checking the raycaster for intersections.

A-Frame provides a gaze-based cursor for clicking by looking at objects, but there's also a controller-cursor component available that attaches the clicking laser to VR tracked controllers. Like the teleport-controls component, we include the script tag and attach the controller-cursor component. This time to the right hand:

Now when we pull the trigger button on the tracked controllers, controller-cursor will emit a click event on both the controller and the entity it is intersecting at the time. Events such as mouseenter, mouseleave are also provided. The event contains details about the intersection.

That provides us with the ability to click, but we'll have to wire up some code to handle those clicks to spawn blocks. We can use an event listener and document.createElement:

document.querySelector('#blockHand').addEventListener(`click`, function (evt) {
// Create a blank entity.
var newVoxelEl = document.createElement('a-entity');
// Use the mixin to make it a voxel.
newVoxelEl.setAttribute('mixin', 'voxel');
// Set the position using intersection point. The `snap` component above which
// is part of the mixin will snap it to the closest half meter.
newVoxelEl.setAttribute('position', evt.detail.intersection.point);
// Add to the scene with `appendChild`.
this.appendChild(newVoxelEl);
});

To generalize creating entities from an intersection event, we've created an intersection-spawn component that can be configured with any event and list of properties. We won't go into the detail of the implementation, but you can check out the simple intersection-spawn component source code on GitHub. We attach intersection-spawn capabilities to the right hand:

Adding Support for Mobile and Desktop

We see how we've built a custom type of object (i.e., a tracked hand controller with a hand model that has click capabilities and spawns blocks on click) by mixing together components. The wonderful thing with components is that they are reusable in other contexts. We could even attach the intersection-spawn component with the gaze-based cursor component so that we can also spawn blocks on mobile and desktop, without changing a thing about the component!

Try It Out!

Our VR voxel builder is ultimately 11 HTML elements. We can preview it on desktop and mobile. On desktop, we can drag and click to spawn blacks. On mobile, we can pan the device around and tap to spawn blocks.

A-Frame is the WebVR framework for innovating what VR on the Web will look like while making it easy for anyone to get involved developing with 3D and VR. HTML keeps it dead simple while being fed by the Entity-Component pattern underneath. These experimentations will be the foundation of the open Metaverse, shared persistent connected virtual spaces that will be inhabited by everyone.

It's the early stages of VR and especially for WebVR, but browsers soon shipping WebVR enabled by default to hundreds of millions, web developers should get involved to make VR accessible to everyone.

I actually had an A-Frame VR site at my own wedding, photos of my wife and I surrounding the user, presented via Google Cardboard.

I know a lot of people and agencies attract clients and web development work by doing future-facing experiments with WebVR. Organizations such as Amnesty International UK, NPR, The Washington Post have used A-Frame for some featured articles.

Keven, how did you deal with performance on mobile? What phone did you use? I tried a quick test with a-frame and just a 360 photo on an “a-sky” element. On my iPhone 6 and HTC MOne 8, the performance was really bad. I wouldn’t want people at a wedding barfing from motion sickness :)

We’re more focused on desktop (i.e., “real”) VR. From there you can achieve really low latency and 90+ fps.

But mobile is…different. Cardboard is great for conversational, quickly show-off, but it’s not something you’d actually be inside. No one would get sick zecause you’re just holding it up to your face for like 10 seconds max before you get out because polyfilled/Cardboard VR is not compelling enough to hold attention.

Because it’s polyfilled using Device Motion sensors, the performance will be bad because at least Android throttles that data, and iOS Safari is iOS Safari.

I was asking specifically about what you did for your wedding… you said you used Google Cardboard. I’ve done some commercial projects with Google Cardboard and they held attention for several minutes. I had to use Unity to make native apps to get usable performance. I’m hoping Web VR on mobile can get to that point soon.

The latest version of Chrome on Android doesn’t require the polyfill, but I didn’t notice any performance improvement compared to the previous version.

This comment thread is closed. If you have important information to share, please contact us.

Related

How do you stay up to date in this fast⁠-⁠moving industry?

A good start is to sign up for our weekly hand-written newsletter. We bring you the best articles and ideas from around the web, and what we think about them.

👋

CSS-Tricks* is created, written by, and maintained by Chris Coyier and a team of swell people. It is built on WordPress and powered up by Jetpack. It is made possible through sponsorships from products and services we like.