iOS device - tilt controls

september 28, 2011 10:27pm

Today we bring you another slightly more technical Blog.
I want to talk about our solution for tilt controls on iOS.

When I originally started out with setting up this control scheme for the player, I thought it would be a piece of cake. Because it's simply nothing more then directly mapping the angle the device is held at to the angle the wheels are at for controlling a vehicle. A quick search on the forums and some questioning IRC confirmed this was as easy as I had hoped.

Since our game is locked from rotating the screen around, we knew how the device was held, and could base our code off of that, better yet, even the manual had that exact piece of code written down for us. Sweet!

inputTiltAngle = Mathf.Atan2( -Input.acceleration.y, -Input.acceleration.x ) * Mathf.Rad2Deg;
output = Mathf.Clamp(inputTiltAngle, -maxAngle, maxAngle);

Couldn't have been easier right?
So the programmer in me tests it out, and confirms, it maps the wheels to the angle. Nice, job well done.

If I would've wanted to make a doodle jump (or more recently, goatUp, one that my girlfriend enjoys a lot), this would've done he job.

Unfortunately, it wasn't as easy as that (why else would I have chosen this as topic for a Blog then). After handing my girlfriend the device, giving it a test, I dont think my heart could've been trampled on more. She described into tiny detail how much she disliked the controls, and how it did not work. Obviously, I acted as if I had planned more work on it, and she should just get used to it for now, "You're not a gamer" I believe I even replied (she is though! And a darn good one at it too, she keeps kicking my ass... I'll save that for another time).

After that I started analysing what was wrong with the current implementation.
When someone is holding the device, there is no way they can hold the device at a perfect zero-degree angle (assuming the device is held in front of the player like a wheel, and the home button is in the right hand), there is always some movement, and as it's mapped 1-on-1, the wheels will start rotating as well a bit, making it completely impossible to keep going in a straight line without balancing the device on a spirit level. Besides that, we might also not want the same amount of tilt to convert into the same amount of rotation of the wheels, so I needed a way to remap that as well.

Look at this diagram, it shows the input, and the output value.

To counter this, some form of deadzone is needed, a certain angle under which the player can rotate all he wants, but it does not affect the steering.

When something like that is implemented, our previous diagram now looks more like this.

Nice, now if we test something like that ingame, it works, there is a certain angle under which there is no input, and after that it starts to behave as before. And again, before was not good enough, so I went to think about another implementation of this. the results after the deadzone increased too much too fast, therefor making the wheel angles feel very snappy.

We had to make sure that output would be smoothed out, so it would look more like the following.

I had to use Maths again in a long while (Honestly this was never really my strongest point). I used a java powered math calculator (this one to be exact). To make it easier, here's the equation I ended up using:


This converts into the following with some more readable variable names:

inputTiltAngle = ((input-deadzone)/distance)^falloffAngle

Sweet! That makes no sense to some though. So writing that down into Unity code (Java Script, those that understand the above, are smart enough to convert it themself into C#).

inputTiltAngle = Mathf.Pow(Mathf.Clamp(Mathf.Abs(inputTiltAngle)-deadzone, 0, maxDistance)/(maxDistance-deadzone), falloffAngle)

that returns a value between 0 and 1, where our max wheel angle should be more then 1 ofcourse ;) However, simply multiplying wont do, as it only gives you range towards one side, so we have to make sure the maxAngle also works the other way around. To solve all this, the final piece of code will look like the following.

inputTiltAngle = Mathf.Clamp(Mathf.Atan2( -Input.acceleration.y, -Input.acceleration.x ) * Mathf.Rad2Deg, -maxTiltDeviceAngle, maxTiltDeviceAngle); // clamp values to max
outputAngle = Mathf.Pow(Mathf.Clamp(Mathf.Abs(inputTiltAngle)-deadzone, 0, maxTiltDeviceAngle)/(maxTiltDeviceAngle-deadzone), falloffAngle) * maxTiltDeviceAngle;

outputDevice = Mathf.Clamp(maxTiltDeviceAngle/inputTiltAngle, -1, 1) * outputAngle;

output = (outputDevice/maxTiltDeviceAngle) * maxTargetAngle; // retarget it to new angle

Quick explanation on the variables:

inputTiltAngle = value in degrees the device is being held (raw input)
maxTiltDeviceAngle = max angle the device takes input from
outputAngle = value returned from new input calculation
deadzone = amount of degrees input should not be calculated under
maxAngle = limit how far the output maximum could be
falloffAngle = strength of the curve calculated after the deadzone area
outputDevice = output angle based on device rotation (input)
maxTargetAngle = max angle the object can rotate
output = new output value based on input, in range between minus and plus of maxTargetAngle

The next addition is more of a personal taste, but when I asked around on twitter if people had a good solution themself, Jayenkai (follow him on twitter) reminded me of another very useful piece of code.

use = use - ((use-aim)/step)

This snippet in short basically interpolates any value you put in there towards the value you want it to be (in our use, that would make the wheels smoothly rotate towards the final rotation they should be, based on the input tilt controls). Luckily there is the Mathf class we can use, which contains Lerp, and that already does exactly that!
If we change the last piece of code to make use of this, it would result in this.

inputTiltAngle = Mathf.Clamp(Mathf.Atan2( -Input.acceleration.y, -Input.acceleration.x ) * Mathf.Rad2Deg, -maxTiltDeviceAngle, maxTiltDeviceAngle); // clamp values to max
outputAngle = Mathf.Pow(Mathf.Clamp(Mathf.Abs(inputTiltAngle)-deadzone, 0, maxTiltDeviceAngle)/(maxTiltDeviceAngle-deadzone), falloffAngle) * maxTiltDeviceAngle;

outputDevice = Mathf.Clamp(maxTiltDeviceAngle/inputTiltAngle, -1, 1) * outputAngle;

outputLerp = Mathf.Lerp(outputLerp, outputDevice, lerpSpeed * Time.deltaTime); // lerpSpeed variable is defined as 2.5 in my version but can be any value you feel is best
output = (outputLerp/maxTiltDeviceAngle) * maxTargetAngle; // retarget it to new angle

Woohoo, this covers the entire code for controls! we can leave it here, and we'd have a great set of controls.
Ofcourse, if some people want more feedback on the controls, they can make the camera rotate over the Z-axis in reverse of the input tilt.
That makes the horizon stay in line, even though the device is being rotated around.
The following is a bit of pseudo code, as we have it implemented in our code in a different way, but you should be able to figure out by yourself how this works if you understood all the previous ;)

Camera.main.transform.eulerAngles.z = outputDevice*-1;

That is all we used for implementation of our tilt controls in our upcoming title, keep an eye out for more on that from us! We hope this helped all the developers that have been looking, like us, for a proper implementation of tilt input values.

Keep in mind, all the values used in the Blog are just examples, you will have to tweak them yourself to get the best result out of it!

Read more developer blogs by Rotor Games:

Comments disabled.

Latest News

Final Run available on the AppStore!

After 15 months of hard work we're extremely proud to finally have our ambitious project driving action/vehicular combat game Final Run online on the AppStore! IT'S ONLY $3,99! Check it out!

Final Run - Vehicle action game for iOS!
super simple blog script super simple RSS script