Understanding DeviceOrientation events by creating a small 3D game with babylon.js

Internet Explorer 11 adds support for some new cool DOM events: the DeviceOrientation events. These events provide information about the physical orientation and motion of the current hardware.

The W3C has published a specification for these events: https://www.w3.org/TR/orientation-event/

During this article I will show you how to use these events within a small 3D game featuring an “Amiga” ball.

The final result will look like this:




Wanna try it? Just go there (you can use device orientation or the cursors keys).

DeviceOrientation events and babylon.js

Before looking in detail at the DeviceOrientation specification, you can have a look at this video showing the usage of device orientation within a babylon.js scene. And as you can see it ROCKS!



How DeviceOrientation events work ?

There are two types of data exposed by the DeviceOrientation events:

  • Orientation (deviceorientation): This value defines the orientation of the physical device in relation to a coordinate system centered on Earth. It is expressed in degree. Three coordinates are provided:
    • alpha: rotation around z axis
    • beta: rotation around x axis
    • gamma: rotation around y axis

The axis are defined using a right handed convention:

To understand these values, let’s start with the device in your hands:

The alpha orientation changes when you move the device around the z axis:

The beta orientation changes when you move the device around the x axis:

Finally, the gamma orientation changes when you move the device around the y axis:

  • Motion (devicemotion): This value defined the acceleration along each axis (x, y, z). The values are expressed in m/s² and can include (or not) the effect of the gravity). This event can also provide the rate of rotation (in deg/s) around each axis.

For more in-depth information, please have a look to the MSDN documentation.

The orientation is retrieved using the “deviceorientation” event fired on the window object:

window.addEventListener("deviceorientation", moveBall);
function moveBall(evt) {
    if (evt.alpha < 5 || evt.alpha > 355) {
        ball.position.z += 0.1;

    }
}

The motion is retrieved using the “devicemotion” event fired on the window object:

window.addEventListener("devicemotion", detectShake);
function detectShake(evt) {
    var accl = evt.acceleration;
    if (accl.x > 1.5 || accl.y > 1.5 || accl.z > 1.5) {
        // Tilt 🙂
        onLose();
    }
}

The first obvious usage of these events is to control a game. You can also use them for gesture recognition or for detecting the orientation of the user to use accordingly with a map.

Creating the ball game

The game that we will create is a simple ball game where you must control a ball and make it move to specific points in order to gain points:

The particle system in the upper right corner defines the place where you must move the ball. Once the ball is near the good place, you will earn a point.

Every time you earn a point, the ball speed will be increased and the playground will rotate to increase difficulty.

So first of all, you need to create a simple html file with a reference to babylon.js (incredible!!):

<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
    <title>Device orientation - ball game</title>
    <link href="index.css" rel="stylesheet" />
    <script src="babylon.js"></script>
</head>
<body>
    <canvas id="renderCanvas"></canvas>   
    <div id="score">Score: 0</div>
    <div id="speed">Speed: 1</div>
    <div id="gameOver" class="hidden">
        <div id="gameOverText">Game Over</div>
    </div>
    <script src="index.js"></script>
</body>
</html>

Then inside index.js, we can create the 3D environment required by our game. The first thing to do is to create the engine and the scene;

