2

I'm building a "marble" labyrinth game in order to learn spritekit basics.

I want to map the gravity of the game to the tilt of the device. I've been trying to figure out how to do it but I've only been able to map the y axis successfully:

class func obtainGravity(motionManager: CMMotionManager) {
    var vec = CGVectorMake(0, 0)
    if let attitude = motionManager.deviceMotion?.attitude? {
        let y = CGFloat(-attitude.pitch * 2 / M_PI) // This works, it returns 1/-1 when the device is vertical (1 when the home button is upside down)
        let x = CGFloat(attitude.roll * 2 / M_PI) // This doesn't work
        physicsWorld.gravity = CGVectorMake(x, y)
    }
}   

I could map the Y axis which makes the ball go "up" or "down" (relative to portrait mode) however I don't understand how to map the X axis (the pull from the side of the device).

For example, when putting the device flat on the table (x, y) should be (0,0) and when putting it on the table screen-down it should also be (0,0) however attitude.roll returns -179. Also if I keep my device vertical (on portrait mode) and turn on my feet keeping the device still, gravity should remain (x: 0, y: 1) however x continues to change as it's based on attitude.roll

How can this be done?

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
lisovaccaro
  • 32,502
  • 98
  • 258
  • 410
  • attitude.roll works for me. It returns a value in [-pi/2, pi/2]. Did you call startDeviceMotionUpdates? – 0x141E Jul 31 '14 at 21:50
  • Yes I did, however if I leave my device on the table with the screen facing down attitude.roll returns -pi which can't be right. Do you get the same results? – lisovaccaro Jul 31 '14 at 22:20
  • actually it returns -179 while it should return 0 – lisovaccaro Jul 31 '14 at 22:26
  • On a table, my attitude.roll is nearly zero. -179? Are you converting to degrees? – 0x141E Jul 31 '14 at 23:01
  • Perhaps this is a Swift issue. I'm using Objective-C. – 0x141E Jul 31 '14 at 23:09
  • I'm using swift, I'm getting 0 when the device has it's screen pointed upwards and -179 when it has it's screen towards the table. Still I'm not sure if the roll alone can be used for this, since if I turn around while holding the device on portrait mode the roll value changes, and in game gravity shouldn't change. – lisovaccaro Jul 31 '14 at 23:50
  • You may want to add a low-pass filter to the raw accelerometer values to lessen the effect of spikes. Something like _x = _x * (1-kAlpha) + data.acceleration.x * kAlpha, where _x is filtered tilt value and kAlpha, a value in [0,1], is a smoothing factor. – 0x141E Aug 01 '14 at 04:36

1 Answers1

1

The most straightforward way would be to take accelerometer updates, not device motion updates, and directly read the gravity vector from there — that's exactly what the accelerometer captures: a measure of gravity in the device's coordinate system.

Sadly I'm a Swift thickie so can't provide example code but you're looking to provide a block of type CMAccelerometerHandler which will receive a CMAccelerationData from which you can obtain CMAcceleration struct and that is, directly, the gravity vector to apply.

if let data = motionManager.accelerometerData? {
    vec = CGVectorMake(CGFloat(data.acceleration.x), CGFloat(data.acceleration.y))
}
Tommy
  • 99,986
  • 12
  • 185
  • 204
  • hi Tommy, I added the code for your suggestion. I have tried with the accelerometer but for some reason the movement in the game feels jerky. I tested deviceMotion.attitude and it suddenly everything became stable so I was trying to do it with it. Do you know if it would be possible to get values that match data.acceleration.x and data.acceleration.y but with deviceMotion? – lisovaccaro Jul 31 '14 at 23:39
  • Did you set anything custom for the `accelerometerUpdateInterval` property? That'll affect how often you get updates. It'll probably be quite low initially, since the reason the accelerometer is in the device is to spot the broad device rotation events that happen slowly and infrequently. – Tommy Jul 31 '14 at 23:41
  • no, I didn't. Maybe that's the problem, do you know what initial values I could use to test it? – lisovaccaro Jul 31 '14 at 23:44
  • `1.0/60.0` is a good value to try — it's the device's time per frame. – Tommy Jul 31 '14 at 23:47
  • data.acceleration.x and data.acceleration.y are already in [-1, 1]. You will need to remove the "* 2 / M_PI". – 0x141E Jul 31 '14 at 23:58
  • It was using 0.01 by default, so it was actually faster. Still I changed it to 1.0/60.0. I'll accept the answer as it solves the problem still the accelerometer randomly gives results that are quite off. Some time ago I asked how to get the equivalent to data.acceleration.x and data.acceleration.y from deviceMotion so I'll probably add a bounty to it. – lisovaccaro Aug 01 '14 at 01:17