Using the accelerometer in games - Part 2

In a previous article, we have seen that using the accelerometer for input in games is harder than it seems.

In this article we will explore other ways of using the accelerometer, with the experience gained from releasing Dawn Of Ultra Pong.

First of all I would like to thank all the redditors that have kindly shared some tips and their experience with programming motion controls.

I concluded the previous article saying that I felt that there was still something missing with the algorithm proposed there.

Well, there were a couple of reasons why that algorithm wasn't good enough:

The first reason is that the accelerometer returns a value that is proportional on how much the device is tilted, whereas the algorithm treated it as as an ON/OFF state (tilted left - still - tilted right).

The second reason is again tied to the game feel: Having the sprite that is controlled via the accelerometer that stops immediately once the direction changes, feels like the controls are inaccurate (even though in reality it's the exact contrary).

Ok, so let's get back to the drawing board, and try to find a different way to handle the accelerometer data.

Controlling Speed

Let's take Dawn Of Ultra Pong as the example for the motion controls. In this game, the player moves his paddle by tilting the device to the left or to the right.

The question is: How do we translate the data returned from the accelerometer into paddle movement?

A possible solution can be that the paddle speed is controlled by the device inclination, with a code like this:

var
    // Constants for the tolerance
    MIN_TILT = 1,
    MAX_TILT = 5,

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

window.addEventListener('devicemotion', function (event) {
    var
        multiplier
    ;

    tilt = event.accelerationIncludingGravity.x;
    tilt = friGame.clamp(tilt, -MAX_TILT, MAX_TILT);

    direction = tilt;

    // It's easier to make calculations using positive numbers.
    if (direction < 0) {
        multiplier = -1;
        direction *= -1;
    } else {
        multiplier = 1;
    }

    if (direction < MIN_TILT) {
        direction = 0;
    }

    // Divide by MAX_TILT in order to obtain a number between 0 and 1
    direction /= MAX_TILT;

    // As redditor NovelSpinGames pointed out, there is a sine correlation between
    // the data returned from the accelerometer and the actual tilting
    direction = friGame.fx.easing.easeInSine(direction)

    // Give the direction the correct sign, so that it's a number between -1 and 1
    direction *= multiplier;

    // Now the paddle speed is proportional to the device tilting
    paddle.userData.speed = MAX_PADDLE_SPEED * direction;

    // For some readers it may be obvious, but somewhere
    // in the game loop there is code like this:
    // paddle.move({centerx: paddle.centerx + paddle.userData.speed});
}, false);

And it's with code like this that we released the first version of Dawn Of Ultra Pong.

Every person that played this version of the game disliked the controls, but nobody was able to explain why.

So there must be a different approach.

Controlling Position

While looking for a different approach, I went back playing other games that use motion controls, and when playing at Ridiculous Fishing, I realized that the controls were intuitive because it wasn't the fish hook speed that was controlled by the accelerometer, but instead it was its position.

Very well, so I tried to implement the same mechanism for Dawn Of Ultra Pong, and on the first try the game was unplayable, as the paddle was moving randomly due to the noise in the accelerometer data.

The solution comes from redditor k-mouse, who suggested to implement a low-pass filter, and now the code looks like this:

var
    // Constants for the tolerance
    MAX_TILT = 5,

    // Constant for the low-pass filter (from 0 to 1)
    // 1 means no filtering
    // the more the number is close to 0, the more filtering is performed,
    // and the more latency is introduced
    FILTER_ALPHA = 0.05,

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

window.addEventListener('devicemotion', function (event) {
    var
        multiplier,
        new_tilt
    ;

    new_tilt = event.accelerationIncludingGravity.x;
    new_tilt = friGame.clamp(new_tilt, -MAX_TILT, MAX_TILT);

    // Do a low-pass filtering of the accelerometer data
    tilt = tilt + (FILTER_ALPHA * (new_tilt - tilt));

    direction = tilt;

    // It's easier to make calculations using positive numbers.
    if (direction < 0) {
        multiplier = -1;
        direction *= -1;
    } else {
        multiplier = 1;
    }

    // Divide by MAX_TILT in order to obtain a number between 0 and 1
    direction /= MAX_TILT;

    // For some reason, when controlling the position, it feels
    // more natural to have a linear correlation between the accelerometer
    // data and the paddle position (instead of the sine correlation)

    // Give the direction the correct sign, so that it's a number between -1 and 1
    direction *= multiplier;

    // Now the paddle position is proportional to the device tilting
    paddle.move({
        centerx: (1 + direction) * SCREEN_WIDTH / 2
    });
}, false);

So the latest update of Dawn Of Ultra Pong ships with code similar to the one shown above.

In conclusion, what can we learn from all this?

First of all we learn that what sounds good in theory not always delivers satisfactory results in pratice.

Secondly we learn that there are many ways to interpret the data returned by the accelerometer, and interpreting correctly the data makes a huge difference in game feel.

Lastly, there is no one size fits all approach to the accelerometer data, what is good for Dawn Of Ultra Pong and Ridiculous Fishing not necessarily is good for a different kind of game.

Comments !