0
votes

In my sprite kit game, the character moves by tilting the iPhone. I have accomplished this with the CMMotionManager. It works like a charm.

- (void)processUserMotionForUpdate:(NSTimeInterval)currentTime
{
    CMAccelerometerData *data = self.motionManager.accelerometerData;

    //NSLog(@"data is %2@", data);

    if (fabs(data.acceleration.x) > 0.2)
        {
            [self.player.physicsBody applyForce:CGVectorMake(40 * data.acceleration.x, 0)];
        }
}

Thanks Ray Wenderlich!

I start and stop the accelerometer with the following code.

[self.motionManager startAccelerometerUpdates];
[self.motionManager stopAccelerometerUpdates];

That works great as well.

The problem I am having is when the app gets backgrounded and then comes back to the foreground. I have the accelerometer stop when going to the background and start back up when it comes to the foreground. However, if I tilt the iPhone while the app is backgrounded and then come back to the app, my sprite shoots all over the place. It is almost like the accelerometer has held onto the data in a cache while in the back ground and gets it all out at once!

To combat this I tried to use the accelerometerUpdateInterval. I NSLogged the value and it came up as 0.010000. So when the app went to the background I set this to a very high number in hopes that the accelerometer would record any more data.

self.motionManager.accelerometerUpdateInterval = 11111;

But it did not work. The character still shoots around when coming back to the app if I tilt the phone. If I keep it very steady, this does not happen. I even set the character's velocity to zero when the app comes to the foreground, but that doesn't work either. Does anyone have any idea how to deal with this problem? Thanks in advance!!

EDIT #1

This is what my update method looks like.

-(void)update:(CFTimeInterval)currentTime
{
    // Called before each frame is rendered
    NSTimeInterval delta = currentTime - self.previousUpdateTime;

    if (delta > 0.02)
    {
        delta = 0.02;
    }

    self.previousUpdateTime = currentTime;
    [self.player update:delta];
}

where previousUpdateTime is an NSTimeInterval.

EDIT #2

Using Theis' suggestion, I logged a number that started at 0 and incremented each time the method was called, I then also logged the time at which the method was called and got the following results on backgrounding the app and then bringing it to the foreground.

2014-07-12 10:42:34.838 ChargedUp[439:60b] number is 245.000000 at , 33078.175972
2014-07-12 10:42:34.855 ChargedUp[439:60b] number is 246.000000 at , 33078.192806
2014-07-12 10:42:34.871 ChargedUp[439:60b] number is 247.000000 at , 33078.209226

goes to background, comes back and I get

2014-07-12 10:42:42.762 ChargedUp[439:60b] number is 248.000000 at , 33086.082453
2014-07-12 10:42:42.798 ChargedUp[439:60b] number is 249.000000 at , 33086.125970
2014-07-12 10:42:42.803 ChargedUp[439:60b] number is 250.000000 at , 33086.142641
2014-07-12 10:42:42.822 ChargedUp[439:60b] number is 251.000000 at , 33086.159510
2014-07-12 10:42:42.837 ChargedUp[439:60b] number is 252.000000 at , 33086.176046

So it seems as though the method is getting called at regular intervals. I also logged the value of the accelerometer at the current time and got.

2014-07-12 10:50:30.367 ChargedUp[452:60b] data is x 0.288071 y -0.562469 z -0.778046 @ 33553.664755 at , 33553.704597
2014-07-12 10:50:30.382 ChargedUp[452:60b] data is x 0.286469 y -0.550476 z -0.794571 @ 33553.684229 at , 33553.721224
2014-07-12 10:50:30.399 ChargedUp[452:60b] data is x 0.292175 y -0.541321 z -0.808090 @ 33553.703698 at , 33553.737901

goes to background and comes back I get.

2014-07-12 10:50:41.716 ChargedUp[452:60b] data is x -0.241135 y -0.416245 z -0.877899 @ 33565.009330 at , 33565.036282
2014-07-12 10:50:41.756 ChargedUp[452:60b] data is x -0.253601 y -0.390457 z -0.879852 @ 33565.067755 at , 33565.087821
2014-07-12 10:50:41.765 ChargedUp[452:60b] data is x -0.253601 y -0.390457 z -0.879852 @ 33565.067755 at , 33565.104657
2014-07-12 10:50:41.783 ChargedUp[452:60b] data is x -0.246246 y -0.403030 z -0.869965 @ 33565.087311 at , 33565.121255
2014-07-12 10:50:41.799 ChargedUp[452:60b] data is x -0.250214 y -0.405685 z -0.860123 @ 33565.106754 at , 33565.137892

201

opps, guess I didn't need that extra currentTime reading!! Thanks for all your help.

EDIT#3 [SOLUTION]

Thanks to Theis' help I was able to finally figure it out. I ended up logging all the data from the accelerometer and found it did not stop when it went to the background right away. I was pausing my game by using notifications.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willResignActive) name:UIApplicationWillResignActiveNotification object:nil];

Pausing my game in the willResignActive method. However, I placed these notifications in the view controller that held my Sprite Kit Scene. So when the application actually did Resign Active, it took some time for the accelerometer to actually stop. In fact it took until I touched another window in the multitasking view. All this data was saved by the accelerometer and then used when it came back. So I just had to place my notifications in the Scene itself and all works fine now. Thanks to everyone for the insight.

2
maybe just ignore the first value you get after the app returns from background or take the first value you get as an offset or something like that ?Bastian
Have you checked if the delta is negative by chance?Theis Egeberg
@TheisEgeberg, just logged it. It starts out as something real high like 567, then drops to 1.5, then to around 0.003 as the average for the rest of the time, but that's why I put in the if statement hoping to deal with that, but no negative values.Douglas
Could it be that the accelerometer stores value which is then fires off once you reopen it? I know some other apple frameworks does this trick. So it holds a lot of buffered values that you then get when your app is returned to normal. Try logging out a simple count of the times the processMotionForUpdate is run.Theis Egeberg
@TheisEgeberg, thanks for your help, I added another edit to my post. I was thinking maybe saving that last accelerometer data, then putting that back in? but I think the data is read only.Douglas

2 Answers

1
votes

I don't think it makes much sense to have it go to background, resume and then go back to the game without interpolating between the last reading and the first one you get after coming back in. In game terms it basically means the user can "move" the device infinitely fast. That's to say that the user can tilt the device left, and then put it in background. Tilt it right while in background and open it again. There's now a HUGE jump in the accelerometer data, which means a huge jump. A way to fix it is to have the accelerometer not SET the speed of the moving physics objects, but have an intermediary variable that affects the velocity over time.

0
votes

Thanks to Theis' help I was able to finally figure it out. I ended up logging all the data from the accelerometer and found it did not stop when it went to the background right away. I was pausing my game by using notifications.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willResignActive) name:UIApplicationWillResignActiveNotification object:nil];

Pausing my game in the willResignActive method. However, I placed these notifications in the view controller that held my Sprite Kit Scene. So when the application actually did Resign Active, it took some time for the accelerometer to actually stop. In fact it took until I touched another window in the multitasking view. All this data was saved by the accelerometer and then used when it came back. So I just had to place my notifications in the Scene itself and all works fine now. Thanks to everyone for the insight.