Advertise Mobile SDKs Books Events Forum News Social Networking Support Us
Follow @iphonedevsdk on Twitter

Mockup & CodeGen, iPhone & iPad
($9.99)

Make your own iPhone apps
and run them live!
(free)

Manu
($0.99)

Want your application or service advertised on iPhone Dev SDK?

Go Back   iPhone Dev SDK Forum > iPhone SDK Development Forums > iPhone SDK Tutorials

Reply
 
LinkBack Thread Tools Display Modes
Old 02-05-2010, 12:47 PM   #1 (permalink)
Registered Member
 
LiquidChaz's Avatar
 
Join Date: Sep 2009
Location: New York City
Posts: 53
Default [TUTORIAL] Accelerometer Calibration & Optimizations

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
LiquidChaz is offline   Reply With Quote
Old 02-05-2010, 10:50 PM   #2 (permalink)
indie dev
 
rocotilos's Avatar
 
Join Date: Oct 2009
Posts: 2,754
Default

Nice tutorial. But I think wrong section - should be posted in Tutorial Section.
rocotilos is offline   Reply With Quote
Old 02-06-2010, 12:42 AM   #3 (permalink)
Registered Member
 
LiquidChaz's Avatar
 
Join Date: Sep 2009
Location: New York City
Posts: 53
Default

Quote:
Originally Posted by rocotilos View Post
Nice tutorial. But I think wrong section - should be posted in Tutorial Section.
I agree... But I don't have the permissions to post in the tutorials section.
LiquidChaz is offline   Reply With Quote
Old 02-06-2010, 11:23 AM   #4 (permalink)
Pro. Game Developer
iPhone Dev SDK Supporter
 
Join Date: Feb 2009
Location: żLa Islas Hermosas?
Posts: 2,178
Default

Quote:
Originally Posted by LiquidChaz View Post
I agree... But I don't have the permissions to post in the tutorials section.
Moved... and thank you for the nice post!
__________________
~~ Word Flurry ~~ App Store / Website / Facebook
Kalimba is offline   Reply With Quote
Old 06-15-2010, 05:09 PM   #5 (permalink)
Registered Member
 
Join Date: Mar 2010
Posts: 2
Default 2D version of Accelerometer control.

Hi;
Thought I'd post my example using the tutorial. Thanks for the tutorial.
I spent days researching and experimenting to come up with this combination of stuff. This works pretty well. I haven't however been able to get calibration to work. I did add a threshold value to stop the jitter. Any suggestions on Calibration or Jitter are appreciated.
-Jim

Code:
//
// REFERENCES::
// http://iphonedevelopertips.com/user-interface/accelerometer-101.html
// http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/39833-tutorial-accelerometer-calibration-optimizations.html
//
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 
{
   // in landscape left mode so the Y acceleration
   // value is actually our X value and vise versus. 
   // direction = -1 for normal controls or 1 for inverted
   // sensitivity can be whatever value works for your game. 
   // choose values like 20 for low, 45 for med, and 70 for high.
   
   // High Level Filter.
   double tempPrevX = (acceleration.y *  direction * kFilteringFactor) + 
           (prevX * (1.0 - kFilteringFactor));
   double tempPrevY = (acceleration.x * -direction * kFilteringFactor) + 
           (prevY * (1.0 - kFilteringFactor));
   
   double tempAccelX = (acceleration.y *  direction) - tempPrevX;
   double tempAccelY = (acceleration.x * -direction) - tempPrevY;
   
   // NOTE:: Can't get this to work
   // To create the offset 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 offsetX = (float)calibrationX * (sensitivity * (  direction * -1 ));
   float offsetY = (float)calibrationY * (sensitivity * ( -direction * -1 ));
   float movementX = ((float)tempAccelX * sensitivity) + offsetX;
   float movementY = ((float)tempAccelY * sensitivity) + offsetY;

   // To stop jitter check for a threshold of movement. 
   // If doesn't make jitter thresh leave old values in place.
   // Setting it to 1 or 2 Pixels
   BOOL changeInX = NO;
   BOOL changeInY = NO;
   if (movementX >  kDefMovementThreshX || movementX < -kDefMovementThreshX) 
   {
      ratSprite.position = ccp(ratSprite.position.x + movementX, 
                               ratSprite.position.y);
      prevX   = tempPrevX;
      accelX  = tempAccelX;
      changeInX = YES;
   }
   
   if (movementY >  kDefMovementThreshY || movementY < -kDefMovementThreshY) 
   {
      ratSprite.position = ccp(ratSprite.position.x, 
                               ratSprite.position.y + movementY);
      prevY   = tempPrevY;
      accelX  = tempAccelY;
      changeInY = YES;
   }
   
   // Only do this if needed
   if (changeInX || changeInY)
   {
      //NSLog(@"movementX: %f, movementY: %f",movementX,movementY);
      [self boundryCheck];
   }
}
jmurff is offline   Reply With Quote
Old 08-12-2010, 10:11 PM   #6 (permalink)
Registered Member
 
Join Date: Jan 2010
Posts: 10
Default slowness

Hi, thanks for the tutorial! It works with a slight nuisance though.