var canvas = document.getElementById("renderCanvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

if (BABYLON.Engine.isSupported()) {

    var engine = new BABYLON.Engine(canvas, true);
    var scene = new BABYLON.Scene(engine);
    var light = new BABYLON.DirectionalLight("light", new BABYLON.Vector3(2, -10, 5), scene);
    var camera = new BABYLON.ArcRotateCamera("camera", 3 * Math.PI / 2.0, Math.PI / 4.0, 20.0, new BABYLON.Vector3(0, 0, 0), scene);

    scene.activeCamera = camera;

You will also need a render loop to ensure frames are drawn to the canvas:

engine.runRenderLoop(function () {
    scene.render();

    if (!started) {
        return;
    }

});

For now, the screen is a bit empty:

Then we can create the starfield to get a cool background. It will be created as a particle system:

// Starfield
var starfield = new BABYLON.ParticleSystem("particles", 4000, scene);
starfield.particleTexture = new BABYLON.Texture("star.png", scene);
starfield.minAngularSpeed = -4.5;
starfield.maxAngularSpeed = 4.5;
starfield.minSize = 0.5;
starfield.maxSize = 1.0;
starfield.minLifeTime = 0.5;
starfield.maxLifeTime = 2.0;
starfield.minEmitPower = 0.5;
starfield.maxEmitPower = 1.0;
starfield.emitRate = 600;
starfield.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
starfield.minEmitBox = new BABYLON.Vector3(-25, 0, -25);
starfield.maxEmitBox = new BABYLON.Vector3(25, 0, 25);
starfield.direction1 = new BABYLON.Vector3(0, 1, 0);
starfield.direction2 = new BABYLON.Vector3(0, 1, 0);
starfield.color1 = new BABYLON.Color4(0, 0, 0, 1);
starfield.color2 = new BABYLON.Color4(1, 1, 1, 1);
starfield.gravity = new BABYLON.Vector3(0, 5, 0);
starfield.emitter = new BABYLON.Vector3(0, -2, 0);
starfield.start();

For more information on particle system, you can go here.

Now it starts to have a good looking:

Then we have to create the ball (a simple sphere), add a material and prepare a small animation (used when the ball grabs a point):

// Ball
var ball = BABYLON.Mesh.CreateSphere("ball", 16, 1.0, scene, false);
var ballMaterial = new BABYLON.StandardMaterial("ballMaterial", scene);
ballMaterial.diffuseColor = new BABYLON.Color3(1, 0, 0);
ballMaterial.diffuseTexture = new BABYLON.Texture("amiga.jpg", scene);
ballMaterial.diffuseTexture.uScale = 3;
ballMaterial.diffuseTexture.vScale = 4;
ball.material = ballMaterial;
ball.position = new BABYLON.Vector3(0, 0.5, 0);
ball.renderingGroupId = 1;
ball.rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(0, 0, 0);
var animationScale = new BABYLON.Animation("scale", "scaling", 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
animationScale.setKeys([{ frame: 0, value: new BABYLON.Vector3(1, 1, 1) }, { frame: 20, value: new BABYLON.Vector3(2.0, 2.0, 2.0) },
                        { frame: 40, value: new BABYLON.Vector3(1, 1, 1) }]);
ball.animations.push(animationScale);

Please note the usage of ball.renderingGroupId = 1 that will allow the ball (and the playground to be on a different layer than the starfield in order to avoid having stars that are going through the playground).

For more information on how to create simple objects with babylon.js, you can go there.

For more information on materials, you can go here.

For more information on animations, you can go here.

The ball is the center of the universe:

The playground will be a simple plane textured with a wood picture:

// Playground
var ground = BABYLON.Mesh.CreateGround("ground", 20, 20, 1, scene, false);
var groundMaterial = new BABYLON.StandardMaterial("groundMaterial", scene);
groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
groundMaterial.diffuseTexture = new BABYLON.Texture("wood.png", scene);
groundMaterial.diffuseTexture.uScale = 2;
groundMaterial.diffuseTexture.vScale = 2;
ground.material = groundMaterial;
ground.receiveShadows = true;
ground.renderingGroupId = 1;

The game is almost ready:

To add a more realistic effect, let’s add some shadows:

// Shadows
var shadowCaster = new BABYLON.ShadowGenerator(1024, light);
light.position = new BABYLON.Vector3(-4, 14, -12.5);
shadowCaster.useVarianceShadowMap = true;
shadowCaster.getShadowMap().renderList.push(ball);

You can have more information about shadows here.

And it looks great:

The final thing to add is the target (ie. place where to go to gain one point). We will also use a particle system here:

// Target
var target = new BABYLON.ParticleSystem("particles", 4000, scene);
target.particleTexture = new BABYLON.Texture("star.png", scene);
target.minAngularSpeed = -4.5;
target.maxAngularSpeed = 4.5;
target.minSize = 0.5;
target.maxSize = 3.0;
target.minLifeTime = 0.5;
target.maxLifeTime = 2.0;
target.minEmitPower = 0.5;
target.maxEmitPower = 1.0;
target.emitRate = 200;
target.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
target.minEmitBox = new BABYLON.Vector3(-1, 0, -1);
target.maxEmitBox = new BABYLON.Vector3(1, 0, 1);
target.direction1 = new BABYLON.Vector3(0, 1, 0);
target.direction2 = new BABYLON.Vector3(0, 1, 0);
target.color1 = new BABYLON.Color4(1, 1, 0, 1);
target.color2 = new BABYLON.Color4(1, 1, 1, 1);
target.gravity = new BABYLON.Vector3(0, 5, 0);
target.emitter = new BABYLON.Vector3(8, 0, 8);
target.renderingGroupId = 1;
target.start();

Our game is ready to be played:

Adding DeviceOrientation events to our game

Using the DeviceOrientation events is pretty straightforward. The ball will be controlled using the device rotation. To do so, you need some variables to store the current and previous rotations values:

var orientationGamma = 0;
var orientationBeta = 0;
var initialOrientationGamma = 0;
var initialOrientationBeta = 0;

Using these variables, here is the code to detect rotations changes:

// Orientation
window.addEventListener("deviceorientation", moveBall);
function moveBall(evt) {
    if (!started) {
        return;
    }
    if (!initialOrientationGamma) {
        initialOrientationGamma = evt.gamma;
        initialOrientationBeta = evt.beta;
    }

    orientationGamma = evt.gamma;
    orientationBeta = evt.beta;
}

window.addEventListener("devicemotion", detectShake);
function detectShake(evt) {
    var accl = evt.acceleration;
    if (accl.x > 1.5 || accl.y > 1.5 || accl.z > 1.5) {
        // Tilt 🙂
        onLose();
    }
}

You can note that gamma and beta values are used here. The devicemotion is used to simulate a “tilt” when you shake too quickly the device.

For the sake of simplicity, I will not include code for onLose (and onWin) functions but you can find them in the game source code available below.

You then have to update the renderLoop to use these values:

engine.runRenderLoop(function () {
    scene.render();


    // Compute direction
    if (orientationGamma) {
        var z = (initialOrientationBeta - orientationBeta) * 0.05;
        var x = (initialOrientationGamma - orientationGamma) * -0.05;
        direction.addInPlace(new BABYLON.Vector3(0, 0, z * speed * scale));
        direction.addInPlace(new BABYLON.Vector3(x * speed * scale, 0, 0));
    }

    // Moving and rotating ball
    ball.position.addInPlace(direction);
    var rotationToApply = BABYLON.Quaternion.RotationYawPitchRoll(0, direction.z * 1.5, -direction.x * 1.5);
    ball.rotationQuaternion = rotationToApply.multiply(ball.rotationQuaternion);

    direction.scaleInPlace(0.95);

    // Collisions
    checkCollisions();
});

Gamma rotation controls the direction to x and beta to z. The ball is then moved accordingly and rotated a bit in the right direction to simulate a rolling.

The checkCollisions function just checks if the ball is still inside the playground and if the target is not reached (to call the onWin function then):

// Collisions
var checkCollisions = function() {
    // Target met
    if (BABYLON.Vector3.Distance(ball.position, target.emitter) < 1.2) {
        onWin();
        return;
    }

    var point = ball.position.clone();
    point.y -= 0.5;
    if (!ground.intersectsPoint(point)) {
        onLose();
    }
};

And that’s it. You now have a modern, beautiful and “device orientation controlled” game!

The complete game

The complete game also supports cursors keys. You can find the code here. Feel free to use it as a base for your own applications!

Going further

If you want to go further, here are some pointers: