accelerometer data not correct, delayed for few seconds - ios

I am creating a very simple game using Swift and SpriteKit and I am moving a ball on the screen using the accelerometer data (acceleration x,y).
I would say the code works fine but I have noticed that sometimes (often right when I open the app) the accelerometer data is not correct and delayed for few seconds.
Why is that happening?
I am using the following code to read the accelerometer data:
if motionManager.accelerometerAvailable == true {
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.currentQueue(), withHandler:{
data, error in
self.accX = CGFloat(data.acceleration.x)
self.accY = CGFloat(data.acceleration.y)
})
}
And the function update to apply some impulse to the ball:
override func update(currentTime: CFTimeInterval) {
var impulse = CGVectorMake(accX, accY)
var obj = childNodeWithName("ball") as SKSpriteNode
obj.physicsBody?.applyImpulse(impulse)
}
Am i missing something?
Thank you

With any accelerometer data, it is a good idea to run it through a filter to smooth out any irregular spikes. Here is my favorite:
double filteredAcceleration[3];
memset(filteredAcceleration, 0, sizeof(filteredAcceleration));
CMAccelerometerData *newestAccel = motionManager.accelerometerData;
filteredAcceleration[0] = (filteredAcceleration[0]*(1.0-alpha)) + (newestAccel.acceleration.x*alpha);
filteredAcceleration[1] = (filteredAcceleration[1]*(1.0-alpha)) + (newestAccel.acceleration.y*alpha);
filteredAcceleration[2] = (filteredAcceleration[2]*(1.0-alpha)) + (newestAccel.acceleration.z*alpha);
alpha can be any value from 0 to 1. The closer to 1 the more responsive it will be, the closer to zero the more smooth it will be. My favorite value on the iPhone is 0.2 It is a good compromise for smooth yet responsive for a game like doodle jump, or possibly moving a ball around.
I don't know why the accelerometer data is incorrect/delayed on startup, my guess would be that the hardware has to wake up and calibrate itself, but regardless of the why, if you implement a filter, it will smooth out these irregularities, and they won't be nearly as noticeable.

I have given priority to both functions and the issue seems fixed.
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
dispatch_async(dispatch_get_main_queue()) {
// code with priority
}
}

Related

Detect when a chopping motion has been made - Swift

