Make a WebVR Ball Pit with A-Frame Physics

Kelly Lougheed
8 min readJul 31, 2018

--

This tutorial will show you how to make a ball pit using WebVR with A-Frame and the physics extras in less than 20 lines of HTML and JavaScript (each)!

Prior knowledge of A-Frame or JavaScript (ES6+) helps, but is not required. Basic familiarity with HTML is assumed.

If you get stuck at any time, feel free to check out the solution code on Glitch!

Setup

Go to the A-Frame starter code on Glitch and find the button below the project preview on the lower right that says “Remix your own.” Click this button.

Navigate to index.html in the file tree. You will see the following HTML-like code:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello, WebVR! • A-Frame</title>
<meta name="description" content="Hello, WebVR! • A-Frame">
<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
</head>
<body>
<a-scene background="color: #ECECEC">
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
</a-scene>
</body>
</html>

The A-Frame library is linked at the top of this code and allows us to create WebVR scenes and shapes with the a prefix. As you might imagine, a-box creates a box, a-sphere creates a sphere, and a-cylinder creates a cylinder in WebVR. Since these shapes are pretty basic, we call them primitives.

To view the scene, press the “Show Live” button in the top left corner of your screen:

Then you should see:

If you navigate through the scene with the arrow keys, you’ll notice you can walk through the shapes. Wild!

We’ll be seeing a-sphere again, but right now, we’re actually going to gut everything inside a-scene (and delete its background color).

Replace the default code with the following (gutted) code:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello, WebVR! • A-Frame</title>
<meta name="description" content="Hello, WebVR! • A-Frame">
<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
</head>
<body>
<a-scene>

</a-scene>
</body>
</html>

Find the opening and closing a-scene tags. We’ll be coding our A-Frame scene inside there!

Adding physics

My ball pit is going to involve a lot of bouncing balls — but the default A-Frame spheres, boxes, and cylinders don’t obey the laws of physics. (Remember how you could walk through the shapes?)

To add physics, we just need to add this script link to an “extras” library. Add it right BELOW your link to the A-Frame library inside the head tags:

<script src="//cdn.rawgit.com/donmccurdy/aframe-extras/v3.13.1/dist/aframe-extras.min.js"></script>

Now, with this “extras” library that gives us physics, we can specify an A-Frame primitive as a static-body or dynamic-body (or kinematic-body, but we won’t go there today). A static body CANNOT be walked through (it is solid), and a dynamic body will bounce and roll like a real-life ball, obeying the laws of gravity!

Setting the scene

Before we add in our dozens of bouncing balls, we want to set the scene. My balls will be multi-colored, so I just want a plain grey backdrop. Add this code to your scene, inside the a-scene tags:

<a-sky color="#eee"></a-sky>

You’ll notice the backdrop becomes a light grey.

A light grey background

Now, I want to give the balls some kind of floor to bounce on. Add this code into your scene:

<a-plane static-body position="0 0 0" rotation="-90 0 0" width="30" height="30" color="black" material="transparent: true; opacity: 0.2"></a-plane>

I’m adding a 30x30 floor (rotated at -90 degrees on the x-axis so it’s visible) that’s a static body: it’s solid, and balls will bounce off it. I’m also making it semi-transparent because I happen to know that the balls will roll off the ends of the floor, and I want to see them plummet downwards!

A light grey background with a darker grey floor

Now, do I add 75 or so a-sphere objects? No! That wouldn’t be efficient. Instead, I will be adding those spheres dynamically with JavaScript (more on that soon). For now, I just want to add an empty container to hold all the spheres. It’s invisible, but very important. Add this code into your scene:

<a-entity id="container"></a-entity>

Again, you won’t see anything as a result of code, but it’s very important.

Adding JavaScript

Before I create a new file to hold my JavaScript, I’m going to preemptively link it into my HTML. I’ll be using this JavaScript to manipulate the contents of my HTML file (which holds my A-Frame scene), so it’s important that I link the JavaScript below all the A-Frame content.

Right before the closing body tag </body>, add in this link to your (so far nonexistent) script:

<script src="script.js"></script>

OK, now we can actually create the script file. Click on “New File” in your file tree and call it “script.js”. Then click “Add File.”

In your script.js file, add the following line:

console.log("A-Frame rocks!")

