Marshall D
Marshall D

Reputation: 454

SpriteKit camera actions being very jerky/laggy

I'm using Swift 3.0 and Xcode 8.2.1, testing on an iPhone 6s running iOS 10.2.

I'm simply attempting to rotate an SKCameraNode at a rate that is slowing down (I.E. it has both an angular velocity and acceleration).

This is my current solution, and yes I know it's weird:

 cam.run(
    SKAction.repeatForever(
        SKAction.sequence([
            SKAction.run
                {
                    self.cam.run(SKAction.rotate(toAngle: 0.0, duration: 0.7, shortestUnitArc: true))
                },
            SKAction.wait(forDuration: 0.1)])))

The camera's zRotation starts at some non-zero place, and then this is called. It then calls the SKAction.rotate part every 0.1 seconds. It's not shown here, but I have it terminate after 3 seconds.

This provides the exact effect that I want, because every time the SKAction is called, the zRotation is closer to its 0.0, but the time it needs to take to get there stays at 0.7, so the rate at which it approaches 0.0 slows down.

HOWEVER, 1 of 3 things happen from this:

  1. It works perfectly as intended
  2. It feels like it stops and continues every 0.1 seconds
  3. The camera just immediately and completely stops functioning as a camera as soon as the SKAction is called, and the player is just stuck looking at the same spot till the cameras are switched.

I tried reducing the waitForDuration from 0.1 to 0.01 but then it always does option 3.

Is there some sort of "rules of execution" that I'm not following when it comes to using cameras?

Thanks!

Upvotes: 0

Views: 178

Answers (1)

0x141E
0x141E

Reputation: 12753

You can use a single rotate(toAngle:) action that decelerates when the camera's zRotation is close to the ending angle and setting the timingMode property to .easeOut. For example,

cam.zRotation = CGFloat.pi
let action = SKAction.rotate(toAngle: 0, duration: duration, shortestUnitArc: true)

// Slows when zRotation is near 0
action.timingMode = .easeOut

cam.run(action)

Alternatively, you can define your own timing function to customize the ease-out behavior by replacing timingMode statement with

action.timingFunction = {
    time in
    // Custom ease-out, modify this as needed
    return 1-pow(1-time, 5)
 }

Moreover, you can use the following to calculate the action's duration so the angular velocity and acceleration are consistent regardless of the starting angle (i.e., the amount of rotation)

let duration = TimeInterval(normalizedArcFromZero(angle:cam.zRotation)) * 3

func normalizedArcFromZero(angle:CGFloat) -> CGFloat {
    let diff = angle.mod(dividingBy:CGFloat.pi*2)
    let arc = CGFloat.pi - (diff + CGFloat.pi).mod(dividingBy:2 * CGFloat.pi)
    return abs(arc)/CGFloat.pi
}

Lastly, since the above requires a modulo function that performs a "floored" division (instead of Swift's truncatingRemainder), you'll need to add this to your project

extension CGFloat {
    func mod(dividingBy x:CGFloat) -> CGFloat {
        return self - floor(self/x) * x
    }
}

Upvotes: 2

Related Questions