I'm starting with my first app for iOS and I am trying to get gyro data to play a whip sound when you flick your phone.
From what I can tell, I should be using CoreMotion to get the state of the gyro, then doing some math to work out when a whip-like gesture is made, and then to run my function?
This is what I have so far - this is my ContentView.swift file.
import SwiftUI
import AVFoundation
import CoreMotion
let popSound = Bundle.main.url(forResource: "whip", withExtension: "mp3")
var audioPlayer = AVAudioPlayer()
var motionManager: CMMotionManager!
func audioPlayback() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: popSound!)
audioPlayer.play()
} catch {
print("couldn't load sound file")
}
}
struct ContentView: View {
var body: some View {
Text("Press button!")
Button(action: {
audioPlayback()
}, label: {
Text("Press me!")
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Currently it's set to a button. Can someone link me to a resource, or walk me though this?
Usually when dealing with such devices you can either get a current snapshot or you can request to get a feed of snapshot changes. In your case the promising methods seem to be startAccelerometerUpdates and startDeviceMotionUpdates for CMMotionManager. I am pretty sure somewhere in there should be sufficient information to nearly detect a gesture you are describing.
If you dig into these methods you will see that you get a feed of "frames" where each frame describes a situation at certain time.
Since you are detecting a gesture you are not interested into a single frame but rather a series of frames. So probably the first thing you need is some object to which you can append frames and this object will evaluate if current set of frames corresponds to your gesture or not. It should also be able to discard data which is not interesting. For instance frames older than 3 seconds can be discarded as this gesture should never need more than 3 seconds.
So now your problem is split into 2 parts. First part is creating an object that is able to collect frames. I would give it a public method like appendFrame or appendSnapshot. Then keep collecting frames on it. The object also needs to be able to report back that it has detected a required gesture so that you play a sound at that point. Without the detection you should be able to mock for instance that after 100 frames the buffer is cleared and that notification is reported back which then triggers the sound. So no detection at this point but everything else.
The second part is the detection itself. You now have a pool of samples, frames or snapshots. You can at any time aggregate data anyway you want to. You would probably use a secondary thread to process the data so the UI is not laggy and to be able to throttle how much CPU power you put into it. As for the detection itself I would say you may try to create some samples and try figure out the "math" part. When you have some idea or can at least preset the community with some recordings you could ask another specific question about that. It does look like a textbook example to use Machine Learning for instance.
From mathematical point of view there may be some shortcuts. A very simple example would be just looking at the direction of your device as normalized direction(x, y, z). I think you can actually already get that very easily from native components. In a "chopping" motion we expect that rotation suddenly (nearly) stopped and was recently (nearly) 90 degrees offset from current direction.
Speed:
Assuming you have an array of direction such as let directions[(x: CGFloat, y: CGFloat, z: CGFloat)] then you could identify some rotation speed changes with length of cross product.
let rotationSpeed = length(cross(directions[index], directions[index+1]))
the speed should always be a value between 0 and 1 where a maximum of 1 would mean 90 degrees change. Hope it never comes to that and you are always in values between 0 and 0.3. If you DO get to values larger than 0.5 then frame-rate of your device is too low and samples are best just discarded.
Using this approach you can map your rotations from array of vectors to array of speeds rotationSpeeds: [Float] which becomes more convenient for you. You are now looking within this array if there is a part where the rotation speed suddenly drops from high value to low value. What those values are you will need to test yourself and tweak them. But a "sudden drop" may not be on only 2 sequential samples. You need to find for instance 5 high speed frames followed by 2 low speed frames. Rather even more than that.
Now that you found such a point you found a candidate for end of your chop. At this point you can now go backwards and check all frames going back in time up to somewhere between 0.5 and 1.0 seconds from candidate (again a value you will need to try out yourself). If any of this frame is nearly 90 degrees away from candidate then you have your gesture. Something like the following should do:
length(cross(directions[index], directions[candidateIndex])) > 0.5
where the 0.5 is again something you will need to test. The closer to 1.0 the more precise the gesture needs to be. I think 0.5 should be pretty good to begin with.
Perhaps you can play with the following and see if you can get satisfying results:
struct Direction {
let x: Float
let y: Float
let z: Float
static func cross(_ a: Direction, _ b: Direction) -> Direction {
Direction(x: a.y*b.z - a.z*b.y, y: a.z*b.x - a.x*b.z, z: a.x*b.y - a.y*b.z) // Needs testing
}
var length: Float { (x*x + y*y + z*z).squareRoot() }
}
class Recording<Type> {
private(set) var samples: [Type] = [Type]()
func appendSample(_ sample: Type) { samples.append(sample) }
}
class DirectionRecording: Recording<Direction> {
func convertToSpeedRecording() -> SpeedRecording {
let recording = SpeedRecording()
if samples.count > 1 { // Need at least 2 samples
for index in 0..<samples.count-1 {
recording.appendSample(Direction.cross(samples[index], samples[index+1]).length)
}
}
return recording
}
}
class SpeedRecording: Recording<Float> {
func detectSuddenDrops(minimumFastSampleCount: Int = 4, minimumSlowSampleCount: Int = 2, maximumThresholdSampleCount: Int = 2, minimumSpeedTreatedAsHigh: Float = 0.1, maximumSpeedThresholdTreatedAsLow: Float = 0.05) -> [Int] { // Returns an array of indices where sudden drop occurred
var result: [Int] = [Int]()
// Using states to identify where in the sequence we currently are.
// The state should go none -> highSpeed -> lowSpeed
// Or the state should go none -> highSpeed -> thresholdSpeed -> lowSpeed
enum State {
case none
case highSpeed(sequenceLength: Int)
case thresholdSpeed(sequenceLength: Int)
case lowSpeed(sequenceLength: Int)
}
var currentState: State = .none
samples.enumerated().forEach { index, sample in
if sample > minimumSpeedTreatedAsHigh {
// Found a high speed sample
switch currentState {
case .none: currentState = .highSpeed(sequenceLength: 1) // Found a first high speed sample
case .lowSpeed: currentState = .highSpeed(sequenceLength: 1) // From low speed to high speed resets it back to high speed step
case .thresholdSpeed: currentState = .highSpeed(sequenceLength: 1) // From threshold speed to high speed resets it back to high speed step
case .highSpeed(let sequenceLength): currentState = .highSpeed(sequenceLength: sequenceLength+1) // Append another high speed sample
}
} else if sample > maximumSpeedThresholdTreatedAsLow {
// Found a sample somewhere between fast and slow
switch currentState {
case .none: break // Needs to go to high speed first
case .lowSpeed: currentState = .none // Low speed back to threshold resets to beginning
case .thresholdSpeed(let sequenceLength):
if sequenceLength < maximumThresholdSampleCount { currentState = .thresholdSpeed(sequenceLength: sequenceLength+1) } // Can still stay inside threshold
else { currentState = .none } // In threshold for too long. Reseting back to start
case .highSpeed: currentState = .thresholdSpeed(sequenceLength: 1) // A first transition from high speed to threshold
}
} else {
// A low speed sample found
switch currentState {
case .none: break // Waiting for high speed sample sequence
case .lowSpeed(let sequenceLength):
if sequenceLength < minimumSlowSampleCount { currentState = .lowSpeed(sequenceLength: sequenceLength+1) } // Not enough low speed samples yet
else { result.append(index); currentState = .none } // Got everything we need. This is a HIT
case .thresholdSpeed: currentState = .lowSpeed(sequenceLength: 1) // Threshold can always go to low speed
case .highSpeed: currentState = .lowSpeed(sequenceLength: 1) // High speed can always go to low speed
}
}
}
return result
}
}
func recordingContainsAChoppingGesture(recording: DirectionRecording, minimumAngleOffset: Float = 0.5, maximumSampleCount: Int = 50) -> Bool {
let speedRecording = recording.convertToSpeedRecording()
return speedRecording.detectSuddenDrops().contains { index in
for offset in 1..<maximumSampleCount {
let sampleIndex = index-offset
guard sampleIndex >= 0 else { return false } // Can not go back any further than that
if Direction.cross(recording.samples[index], recording.samples[sampleIndex]).length > minimumAngleOffset {
return true // Got it
}
}
return false // Sample count drained
}
}

Accelerometer freak out

I am testing my app, and i am meant to be getting the readings from the accelometer...pretty simple! right now , the code says, if the acceleration is above 0.005, start adding 1 to a value... however for some reason, when i rotate the ipad, , so it seems to be 'standing on one of its edges' like a diamond, the value seems to be increasing? it increases even if the ipad is completely still.
Here is the code :
motionManager.accelerometerUpdateInterval = 0.02
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { (data,error) in
if let myData = data {
if myData.acceleration.y > 0.05 {
self.damian += 1
print(data)
}
I'm not sure but based on documentation it looks like you're getting gravity + userAcceleration. So it's pretty normal to get non-zero values for y-axis in some device positions.
The total acceleration of the device is equal to gravity plus the acceleration the user imparts to the device (
userAcceleration
).
To get userAcceleration you should call startDeviceMotionUpdates(to:withHandler:) - https://developer.apple.com/documentation/coremotion/cmmotionmanager/1616048-startdevicemotionupdates

How to display current speed in MPH based on data from GPS and Accelerometer. I have an idea, but having difficulty executing it

I apologize for not knowing the proper terminology for everything here. I'm a fairly new programmer, and entirely new to Swift. The task I'm trying to accomplish is to display a current speed in MPH. I've found that using the "CoreLocation" and storing locations in an array and using "locations.speed "to display the speed is quite slow and does not refresh as often as I want.
My thought was to get an initial speed value using the "MapKit" and "CoreLocation" method, then feed that initial speed value into a function using the accelerometer to provide a quicker responding speedometer. I would do this by integrating the accelerometer values and adding the initial velocity. This was the best solution I could come up with to get a more accurate speedometer with a better refresh rate.
I'm having a couple of issues currently:
First Issue: I don't know how to get an initial speed value from a function using location data as parameters into a function using accelerometer data as parameters.
Second Issue: Even when assuming an initial speed of 0, my current program displays a value that keeps increasing infinitely. I'm not sure what the issue is that is causing this.
I will show you the portion of my code responsible for this, and would appreciate any insight any of you may have!
For my First Issue, here is my GPS data Function:
func provideInitSpeed(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])->Double {
let location = locations[0]
return ((location.speed)*2.23693629) //returns speed value converted to MPH
}
I'm not sure how to make a function call to retrieve this value in my Accelerometer function.
For my Second Issue, here is my Accelerometer Function with assumed starting speed of 0:
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) {
(data,error) in
if let myData = data {
//getting my acceleration data and rounding the values off to the hundredths place to reduce noise
xAccel = round((myData.acceleration.x * g)*100)/100
yAccel = round((myData.acceleration.y * g)*100)/100
zAccel = round((myData.acceleration.z * g)*100)/100
// Integrating accel vals to get velocity vals *Possibly where error occurs* I multiply the accel values by the change in time, which is currently set at 0.2 seconds.
xVel += xAccel * self.motionManager.accelerometerUpdateInterval
yVel += yAccel * self.motionManager.accelerometerUpdateInterval
zVel += zAccel * self.motionManager.accelerometerUpdateInterval
// Finding total speed; Magnitude of Velocity
totalSpeed = sqrt(pow(xVel,2) + pow(yVel,2) + pow(zVel,2))
// if-else statment for further noise reduction. note: "zComp" just adjusts for the -1.0 G units z acceleration value that the phone reads by default for gravity
if (totalSpeed - zComp) > -0.1 && (totalSpeed - zComp) < 0.1 {
self.view.reloadInputViews()
self.speedWithAccelLabel.text = "\(0.0)"
} else {
// Printing totalSpeed
self.view.reloadInputViews()
self.speedWithAccelLabel.text = "\(abs(round((totalSpeed - zComp + /*where initSpeed would go*/)*10)/10))"
}
}//data end
}//motionManager end
I'm not sure why but the speed this function displays is always increasing by about 4 mph every refresh of the label.
This is my first time using Stack Overflow, so I apologize for any stupid mistakes I might have made!
Thanks a lot!

Exponentially shrink an SKNode

In a game I'm developing with SpriteKit, I want certain objects to appear and shrink. I already know how to scale them down, and I'm achieving this using the following code:
myNode.run(SKAction.scale(to: 0, duration: 3))
However, the shrinking happens 'linearly'. Is there a way to make it shrink exponentially faster? Or at least that it starts slowly and at the last second it shrinks twice as fast?
Sorry I could not test this out, I do not have a compiler. It is properly not the best way to do it, but I gave it a shot:
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
let totalRunTime = 1
var add = 0.0
var scale = 1
var done = false
while !done{
delay(add, closure: {
add += 0.1
scale = 1 - (add ^2)
myNode.run(SKAction.scale(to: scale, duration: totalRunTime / 10))
})
if add == 0{
done = true
}
}
Edit: When I look at my code I may see a bug: maybe you need to switch the exponential formula from scale to duration to make it work, I can not test it now :(
Have a look at the Sprite Kit Utils from Ray Wenderlich. It's quite a useful library and also provides easing functions for movement, scale, and rotate actions.
(For reference, have a look at the different easing functions demonstrated on easings.net)
Hope that helps!

Any way to speed up gameplay gradually in Swift?

I'm currently working on a game using Spritekit. The game has objects which spawn at the top of the screen and fall towards the player character, and the game ends when the player character collides with any of the objects. I am trying to find a way to gradually speed up gameplay over time to make the game more difficult (i.e. objects fall at normal speed when the game begins, after 5 seconds speed up 50%, after 5 more seconds speed up another 50%, ad infinitum.)
Would I need to use NSTimer to make a countdown to increase the gravity applied to the falling objects? Sorry if this is a basic thing, I'm kind of new to programming.
Thanks, Jake
EDIT:
My spawn method for enemies-
let spawn = SKAction.runBlock({() in self.spawnEnemy()})
let delay = SKAction.waitForDuration(NSTimeInterval(2.0))
let spawnThenDelay = SKAction.sequence([spawn, delay])
let spawnThenDelayForever = SKAction.repeatActionForever(spawnThenDelay)
self.runAction(spawnThenDelayForever)
And my method for making the enemies fall-
func spawnEnemy() {
let enemy = SKNode()
let x = arc4random()
fallSprite.physicsBody = SKPhysicsBody(rectangleOfSize: fallSprite.size)
fallSprite.physicsBody.dynamic = true
self.physicsWorld.gravity = CGVectorMake(0.0, -0.50)
enemy.addChild(fallSprite)
}
In spawnEnemy(), you set self.physicsWorld.gravity. Move this line to your update: method.
If you are not keeping track of the game's duration right now, you will want to implement that. You can use the parameter of the update: method to accomplish this.
You can then use the game duration to change the gravity.
For example,
override func update(currentTime: CFTimeInterval) {
if gameState == Playing{
//update "duration" using "currentTime"
self.physicsWorld.physicsBody = CGVectorMake(0.0, -0.50 * (duration / 10.0))
}
}
10.0 can be changed depending on how fast you want the gravity to increase. A higher number makes it change less drastically, and a smaller number makes the gravity increase quite rapidly.
Hopefully this answers your question.

Resources