2
votes

I've been looking around for a way to add an offset to the way the accelerometer tracks movement of the player in my game. In my game, you control the player character by tilting the phone forwards and backwards. This is translated by the accelerometer to x and y coordinates on screen. unfortunately, this means in order to stop the character from constantly being at the bottom of the screen, you have to hold the phone flat. Which isn't very comfortable for players.

So I'm trying to implement a way that whatever the angle of the phone is at when they start a game session, that is the default angle and then the tilt is determined by the delta of that angle rather than the raw data which would be against 0,0 I guess...

here is my attempt at figuring it out:

func instantiateAcceleration() {
    if motionManager.accelerometerAvailable{
        let queue = NSOperationQueue()


        motionManager.startAccelerometerUpdatesToQueue(queue) { data, error in
            let currentX = self.player.position.x
            let currentY = self.player.position.y

            if(!self.hasCapturedOffset){

                /* So the first time we enter this loop. we'll cature the offset */

                self.offsetY = data!.acceleration.y;
                self.offsetX = data!.acceleration.x;

                print("captured offset : x - \(self.offsetX) y - \(self.offsetY)")
                self.hasCapturedOffset = true;
            }

            /* x coords */
            if data!.acceleration.y < (0 - self.offsetY) {
                self.destX = currentX + CGFloat(data!.acceleration.y * 100)
            }

            else if data!.acceleration.y > (0 + self.offsetY) {
                self.destX = currentX + CGFloat(data!.acceleration.y * 100)
            }

            /* y coords */
            if data!.acceleration.x > (0.1 - self.offsetX){
                self.destY = currentY - CGFloat(data!.acceleration.x * 100)
            }

            else if data!.acceleration.x < (0.1 + self.offsetX) {
                self.destY = currentY - CGFloat(data!.acceleration.x * 100)
            }

            /* keep it in bounds */
            if(self.destY > self.size.height){
                self.destY = self.size.height;

            }else if(self.destY < 0){
                self.destY = 0;
            }

            if(self.destX > self.size.width){
                self.destX = self.size.width;

            }else if(self.destX < 0){
                self.destX = 0;
            }

        }

    } else {
        print("Accelerometer is not available")
    }

}

that above func is called on the viewDidLoad and the self.DestX, self.DestY are used for updating the player in the update method:

override func update(currentTime: CFTimeInterval) {
    /* Called before each frame is rendered */
    /* Accelerometer data */
    let action = SKAction.moveTo(CGPoint(x:destX, y:destY), duration: 0.1)
    self.player.runAction(action)

}

I would really like to learn how to do this properly, I'm aware there are definitely some maths behind this is that I'm not familiar with. If somebody could point me in the right direction, I would really appreciate it :)

1

1 Answers

2
votes

You can store the starting orientation as referenceAttitude and use

 currentattitude.multiplyByInverseOfAttitude(referenceAttitude) 

to calculate the movement.

Here is a code sample I did some time ago:

import Foundation
import CoreMotion

// Damping factor
let cLowPassFactor: Float = 0.95

class MotionManagerSingletonSwift: NSObject {


var motionManager: CMMotionManager
var referenceAttitude:CMAttitude?=nil
var bActive = false
var lastVector:[Float] = [0.0, 0.0, 0.0]


override init()  {
    motionManager=CMMotionManager()
    motionManager.deviceMotionUpdateInterval = 0.25
    motionManager.startDeviceMotionUpdates()
    bActive=true;
}

// only one instance of CMMotionManager can be used in your project.
// => Implement as Singleton which can be used in the whole application
class var sharedInstance: MotionManagerSingletonSwift {
struct Singleton {
    static let instance = MotionManagerSingletonSwift()
    }
    return Singleton.instance
}

class func getMotionManager()->CMMotionManager {
    if (sharedInstance.bActive==false) {
        sharedInstance.motionManager.startDeviceMotionUpdates()
        sharedInstance.bActive=true;

    }
    return sharedInstance.motionManager
}

// Returns an array with the movements
// At the first time a reference orientation is saved to ensure the motion detection works
// for multiple device positions
class func getMotionVectorWithLowPass() -> [Float] {
    // Motion
    var attitude: CMAttitude? = getMotionManager().deviceMotion?.attitude

    if sharedInstance.referenceAttitude==nil {
        // Cache Start Orientation to calibrate the device. Wait for a short time to give MotionManager enough time to initialize
        dispatch_after(250, dispatch_get_main_queue(), {
            MotionManagerSingletonSwift.calibrate()
            })
    } else if attitude != nil {
        // Use start orientation to calibrate
        attitude!.multiplyByInverseOfAttitude(sharedInstance.referenceAttitude)
    }

    if attitude != nil {
        return lowPassWithVector([Float(attitude!.yaw), Float(attitude!.roll), Float(attitude!.pitch)])
    } else {
        return [0.0, 0.0, 0.0]
    }
}

// Stop collection motion data to save energy
class func stop() {
    sharedInstance.motionManager.stopDeviceMotionUpdates()
    sharedInstance.referenceAttitude=nil
    sharedInstance.bActive=false
}

// Calibrate motion manager with a ne reference attitude
class func calibrate() {
    sharedInstance.referenceAttitude = getMotionManager().deviceMotion?.attitude?.copy() as? CMAttitude
}


// Damp the jitter caused by hand movement
class func lowPassWithVector(var vector:[Float]) -> [Float]
{
    vector[0] = vector[0] * cLowPassFactor + sharedInstance.lastVector[0] * (1.0 - cLowPassFactor)
    vector[1] = vector[1] * cLowPassFactor + sharedInstance.lastVector[1] * (1.0 - cLowPassFactor)
    vector[2] = vector[2] * cLowPassFactor + sharedInstance.lastVector[2] * (1.0 - cLowPassFactor)

    sharedInstance.lastVector = vector
    return sharedInstance.lastVector
}
}

Full tutorial: http://developerplayground.net/?p=19