Now press “Show Live” to preview your A-Frame scene and pop open the developer console. On Google Chrome, go to “View,” then “Developer,” then “JavaScript Console.” On Chrome on a Mac, the keyboard shortcut is alt-command-j.

When the console opens, you should see the message you just logged:

Check out the last message: “A-Frame rocks!”

If the message doesn’t appear, you may have made an error linking up your JavaScript with the script tag — check for typos. Otherwise, proceed!

Adding balls dynamically

We want to put all our balls inside that a-entity with the id of container in our HTML file. To accomplish this with JavaScript, we first need to select that a-entity element and store it in a variable. (A variable is a named container for a value.)

Add this code to your JavaScript file (below the console.log is fine):

let container = document.querySelector("#container")

Now whenever we want to deal with that a-entity with the id of container, we can just say container.

Next, I want to add 75 balls to the scene. A convenient way to do something x number of times in programming is to use a construction called a for loop. Add this code to your JavaScript:

for (let i = 0; i < 75; i++) {
addBall()
}

Basically, this code initializes a variable called i that starts at 0 and counts up to 75. Every time we increase i by one, using the code i++, it runs a function called addBall. A function is basically a package of code.

If I wanted to create a different number of balls (which you certainly can), I would change the number 75 in the first line. However, beware of adding too many balls, as it may be too much for your computer to handle!

You may notice that this code does nothing, and may even throw an error. This happens because addBall is a function I just made up and haven’t defined yet!

Errors are just a normal part of a developer’s life.

Let’s define our function next.

Writing a function to add a ball

In the for loop above, I’m telling the addBall function to run by using parentheses: (). Parentheses tell the computer to do an action. But first, I need to write the function definition for addBall.

My function will generate random x, y, and z coordinates to place each sphere (z coordinate?! yes, we’re in 3D) and also add it to the container we declared earlier. Each sphere will be a dynamic-body, enabling it to bounce. Add this function definition to your code:

function addBall() {
let x = Math.random() * 10 - 5
let y = Math.random() * 50 + 2
let z = Math.random() * -10
container.innerHTML += `<a-sphere dynamic-body position="${x} ${y} ${z}" radius="0.5" color="dodgerblue" mass="0.5"></a-sphere>`
}

A few things to note:

  • We can produce random numbers with the formula Math.random() * MAX + MIN (with MAX and MIN being numbers; MAX is more like a range if your MIN is greater than 0)
  • I can add to the HTML inside the container element by accessing its innerHTML
  • I can interpolate (insert) the variables x, y, and z into my string of HTML by surrounding the whole HTML string with backticks `` and surrounding each variable with a dollar sign and curly braces ${}

Test out this code! Hopefully you see a bunch of bouncing blue balls!

If not, check your JavaScript console for errors and troubleshoot. Otherwise, proceed!

Making the balls randomly colored

We have a pretty cool app so far. But all the balls are BLUE! That’s because inside each sphere, it says color="dodgerblue". I think it would be neat if all the balls were randomly colored.

To accomplish this, I first want to create an array (list) containing the colors of the rainbow. Early in your code, right after you declare the container variable, add this new colors variable with our list of colors:

let colors = ["red", "orange", "yellow", "green", "blue", "purple", "hotpink"]

We can access any of these colors using their index number. Each color secretly has a number from zero to six, since the array contains seven colors. For instance, if I wanted to access "orange", I would say colors[1] (because the numbers start at 0)!

If I wanted to access a random index/color, I would use:

  • Math.random() to produce a random number
  • colors.length to get the length of the colors list (our MAX value)
  • Math.floor() to ensure I get an integer (whole number)

And I might come up with this formula to get a random color:

colors[Math.floor(Math.random() * colors.length)]

Whew!

Using the examples of x, y, and z already in the innerHTML, can you figure out how to interpolate (insert) the random color into the innerHTML for each sphere?

The solution is below! You’ll want to revise the last line of your addBall function to read:

container.innerHTML += `<a-sphere dynamic-body position="${x} ${y} ${z}" radius="0.5" color="${colors[Math.floor(Math.random() * colors.length)]}" mass="0.5"></a-sphere>`

Try out your code now! Do randomly-colored balls come raining from the ceiling?

If not, check your JavaScript console for errors. You can also look at the solution code. Otherwise, congratulations! You’ve made an amazing WebVR app!

Links

--

--