January 2016

Volume 31 Number 1

[Game Development]

Babylon.js: Advanced Features for Enhancing Your First Web Game

By Raanan Weber

In the December issue, I began this tutorial by reviewing the basic building blocks of Babylon.js, a WebGL-based 3D game engine (msdn.com/magazine/mt595753). I started designing a very simple bowling game using the tools Babylon.js offers. So far, the game includes the needed objects—a bowling ball, the lane, the gutters and 10 pins.

Now I’ll show you how to bring the game to life—how to throw the ball, hit the pins, add some audio effects, provide different camera views and more.

This part of the tutorial naturally relies on and extends the code from the first part. To differentiate between the two parts, I created a new JavaScript file to contain the code that will be used in this part. Even when extending the functionality of a certain object (such as the scene or the main camera), I won’t implement it in the function that created the object in the first part. The only function I’ll have to extend is init, which contains all of the variables needed for both parts of the tutorial.

This was done for your convenience. When implementing your own game you should, of course, use any programming paradigm and syntax you wish. I recommend giving TypeScript and its object-oriented style of programming a try. It’s wonderful for organ­izing a project.

In the time it took me to write this article, a new version of Babylon.js was released. I’ll continue using version 2.1. To check what’s new in Babylon.js 2.2, go to bit.ly/1RC9k6e.

Native Collision Detection

The main camera used in the game is the free camera, which allows the player to travel around the entire 3D scene using mouse and keyboard. Without certain modifications, however, the camera would be able to float through the scene, walk through walls, or even through the floor and the lane.  To create a realistic game, the player should be able to move only on the ground and the lane.

To enable this, I’ll use Babylon’s internal collision detection system. The collision system prevents two objects from merging into each other. It also has a gravity feature that prevents the player from floating in the air if walking forward while looking upward.

First, let’s enable collision detection and gravity: 

function enableCameraCollision(camera, scene) {
  // Enable gravity on the scene. Should be similar to earth's gravity. 
  scene.gravity = new BABYLON.Vector3(0, -0.98, 0);
  // Enable collisions globally. 
  scene.collisionsEnabled = true;
  // Enable collision detection and gravity on the free camera. 
  camera.checkCollisions = true;
  camera.applyGravity = true;
  // Set the player size, the camera's ellipsoid. 
  camera.ellipsoid = new BABYLON.Vector3(0.4, 0.8, 0.4);
}

This function enables the collision system on the game’s scene and camera and sets the camera’s ellipsoid, which can be seen as the player’s size. This is a box, sizing (in this case) 0.8x1.6x0.8 units, a roughly average human size. The camera needs this box because it’s not a mesh. The Babylon.js collision system inspects collisions between meshes only, which is why a mesh should be simulated for the camera. The ellipsoid defines the center of the object, so 0.4 translates to 0.8 in size. I also enabled the scene’s gravity, which will be applied to the camera’s movement.

Once the camera has collisions enabled, I need to enable ground and lane collision inspection. This is done with a simple Boolean flag set on each mesh I want to collide against:

function enableMeshesCollision(meshes) {
  meshes.forEach(function(mesh) {
    mesh.checkCollisions = true;
  });
}

Adding the two function calls to the init function is the final step:

// init function from the first part of the tutorial.
  ...
  enableCameraCollision(camera, scene);
  enableMeshesCollision[[floor, lane, gutters[0], gutters[1]]);
}

The collision detection’s only task is to prevent meshes from merging into one another. Its gravity feature was implemented to keep the camera on the ground. To create a realistic physical interaction between specific meshes—in this case the ball and the pins—a more complex system is required: the physics engine.

Throwing the Ball—Physics Integration in Babylon.js

The main action of the game is to throw the ball toward the pins. The requirements are fairly simple: The player should be able to set the direction and strength of the throw. If specific pins are hit, they should fall. If the pins hit other pins, those should fall, as well. If the player throws the ball to the side, it should fall into the gutter. The pins should fall according to the speed with which the ball was thrown at them.

This is exactly the domain of a physics engine. The physics engine calculates the meshes’ body dynamics in real time and calculates their next movement according to the applied forces. Simply put, the physics engine is the one who decides what happens to a mesh when another mesh collides with it or when meshes are moved by a user. It takes into account the mesh’s current speed, weight, shape and more.

To calculate the rigid body dynamics (the mesh’s next movement in space) in real time, the physics engine must simplify the mesh. To do that, each mesh has an impostor—a simple mesh that bounds it (usually a sphere or a box). This reduces the precision of the calculations, but allows a quicker calculation of the physical forces that move the object. To learn more about how the physics engine works, visit bit.ly/1S9AIsU.