Im doing landscape with only changing the object.position.y. (therefore acceleration.x is what I'm using).

Now if I start with the iphone at a 45 degrees tilt down towards me, the object moves faster upwards than downwards.

If I do 90 degree angle which is essential the phone on its side, it is nearly impossible to bring the object down.

Can you give some hints on this? I'm suspecting a variant speed factor is needed?
Fifaguy is offline   Reply With Quote
Old 08-12-2010, 10:13 PM   #7 (permalink)
Registered Member
 
Join Date: Jan 2010
Posts: 10
Default

Quote:
Originally Posted by Fifaguy View Post
Hi, thanks for the tutorial! It works with a slight nuisance though.

Im doing landscape with only changing the object.position.y. (therefore acceleration.x is what I'm using).

Now if I start with the iphone at a 45 degrees tilt down towards me, the object moves faster upwards than downwards.

If I do 90 degree angle which is essential the phone on its side, it is nearly impossible to bring the object down.

Can you give some hints on this? I'm suspecting a variant speed factor is needed?
EDIT:

Sorry I thought i had a working code. It doesnt work for 90 degrees. i will post it when I finish tinkering with it.

Last edited by Fifaguy; 08-12-2010 at 10:17 PM.
Fifaguy is offline   Reply With Quote
Old 12-02-2010, 05:29 PM   #8 (permalink)
may
Registered Member
 
Join Date: Jan 2009
Posts: 18
Default

Quote:
Originally Posted by jmurff View Post
Hi;
Thought I'd post my example using the tutorial. Thanks for the tutorial.
I spent days researching and experimenting to come up with this combination of stuff. This works pretty well. I haven't however been able to get calibration to work. I did add a threshold value to stop the jitter. Any suggestions on Calibration or Jitter are appreciated.
-Jim

Code:
//
// REFERENCES::
// http://iphonedevelopertips.com/user-interface/accelerometer-101.html
// http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/39833-tutorial-accelerometer-calibration-optimizations.html
//
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 
{
   // in landscape left mode so the Y acceleration
   // value is actually our X value and vise versus. 
   // direction = -1 for normal controls or 1 for inverted
   // sensitivity can be whatever value works for your game. 
   // choose values like 20 for low, 45 for med, and 70 for high.
   
   // High Level Filter.
   double tempPrevX = (acceleration.y *  direction * kFilteringFactor) + 
           (prevX * (1.0 - kFilteringFactor));
   double tempPrevY = (acceleration.x * -direction * kFilteringFactor) + 
           (prevY * (1.0 - kFilteringFactor));
   
   double tempAccelX = (acceleration.y *  direction) - tempPrevX;
   double tempAccelY = (acceleration.x * -direction) - tempPrevY;
   
   // NOTE:: Can't get this to work
   // To create the offset 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 offsetX = (float)calibrationX * (sensitivity * (  direction * -1 ));
   float offsetY = (float)calibrationY * (sensitivity * ( -direction * -1 ));
   float movementX = ((float)tempAccelX * sensitivity) + offsetX;
   float movementY = ((float)tempAccelY * sensitivity) + offsetY;

   // To stop jitter check for a threshold of movement. 
   // If doesn't make jitter thresh leave old values in place.
   // Setting it to 1 or 2 Pixels
   BOOL changeInX = NO;
   BOOL changeInY = NO;
   if (movementX >  kDefMovementThreshX || movementX < -kDefMovementThreshX) 
   {
      ratSprite.position = ccp(ratSprite.position.x + movementX, 
                               ratSprite.position.y);
      prevX   = tempPrevX;
      accelX  = tempAccelX;
      changeInX = YES;
   }
   
   if (movementY >  kDefMovementThreshY || movementY < -kDefMovementThreshY) 
   {
      ratSprite.position = ccp(ratSprite.position.x, 
                               ratSprite.position.y + movementY);
      prevY   = tempPrevY;
      accelX  = tempAccelY;
      changeInY = YES;
   }
   
   // Only do this if needed
   if (changeInX || changeInY)
   {
      //NSLog(@"movementX: %f, movementY: %f",movementX,movementY);
      [self boundryCheck];
   }
}
Hi thought i could add a little bit of help to the subject.
This is how i handle the jitters. Basically you have to do a little math / average the value of the reading. like this
Code:
- (float)  addValueAndAverage: (float*)valueArray: (float) value
{
	if (valueArray == nil ){
		return value;
	}
	// cap arraySize to size of the array
	if ( insertAt >= maxaccumulatedCount ){
		return value;
	}
	
	float accumulator = 0.0;
	int i = 0;
	
	// stick the new value in 
	valueArray[insertAt] = value;
	
	// add up, and shift the values down if full
	for (i = 0; i < accumulatedCount; i++){
		accumulator += valueArray[i];
	}
	
	return accumulator / accumulatedCount;
}
then in the accel call run something like this.
Code:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
   
	
	if ( accumulatedCount < maxaccumulatedCount){
		accumulatedCount++;
	}
	
	float newaccelerationX = [self addValueAndAverage:accelXPast :acceleration.x]; 
	if (fabs((fabs(newaccelerationX)  - fabs(accelerationX))) >= 0.005){
		accelerationX = newaccelerationX;
	}
 }
        insertAt = (insertAt+1) % maxaccumulatedCount;
}
Hope this helps someone.
__________________
our site at http://www.HamwayApps.net
may is offline   Reply With Quote
Reply

Bookmarks

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is On
Trackbacks are On
Pingbacks are On
Refbacks are On



» Advertisements
» Online Users: 233
15 members and 218 guests
ADY, AragornSG, Dani77, Dattee, Duncan C, fkmtc, HDshot, HemiMG, Promo Dispenser, Punkjumper, Rudy, sacha1996, sneaky, spiderguy84, theone8one
Most users ever online was 1,187, 10-11-2011 at 08:09 AM.
» Stats
Members: 158,885
Threads: 89,231
Posts: 380,768
Top Poster: BrianSlick (7,129)
Welcome to our newest member, bookesp
Powered by vBadvanced CMPS v3.1.0

All times are GMT -5. The time now is 03:16 PM.
Powered by vBulletin® Version 3.8.0
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.0