After recently completing an iPhone game that relied almost exclusively on the accelerometer for gameplay, I thought a proper tutorial on how to calibrate and optimize the accelerometer would be appropriate. Hopefully, this will make your development go a little smoother.
This tutorial is not an introduction to the UIAccelerometer. A quick search on this forum or on Google will turn up plenty of tutorials and sample code. This tutorial will focus on 3 things:
1. Calibrating the accelerometer so the user can play your game from any position.
2. Changing the "sensitivity" of your object's movement.
3. Adding the option to "invert" the controls.
First off, why bother adding these features? Simple. Launch your accelerometer based game and try the following tests:
1. Play sitting up in perfect position.
2. Play it slouched over.
3. Play it lying down on your side.
4. Play it lying on your back with the device parallel to the floor and the screen facing you.
I'm sure the gaming experience is different in all these positions and I wouldn't be surprised if one or two of the listed positions renders a game unplayable. Implementing calibration will allow the user to play your game in any position they wish.
Here's the starting accelerometer code I'll be using. Just a quick note… my code was written for a cocos2d sprite but the same basic theory will apply to those using UIKit objects (like UIImageViews). This implements a "Breakout-style paddle". The device is landscaped (so the X values are actually taken from the Y accelerometer) and the paddle can only move left or right depending on the tilt of the device:
Code:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
/*
Some preliminary clarification:
1. In my example, paddle is a sprite. However, this could be any object (like a UIImageView, etc).
2. To make this easy to understand, let's say the paddle is 50 pixels wide and I'll hard code the bounds.
3. cocos2d sprites use the CENTER of the object as the position point. UIKit uses the TOP LEFT corner. Change your values accordingly.
4. In case you're wondering, "ccp" means CoCos2d Point. It's comparable to CGPointMake(x,y). ie: myObject.position = ccp(xPos, yPos)
*/
float acelx = -acceleration.y;
float movement = acelx * 40;
AtlasSprite *paddle = (AtlasSprite *)[spriteManager getChildByTag:kPaddle];
if ( paddle.position.x > 0 && paddle.position.x < 480) {
//paddle is at neither edge of the screen so move the paddle!
paddle.position = ccp(paddle.position.x + movement, paddle.position.y);
}
if ( paddle.position.x < 26 ) {
//paddle hit the left edge of the screen, set the left bound position with no movement.
paddle.position = ccp( 25, paddle.position.y);
}
if ( paddle.position.x > 454 ) {
//paddle hit the right edge of the screen, set the right bound position with no movement.
paddle.position = ccp( 455, paddle.position.y);
}
if ( paddle.position.x < 26 && movement > 1 ) {
//paddle is at the left edge of the screen and the device is tiled right. Move the paddle!
paddle.position = ccp(paddle.position.x + movement, paddle.position.y);
}
if ( paddle.position.x > 454 && movement < 0) {
//paddle is at the right edge of the screen and the device is tiled left. Move the paddle!
paddle.position = ccp(paddle.position.x + movement, paddle.position.y);
}
}
So I've included the paddle movement/positioning just for the heck of it really. The IMPORTANT part of this tutorial actually lies in the first two lines of this function:
Code:
float acelx = -acceleration.y;
float movement = acelx * 40;
First, I'm in landscape left mode so the Y acceleration value is actually our X value. Secondly, I'm multiplying the acceleration value by a constant 40. I like to refer to this as my "Acceleration Factor". In fact, if you want to give the user the option to change sensitivity, all you have to do is create some way to change this value. Changing it to a lower integer (like 20) makes the paddle move slower and creates the feeling of lower sensitivity. Increasing the value (try 70) creates the feeling of higher sensitivity.
The second thing you might notice is that I'm actually using the NEGATIVE acceleration value for acelx. Want to give the user the option to "invert" the controls? All you have to do is use the POSITIVE acceleration value.
Just a quick side note on giving the user the option for "inverted" options. Sure, some people may just like it better… but there's actually a practical application. As I mentioned at the start of this article, try the above paddle code lying on your back, device parallel to the floor with the screen facing you. Notice that pushing device right or left is actually moving it towards the sky (rather than pushing it toward the floor like when you're sitting upright). In fact, giving the user the option to invert controls is actually allowing them to play the game lying down WITHOUT inverting the controls!!
I realize that may be a little difficult to grasp just by reading it but try it for yourself and you'll see what I mean.
So already, I would propose the following changes to the above code:
Code:
/*
direction = -1 for normal controls or 1 for inverted
sensitivity can be whatever value works for your game. I usually choose values like 20 for low, 45 for med, and 70 for high.
*/
float acelx = acceleration.y * direction;
float movement = acelx * sensitivity;
;
If you use my sample code you may notice one strange behavior. Even when the device is laid on a flat surface, you're going to see the paddle move to the edge of the screen as if it's unbalanced. This leads me to the final and most important part of tutorial: calibration.
In a separate "Calibration Button" all you need to add is this single line of code:
Code:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
calibration = acceleration.y;
}
This will store the current device position that we'll use as an offset to create the proper "pivot point". It should be noted that my calibration button is located in an "Options" class that is different from my "Main Game" class. So I actually have TWO accelerometer functions, one for calibration in my options class and another for paddle movement in my game class. In my case, the calibration is saved as a global value accessible from both classes (or perhaps an even better way would be to save the calibration as an NSUserDefault value).
You would then change your game class accelerometer code to something like this:
Code:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
float acelx = acceleration.y * direction;
/*
To create the offset we do the following:
1. Start with the calibration the user has set.
2. Multiply the calibration by the sensitivity (taking into account whether we're using normal or inverted controls).
3. Keep in mind that the sensitivity will always be a positive value. However, when the controls are inverted we need to make this a NEGATIVE value. This is the reason we multiply the direction * -1 (remember, the direction = -1 when we're playing normal, and 1 when we're inverted).
*/
float offset = calibration * (sensitivity * ( direction * -1 );
float movement = (acelx * sensitivity) + offset;
}
That's all there is! Now, if the device is held at 90 degrees (like it would be if someone were playing while laying on their side) the user can have the option to calibrate the device to their current position and play your game as if they were sitting upright!
Two final pieces of "accelerometer optimization" you may want to consider is the use of a filter. Sometimes you may find that your movement is still "too jittery" for your tastes. Implementing a filter will eliminated these jitters and smooth out your object movement. A "high-pass filter" will ignore the accelerometer for very low values. Notice that when you use the above code and you try to keep the paddle perfectly still there's still some "shake". The high-pass filter will fix this because those very small changes in the accelerometer will be ignored.
A simple example on how to implement a high-pass filter:
High-Pass Filter Example
Although less common, you can also implement a "Low-pass filter" which essentially does the opposite: it ignores large changes in accelerometer movement but responds to the small ones.
A Low-pass filer is best exemplified by this Apple-provided sample (which also includes another high-pass filter as well):
Low-Pass Filter Example
Hopefully this tutorial has explained how to calibrate the accelerometer, set the sensitivity, invert the controls, and potentially implement a filter. I would think all these things could definitely increase the value (and replay value) of your game.
If you found this tutorial helpful, nothing says thank you more than purchasing the $0.99 game upon which these ideas are based. Please check out…
Mad Bomber!
iTunes Link:
Mad Bomber
Screenshots:
Thanks!
Charles