The physics engine isn’t part of Babylon.js. Instead of committing to a single engine, the framework’s developers decided to implement interfaces to different physics engines and let developers decide which one they want to use. Babylon.js currently has interfaces to two physics engines: Cannon.js (cannonjs.org) and Oimo.js (github.com/lo-th/Oimo.js). Both are wonderful! I personally find Oimo integration a bit better and, therefore, will use it in my bowling game. The interface for Cannon.js was completely rewritten for Babylon.js 2.3, which is now in alpha, and now supports the latest Cannon.js version with plenty of bug fixes and new impostors, including complex height maps. I recommend you give it a try if you use Babylon.js 2.3 or later.

Enabling the physics engine is done using a simple line of code:

scene.enablePhysics(new BABYLON.Vector3(0, -9.8, 0), new BABYLON.OimoJSPlugin());

This sets the gravity of the scene and defines the physics engine to use. Replacing Oimo.js with Cannon.js simply requires changing the second variable to:

new BABYLON.CannonJSPlugin()

Next, I need to define the impostors on all objects. This is done using the mesh’s setPhysicsState function. For example, here is the lane’s definition:

lane.setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, {
  mass: 0,
  friction: 0.5,
  restitution: 0
});

The first variable is the type of impostor. Because the lane is a perfect box, I use the Box impostor. The second variable is the body’s physics definition—its weight (in kilograms), its friction and restitution factor. The lane’s mass is 0, as I want it to stay in its location. Setting mass to 0 on an impostor keeps the object locked in its current position.

For the sphere I’ll use the Sphere impostor. A bowling ball weighs roughly 6.8 kg and is usually very smooth, so no friction is needed:

ball.setPhysicsState(BABYLON.PhysicsEngine.SphereImpostor, {
  mass: 6.8,
  friction: 0,
  restitution: 0
});

If you’re wondering why I’m using kilograms and not pounds, it’s the same reason I’m using meters and not feet throughout the project: The physics engine uses the metric system. For example, the default gravity definition is (0, -9.8, 0), which is approximately Earth’s gravity. The units used are meters per second squared (m/s2 ).

Now I need to be able to throw the ball. For that I’ll use a different feature of the physics engine—applying an impulse to a certain object. Here, impulse is a force in a specific direction that’s applied to meshes with physics enabled. For example, to throw the ball forward, I’ll use the following:

ball.applyImpulse(new BABYLON.Vector3(0, 0, 20), ball.getAbsolutePosition());

The first variable is the vector of the impulse, here 20 units on the Z axis, which is forward when the scene is reset. The second variable specifies where on the object the force should be applied. In this case, it’s the center of the ball. Think about a trick shot in a pool game—the queue can hit the ball at many different points, not only the center. This is how you can simulate such a behavior.

Now I can throw the ball forward. Figure 1 shows what it looks like when the ball hits the pins.

The Bowling Ball Hitting the Pins
Figure 1 The Bowling Ball Hitting the Pins

However, I’m still missing direction and strength.

There are many ways to set the direction. The two best options are to use either the current camera’s direction or the pointer’s tap position. I’ll go with the second option.

Finding the point the user touched in space is done using the PickingInfo object, sent by each pointer-down and pointer-up event. The PickingInfo object contains information about the point the event was triggered on, including the mesh that was touched, the point on the mesh that was touched, the distance to this point and more. If no mesh was touched, the PickingInfo’s hit variable will be false. A Babylon.js scene has two useful callback functions that will help me get the picking information: onPointerUp and onPointerDown. Those two callbacks are triggered when pointer events are triggered, and their signature is as follows:

function(evt: PointerEvent, pickInfo: PickingInfo) => void

The evt variable is the original JavaScript event that was triggered. The second variable is the picking information generated by the framework on each event triggered.

I can use those callbacks to throw the ball in that direction:

scene.onPointerUp = function(evt, pickInfo) {
  if (pickInfo.hit) {
    // Calculate the direction using the picked point and the ball's position. 
    var direction = pickInfo.pickedPoint.subtract(ball.position);
    // To be able to apply scaling correctly, normalization is required.
    direction = direction.normalize();
    // Give it a bit more power (scale the normalized direction).
    var impulse = direction.scale(20);
    // Apply the impulse (and throw the ball). 
    ball.applyImpulse(impulse, new BABYLON.Vector3(0, 0, 0));
  }
}

Now I can throw the ball in a specific direction. The only thing missing is the strength of the throw. To add that I’ll calculate the delta of the frames between the pointer-down and pointer-up events. Figure 2 shows the function used to throw the ball with a specific strength.

