Using the accelerometer in games

The accelerometer can be a good input method for some genres, think for example of racing games.

Using it is easy, it's just a matter of mapping a value to a direction, right? Well, let's find out.

Before starting, notice that although in this article I'm talking about HTML5, the concepts are generic enough to be applied to any technology.

Also, the examples used in this article will refer to a racing game, but the concepts still apply to any type of game that uses the accelerometer.

In HTML5, the accelerometer data is accessed through the devicemotion event like this:

var tilt = 0;

window.addEventListener('devicemotion', function (event) {
    // Unless you need complex control schemes,
    // you can measure how much the device is tilted
    // by using only 1 of the 3 axes.
    // In this case I'm using only the y axis.
    tilt = event.accelerationIncludingGravity.y;
}, false);

The accelerometer data is divided in 3 axes (x, y and z), and usually using 1 of the 3 axes is enough.

In order to find out which one of the axes is useful to your purposes, I recommend making a very simple application that continuously outputs all the 3 values, and by tilting the device in the desired positions, see which value changes the most.

By doing this, you will also have a rough estimate on the possible range of values (for example you can have a device that when is perfectly horizontal has an y value of 0, when tilted to the left goes down to about -5, and tilted to the right goes up to about 5).

Ok, now you will be tempted to use the data in a similar way:

if (tilt < 0) {
    turnLeft();
} else if (tilt > 0) {
    turnRight();
} else {
    goStraight();
}

And immediately after you try it, you will notice that the game is completely unplayable.

There are a couple of reasons to that:

The most obvious one is that it is impossible to stand perfectly still, so rarely tilt will be equals to 0. Moreover, the accelerometer data will have some noise in it, so it will constantly change between similar values, even when perfectly still.

So the next logical step is to use a tolerance, for example instead of comparing to 0, you can compare to -1 and 1 respectively, and when you try again the game, you will notice that something still doesn't feel right.

I don't know exactly why, while the game behaviour now seems to make perfect sense, the feeling is still not right, but I have a theory:

we should shift our focus from a physical perspective to a psychological one.

Let me explain:

Let's consider what goes through the mind of the player while he tilts the device, for example to the left:

While he's tilting the device to the left, it is obvious that his intention is to turn the car to the left. Once the car is in the position that the player wanted, he starts tilting the device to the right, in order to return to the horizontal position.

Here's our first problem: Due to the speed of the game (no one likes slow cars in games, right?), the car will continue to turn to the left until the player reaches the horizontal position, and this will result in the car turning too much to the left.

The player will now try to adjust the position by turning slightly to the right, but the same problem will occur and now the car will be too much to the right, and so on.

A possible solution to this problem might be to stop turning as soon as we detect that the player is changing direction, and the code to achieve that may look like this:

var
    // Constants for the state machine
    AS_STILL = 0,
    AS_TILTED_LEFT = 1,
    AS_STILL_LEFT = 2,
    AS_TILTED_RIGHT = 3,
    AS_STILL_RIGHT = 4,

    // Constants for the tolerance
    DYN_TILT = 0.5,
    MIN_TILT = 1,

    tilt,       // The tilting of the device
    direction,  // The computed direction based on the tilting

    accel_data = {
        status: AS_STILL,
        min_tilt: 0,
        max_tilt: 0
    }
;

window.addEventListener('devicemotion', function (event) {
    tilt = event.accelerationIncludingGravity.y;

    if (accel_data.status === AS_TILTED_LEFT) {
        if (tilt < accel_data.min_tilt) {
            // The player is turning left
            accel_data.min_tilt = tilt;
            direction = -1;
        } else if (tilt >= (accel_data.min_tilt + DYN_TILT)) {
            // The player stopped turning left
            accel_data.min_tilt = tilt;
            accel_data.max_tilt = tilt;
            direction = 0;
            accel_data.status = AS_STILL_LEFT;
        } else {
            // The player is still in the tilted left position
            direction = -1;
        }
    } else if (accel_data.status === AS_STILL_LEFT) {
        if (tilt >= 0) {
            // The device is now horizontal
            direction = 0;
            accel_data.status = AS_STILL;
        } else if (tilt > accel_data.max_tilt) {
            // The player is returning the device to the horizontal position
            accel_data.max_tilt = tilt;
            direction = 0;
        } else if (tilt <= (accel_data.max_tilt - DYN_TILT)) {
            // The player has started turning left again
            accel_data.min_tilt = tilt;
            accel_data.max_tilt = tilt;
            direction = -1;
            accel_data.status = AS_TILTED_LEFT;
        } else {
            // The player is still in the tilted left position
            direction = 0;
        }
    } else if (accel_data.status === AS_TILTED_RIGHT) {
        if (tilt > accel_data.max_tilt) {
            // The player is turning right
            accel_data.max_tilt = tilt;
            direction = 1;
        } else if (tilt <= (accel_data.max_tilt - DYN_TILT)) {
            // The player stopped turning right
            accel_data.min_tilt = tilt;
            accel_data.max_tilt = tilt;
            direction = 0;
            accel_data.status = AS_STILL_RIGHT;
        } else {
            // The player is still in the tilted right position
            direction = 1;
        }
    } else if (accel_data.status === AS_STILL_RIGHT) {
        if (tilt <= 0) {
            // The device is now horizontal
            direction = 0;
            accel_data.status = AS_STILL;
        } else if (tilt < accel_data.min_tilt) {
            // The player is returning the device to the horizontal position
            accel_data.min_tilt = tilt;
            direction = 0;
        } else if (tilt >= (accel_data.min_tilt + DYN_TILT)) {
            // The player has started turning right again
            accel_data.min_tilt = tilt;
            accel_data.max_tilt = tilt;
            direction = 1;
            accel_data.status = AS_TILTED_RIGHT;
        } else {
            // The player is still in the tilted right position
            direction = 0;
        }
    } else {
        if (tilt <= -MIN_TILT) {
            // The player has started turning left
            accel_data.min_tilt = tilt;
            accel_data.max_tilt = tilt;
            direction = -1;
            accel_data.status = AS_TILTED_LEFT;
        } else if (tilt >= MIN_TILT) {
            // The player has started turning right
            accel_data.min_tilt = tilt;
            accel_data.max_tilt = tilt;
            direction = 1;
            accel_data.status = AS_TILTED_RIGHT;
        } else {
            // The player is still in the horizontal position
            direction = 0;
        }
    }

    if (direction < 0) {
        turnLeft();
    } else if (direction > 0) {
        turnRight();
    } else {
        goStraight();
    }
}, false);

Wow, that's a lot of code, but it's not as scary as it may seem initially.

First of all we need a state machine that reflects the intention of the player, in theory it should be only 3 states: AS_STILL, AS_TILTED_LEFT, and AS_TILTED_RIGHT. We also need a variable that holds this state and also the minimum and maximum tilting done by the player, and in this example tis variable is accel_data.

Ok, now when the player tilts the device to the left, the state changes to AS_TILTED_LEFT, and the computed direction will be -1, which means to turn left. The minimum value of tilt is stored in accel_data.min_tilt, so that we can know when the player stops tilting the device to the left. So far, so good.

When the player starts tilting the device to the right, we have to stop turning left, and wait until the tilting of the device reaches 0, so that the state machine can return to the AS_STILL state. In order to achieve such condition, the AS_STILL_LEFT and AS_STILL_RIGHT states exist.

By using this method, the game is much more playable, but I feel that I'm still missing something.

You may ask, what am I missing? I'll tell you... I don't know.

Please let me know in the comments if you have any idea.

How do you use the accelerometer and what are your experiences with accelerometer based games? Please let me know in the comments.

Comments !