Figure 2 Throwing the Ball with Strength and Direction

var strengthCounter = 5;
var counterUp = function() {
  strengthCounter += 0.5;
}
// This function will be called on pointer-down events.
scene.onPointerDown = function(evt, pickInfo) {
  // Start increasing the strength counter. 
  scene.registerBeforeRender(counterUp);
}
// This function will be called on pointer-up events.
scene.onPointerUp = function(evt, pickInfo) {
  // Stop increasing the strength counter. 
  scene.unregisterBeforeRender(counterUp);
  // Calculate throw direction. 
  var direction = pickInfo.pickedPoint.subtract(ball.position).normalize();
  // Impulse is multiplied with the strength counter with max value of 25.
  var impulse = direction.scale(Math.min(strengthCounter, 25));
  // Apply the impulse.
  ball.applyImpulse(impulse, ball.getAbsolutePosition());
  // Register a function that will run before each render call 
  scene.registerBeforeRender(function ballCheck() {
    if (ball.intersectsMesh(floor, false)) {
      // The ball intersects with the floor, stop checking its position.  
      scene.unregisterBeforeRender(ballCheck);
      // Let the ball roll around for 1.5 seconds before resetting it. 
      setTimeout(function() {
        var newPosition = scene.activeCameras[0].position.clone();
        newPosition.y /= 2;
        resetBall(ball, newPosition);
      }, 1500);
    }
  });
  strengthCounter = 5;
}

A note about pointer events—I deliberately haven’t used the term “click.” Babylon.js uses the Pointer Events system, which extends the mouse-click features of the browser to touch and other input devices capable of clicking, touching and pointing. This way, a touch on a smartphone or a mouse-click on a desktop both trigger the same event. To simulate this on browsers that don’t support the feature, Babylon.js uses hand.js, a polyfill for pointer events that’s also included in the game’s index.html. You can read more about hand.js on its GitHub page at bit.ly/1S4taHF. The Pointer Events draft can be found at bit.ly/1PAdo9J. Note that hand.js will be replaced in future releases with jQuery PEP (bit.ly/1NDMyYa).

That’s it for physics! The bowling game just got a whole lot better.

Adding Audio Effects

Adding audio effects to a game provides a huge boost to the UX. Audio can create the right atmosphere and add a bit more realism to the bowling game. Luckily, Babylon.js includes an audio engine, introduced in version 2.0. The audio engine is based on the Web Audio API (bit.ly/1YgBWWQ), which is supported on all major browsers except Internet Explorer. The file formats that can be used depend on the browser itself.

I’ll add three different audio effects. The first is the ambient sound—the sound that simulates the surroundings. In the case of a bowling game, the sounds of a bowling hall would normally work nicely, but because I created the bowling lane outside on the grass, some nature sounds will be better.

To add ambient sound, I’ll load the sound, auto-play it and loop it constantly:

var atmosphere = new BABYLON.Sound("Ambient", "ambient.mp3", scene, null, {
  loop: true,
  autoplay: true
});

This sound will play continually from the moment it’s loaded.

The second audio effect I’ll add is the sound of the ball rolling on the bowling lane. This sound will play as long as the ball is on the lane, but the minute the ball leaves the lane, it will stop.

First, I’ll create the rolling sound:

var rollingSound = new BABYLON.Sound("rolling", "rolling.mp3", scene, null, {
  loop: true,
  autoplay: false
});

The sound will be loaded but won’t play until I execute its play function, which will happen when the ball is thrown. I extended the function from Figure 2 and added the following:

...ball.applyImpulse(impulse, new BABYLON.Vector3(0, 0, 0));
// Start the sound.
rollingSound.play();
...

I stop the sound when the ball leaves the lane:

...
If(ball.intersectsMesh(floor, false)) {
  // Stop the sound.
  rollingSound.stop();
  ...
}
...

Babylon.js lets me attach a sound to a specific mesh. This way it automatically calculates the sound’s volume and panning using the mesh’s position and creates a more realistic experience. To do this I simply add the following line after I created the sound:

rollingSound.attachToMesh(ball);

Now the sound will always be played from the ball’s position.

The last sound effect I want to add is the ball hitting the pins. To do that I’ll create the sound and then attach it to the first pin:

var hitSound = new BABYLON.Sound("hit", "hit.mp3", scene);
hitSound.attachToMesh(pins[0]);

This sound won’t loop and it won’t play automatically.

I’ll play this sound every time the ball hits one of the pins. To make this happen I’ll add a function that, after the ball is thrown, will constantly inspect whether the ball intersects with any pin. If it does intersect, I unregister the function and play the sound. I do so by adding the following lines to the scene.onPointerUp function from Figure 2:

scene.registerBeforeRender(function ballIntersectsPins() {
  // Some will return true if the ball hit any of the pins.
  var intersects = pins.some(function (pin) {
    return ball.intersectsMesh(pin, false);
  });
  if (intersects) {
    // Unregister this function – stop inspecting the intersections.
    scene.unregisterBeforeRender(ballIntersectsPins);
    // Play the hit sound.
    hit.play();
  }
});

The game now has all the audio effects I wanted to add. Next, I’ll continue to improve the game by adding a scoreboard.

Note that I can’t include the audio effects I used with the accompanying project due to the material’s copyright. I couldn’t find any freely available audio samples that I could also publish. Therefore, the code is commented out. It will work if you add the three audio samples I used.

Adding a Score Display

After a player hits the pins, it would be great to actually see how many of them still stand and how many were dropped. For that I’ll add a scoreboard.

The scoreboard itself will be a simple black plane with white text on it. To create it, I’ll use the dynamic texture feature, which is basically a 2D canvas that can be used as a texture for 3D objects in the game.

Creating the plane and the dynamic texture is simple:

var scoreTexture = new BABYLON.DynamicTexture("scoreTexture", 512, scene, true);
var scoreboard = BABYLON.Mesh.CreatePlane("scoreboard", 5, scene);
// Position the scoreboard after the lane.
scoreboard.position.z = 40;
// Create a material for the scoreboard.
scoreboard.material = new BABYLON.StandardMaterial("scoradboardMat", scene);
// Set the diffuse texture to be the dynamic texture.
scoreboard.material.diffuseTexture = scoreTexture;

The dynamic texture allows me to directly draw onto the underlying canvas using its getContext function, which returns a CanvasRenderingContext2D (mzl.la/1M2mz01). The dynamic texture object also provides a few help functions that can be useful if I don’t want to deal directly with the canvas context. One such function is drawText, which lets me draw a string using a specific font on top of this canvas. I will update the canvas whenever the number of dropped pins changes:

var score = 0;
scene.registerBeforeRender(function() {
  var newScore = 10 - checkPins(pins, lane);
  if (newScore != score) {
    score = newScore;
    // Clear the canvas. 
    scoreTexture.clear();
    // Draw the text using a white font on black background.
    scoreTexture.drawText(score + " pins down", 40, 100,
      "bold 72px Arial", "white", "black");
  }
});

Checking if the pins are down is trivial—I check only if their position on the Y-axis is equal to the original Y-position of all pins (the predefined variable ‘pinYPosition’):

function checkPins(pins) {
  var pinsStanding = 0;
  pins.forEach(function(pin, idx) {
    // Is the pin still standing on top of the lane?
    if (BABYLON.Tools.WithinEpsilon(pinYPosition, pin.position.y, 0.01)) {
      pinsStanding++;
    }
  });
  return pinsStanding;
}

The dynamic texture can be seen in Figure 3.

The Game’s Scoreboard
Figure 3 The Game’s Scoreboard

All I’m missing now is a function to reset the lane and the board. I’ll add an action trigger that will work when the R key is pressed on the keyboard (see Figure 4).

Figure 4 Resetting the Lane and the Board

function clear() {
  // Reset the score.
  score = 0;
  // Initialize the pins.
  initPins(scene, pins);
  // Clear the dynamic texture and draw a welcome string.
  scoreTexture.clear();
  scoreTexture.drawText("welcome!", 120, 100, "bold 72px Arial", "white", "black");
}
scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction({
  trigger: BABYLON.ActionManager.OnKeyUpTrigger,
  parameter: "r"
}, clear));

Pressing the R key will reset/initialize the scene.

Adding a Follow Camera

A nice effect I want to add to the game is a camera that will follow the bowling ball when it’s thrown. I want a camera that will “roll” together with the ball toward the pins and stop when the ball is back in its original position. I can accomplish this using the multi-view feature of Babylon.js.

In the first part of this tutorial I set the free camera object as the scene’s active camera using:

scene.activeCamera = camera

The active camera variable tells the scene which camera is to be rendered, in case there’s more than one defined. This is fine when I wish to use a single camera throughout the game. But if I want to have a “picture-in-picture” effect, one active camera isn’t enough. Instead, I need to use the active cameras array stored in the scene under the variable name scene.activeCameras. The cameras in this array will be rendered one after the other. If scene.activeCameras isn’t empty, scene.activeCamera will be ignored.

The first step is to add the original free camera to this array. This is easily done in the init function. Replace scene.activeCamera = camera with:

scene.activeCameras.push(camera);

In the second step I’ll create a follow camera when the ball is thrown:

var followCamera = new BABYLON.FollowCamera("followCamera", ball.position, scene);
followCamera.radius = 1.5; // How far from the object should the camera be.
followCamera.heightOffset = 0.8; // How high above the object should it be.
followCamera.rotationOffset = 180; // The camera's angle. here - from behind.
followCamera.cameraAcceleration = 0.5 // Acceleration of the camera.
followCamera.maxCameraSpeed = 20; // The camera's max speed.

This creates the camera and configures it to stand 1.5 units behind and 0.8 units above the object it’s about to follow. This followed object should be the ball, but there’s a problem—the ball might spin and the camera will spin with it. What I want to achieve is a “flight path” behind the object. To do that, I’ll create a follow object that will get the ball’s position, but not its rotation:

// Create a very small simple mesh.
var followObject = BABYLON.Mesh.CreateBox("followObject", 0.001, scene);
// Set its position to be the same as the ball's position.
followObject.position = ball.position;

Then I set the camera’s target to be the followObject:

followCamera.target = followObject;

Now the camera will follow the follow object that’s moving together with the ball.

The last configuration the camera requires is its viewport. Each camera can define the screen real estate it will use. This is done with the viewport variable, which is defined using the following variables:

var viewport = new BABYLON.Viewport(xPosition, yPosition, width, height);

All values are between 0 and 1, just like a percentage (with 1 being 100 percent) relative to the screen’s height and width. The first two values define the camera’s rectangle starting point on the screen, and the width and height define the rectangle’s width and height compared to the screen’s real size. The viewport’s default settings are (0.0, 0.0, 1.0, 1.0), which covers the entire screen. For the follow camera I’ll set the height and width to be 30% of the screen:

followCamera.viewport = new BABYLON.Viewport(0.0, 0.0, 0.3, 0.3);

Figure 5 shows what the game’s view looks like after a ball is thrown. Notice the view at the bottom-left corner.

Picture-in-Picture Effect with the Follow Camera
Figure 5 Picture-in-Picture Effect with the Follow Camera

The input control—which screen is reacting to input events such as pointer or keyboard events—will stay with the free camera defined in the first part of this tutorial. This was already set in the init function.

How to Develop the Game Further

At the start, I said this game would be a prototype. There are a few more things to implement to make it into a real game.

The first would be to add a GUI, one that asks for the user’s name and, for example, shows configuration options (Maybe we can play a bowling game on Mars! Just set the gravity differently.) and whatever else a user might need.

Babylon.js doesn’t offer a native way to create a GUI, but members of the community have created a few extensions you can use to create wonderful GUIs, including CastorGUI (bit.ly/1M2xEhD), bGUi (bit.ly/1LCR6jk) and the dialog extension at bit.ly/1MUKpXH. The first uses HTML and CSS to add layers on top of the 3D canvas. The others add 3D dialogs to the scene itself using regular meshes and dynamic textures. I recommend you try these before writing your own solution. They’re all easy to use and will simplify the GUI-creating process a lot.

Another improvement would be to use better meshes. The meshes in my game were all created using Babylon.js internal functions, and clearly there’s a limit to what can be achieved with code only. There are many sites that offer free and paid 3D objects. The best, in my opinion, is TurboSquid (bit.ly/1jSrTZy). Look for low-poly meshes for better performance.

And after adding better meshes, why not add a human object that will actually throw the ball? To do that you’ll need the bones-animation feature integrated in Babylon.js and a mesh that supports the feature. You’ll find a demo showing what it looks like at babylonjs.com/demos/bones.

As a final touch, try making the game virtual reality-friendly. The only change needed in this case is the camera used. Replace the FreeCamera with a WebVRFreeCamera and see how easy it is to target Google Cardboard.

There are many other improvements you could make—adding a camera above the pins, adding more lanes on the side for multi-player functionality, limiting the camera’s movement and the position from which the ball can be thrown and more. I’ll let you discover some of those other features on your own.

Wrapping Up

I hope you had as much fun reading this tutorial as I had writing it, and that it gives you a push in the right direction and gets you to try Babylon.js. This is truly a wonderful framework, built with great love by developers for developers. Visit babylonjs.com for more demos of the framework. And once again, don’t hesitate to join the support forum and ask any question you have. There are no silly questions, just silly answers!


Raanan Weber is an IT consultant, full stack developer, husband and father. In his spare time he contributes to Babylon.js and other open source projects. You can read his blog at blog.raananweber.com.

Thanks to the following technical expert for reviewing this article: David Catuhe