Calculating Delta in Spritekit Using Swift

Calculating delta in SpriteKit using Swift

This question belongs to codereview. But I just post answer here and hope it will be migrate to the correct place along with the question.

You have some redundant code, this is my first iteration re-write

class GameScene: SKScene {
var lastUpdateTimeInterval: CFTimeInterval?

override func update(currentTime: CFTimeInterval) {

var delta: CFTimeInterval = currentTime // no reason to make it optional
if let luti = lastUpdateTimeInterval {
delta = currentTime - luti
}

lastUpdateTimeInterval = currentTime

if delta > 1.0 {
delta = minTimeInterval
// this line is redundant lastUpdateTimeInterval = currentTime
}

updateWithTimeSinceLastUpdate(delta)
}
}

and further simplified

class GameScene: SKScene {
var lastUpdateTimeInterval: CFTimeInterval = 0

override func update(currentTime: CFTimeInterval) {

var delta: CFTimeInterval = currentTime - lastUpdateTimeInterval

lastUpdateTimeInterval = currentTime

if delta > 1.0 {
delta = minTimeInterval
}

updateWithTimeSinceLastUpdate(delta)
}
}

You can replace the if with ?:, but some people just hate it for some reason

updateWithTimeSinceLastUpdate(delta > 1.0 ? minTimeInterval : delta)

How to properly calculate 1 second with deltaTime in Swift

I always use SKActions for this type of thing: (written in swift 3)

let wait = SKAction.wait(forDuration: 1.0)
let spawnSomething = SKAction.run {
//code to spawn whatever you need
}

let repeatSpawnAction = SKAction.repeatForever(SKAction.sequence([wait, spawnSomething]))

self.run(repeatSpawnAction)

Can you apply delta time to an SKAction

You cannot do much about your FPS slowing down as you add more nodes to your view. Which device is running your app also determines your FPS. Newer models have a much faster CPU.

To use delta time you can do something like this:

-(void)update:(NSTimeInterval) currentTime {
NSTimeInterval delta = currentTime - self.lastUpdateTime;
self.lastUpdateTime = currentTime;
// use the delta time to determine how much your sprites need to move to stay in sync.
}

If you are looking for a Swift version of the code, look at this previous Q&A here.

You cannot slow down or speed up an SKAction in mid run. To adjust speed of movement you will have to move your node manually by either applying a physics force such as CGVectorMake or by changing its x,y positions.


Add this property:

@property (nonatomic) NSTimeInterval lastUpdateTime;

Then in your update method:

-(void)update:(CFTimeInterval)currentTime {

NSTimeInterval delta = currentTime - self.lastUpdateTime;
self.lastUpdateTime = currentTime;

// sanity check
if(delta > 1)
delta = 0;

float distanceToMovePerSecond = 5.0f;
float numberOfFramesPerSecond = 60.0f;

float xPosition = ((distanceToMovePerSecond/numberOfFramesPerSecond) * delta) * 100;

myNode0.position = CGPointMake(myNode0.position.x+xPosition, myNode0.position.y);

}

how to get the delta of swipe/ draging touch

You can detect swipe gestures using the built-in touch handlers of SpriteKit or you can implement a UISwipeGestureRecognizer. The following is an example of how to detect swipe gestures using SpriteKit's touch handlers:

First, define the variables and constants...

Define the starting point and time of the initial touch.

var touchStart: CGPoint?
var startTime : TimeInterval?

Define constants that specify the characteristics of the swipe gesture. By changing these constants, you can detect the difference between a swipe, drag, or flick.

let minSpeed:CGFloat = 1000
let maxSpeed:CGFloat = 5000
let minDistance:CGFloat = 25
let minDuration:TimeInterval = 0.1

In touchesBegan, store the starting point and time of the initial touch

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touchStart = touches.first?.location(in: self)
startTime = touches.first?.timestamp
}

In touchesEnded, calculate the gesture's distance, duration, and speed. Compare these value against the constants to determine if the gesture was a swipe.

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touchStart = self.touchStart else {
return
}
guard let startTime = self.startTime else {
return
}
guard let location = touches.first?.location(in: self) else {
return
}
guard let time = touches.first?.timestamp else {
return
}
var dx = location.x - touchStart.x
var dy = location.y - touchStart.y
// Distance of the gesture
let distance = sqrt(dx*dx+dy*dy)
if distance >= minDistance {
// Duration of the gesture
let deltaTime = time - startTime
if deltaTime > minDuration {
// Speed of the gesture
let speed = distance / CGFloat(deltaTime)
if speed >= minSpeed && speed <= maxSpeed {
// Normalize by distance to obtain unit vector
dx /= distance
dy /= distance
// Swipe detected
print("Swipe detected with speed = \(speed) and direction (\(dx), \(dy)")
}
}
}
// Reset variables
touchStart = nil
startTime = nil
}

calculating angle between two points on edge of circle Swift SpriteKit

Given points p1, p2 on a circle with center center,
you would compute the difference vectors first:

let v1 = CGVector(dx: p1.x - center.x, dy: p1.y - center.y)
let v2 = CGVector(dx: p2.x - center.x, dy: p2.y - center.y)

Then

let angle = atan2(v2.dy, v2.dx) - atan2(v1.dy, v1.dx)

is the (directed) angle between those vectors in radians, and

var deg = angle * CGFloat(180.0 / M_PI)

the angle in degrees. The computed value can be in the range -360 .. 360, so you
might want to normalize it to the range 0 <= deg < 360 with

if deg < 0 { deg += 360.0 }

Swift: Calculate trajectory of node with applied impulse

(an answer from standpoint of physics, I'm not an expert in either swift or sprite-kit)

The choice of 'units' (and what physical distance a point represents) is entirely yours, the math that you have up to the point where you call applyImpulse() is consistent in assuming 1point = 1meter. This may or may not be what you want, but it is an OK choice - BTW, the only thing that (sort-of) fixes a point to a real-world meter in your code is the gravity value of 9.8, otherwise you can just call them point with no change in real meaning - the physics engine doesn't really care.

The consecutive ball positions that you put into array[] in your code are made on the assumption that the launched ball will cover the distance from start_of_drag to end_of_drag in one second (because you simply use the distance as a velocity value, i.e., 1m becomes 1m/sec, 2m = 2m/s, etc.).
(NOTE I don't quite know how the 12-point predicted path worked correctly in your case, because to get a vector pointing in the direction of the drag you'd normally do end_drag - start_drag, while your code does start - end)

The problem you have likely has to do with the fact that applyImpulse expects values in units of newton*second (and the velocity change will depend on the ball's mass: delta-V = impulse / mass), while you call the function with those same "distance-that-became-velocity" units.

If the ball is always stationary prior to the drag, and you want it to start instantly going somewhere at a chosen velocity, it might be easier to simply assign its velocity to the desired value and let it fly, rather than try to use applyImpulse, e.g.:

(edit: as @knight0fdragon pointed out, SK's units of distance are points, but velocity is measured in units of an abstract 'meter' = 150pt/sec, so if you did the math in points and points/sec so far, you'd have to scale the velocity before modifying a physicsBody vector: divide it by 150.0).

ball.physicsBody?.velocity = CGVector(dx:velocityX, dy:velocityY)

If the ball already has some velocity and you want to modify it by some amount (which is expressed in velocity units for you rather than in N*m) and disregarding the mass, add that velocity change to the ball's current velocity.

Move a node to finger using Swift + SpriteKit

You can save yourself a lot of trouble by using: myShip.physicsBody.applyImpluse(vector). It works by acting as if you gave myShip a push in the direction vector points. If you calculate vector as the x distance from your last touch location to myShip, then it'll accelerate, decelerate, change direction, etc. pretty close to the way you're describing because it'll be giving it little pushes in the right direction on each update.

Basically you store the last touch location then, in your update function, you calculate the CGVector pointing from myShip to lastTouch and apply that as an impulse to your physics body.

Something like:

var lastTouch: CGPoint? = nil

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}

override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}

// Be sure to clear lastTouch when touches end so that the impulses stop being applies
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
lastTouch = nil
}

override func update(currentTime: CFTimeInterval) {
// Only add an impulse if there's a lastTouch stored
if let touch = lastTouch {
let impulseVector = CGVector(touch.x - myShip.position.x, 0)
// If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
myShip.physicsBody.applyImpluse(impulseVector)
}
}

You'll also probably want to play with the linearDamping and angularDamping values on myShip.physicsBody. They'll help determine how fast myShip accelerates and decelerates.

I maxed out the values at 1.0 in my app:

myShip.physicsBody.linearDamping = 1.0
myShip.physicsBody.angularDamping = 1.0

If myShip doesn't stop fast enough for you, you can also try applying some breaking in your update function:

override func update(currentTime: CFTimeInterval) {
// Only add an impulse if there's a lastTouch stored
if let touch = lastTouch {
let impulseVector = CGVector(touch.x - myShip.position.x, 0)
// If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
myShip.physicsBody.applyImpluse(impulseVector)
} else if !myShip.physicsBody.resting {
// Adjust the -0.5 constant accordingly
let impulseVector = CGVector(myShip.physicsBody.velocity.dx * -0.5, 0)
myShip.physicsBody.applyImpulse(impulseVector)
}
}

Simulate universal gravitation for two Sprite Kit nodes

You can loop through all nodes and calculate the impulse to all other nodes using the appropriate ratios of the universal gravitation equation. I just wrote a quick example showing how this is done. You can make your own custom "mass" factor, however I'm simply using Sprite Kit's. I also added a strength factor to amplify the impulse. I'm also assuming fixed time step of 1/60 seconds.

class GameScene: SKScene {
var nodes: [SKShapeNode] = []
let dt: CGFloat = 1.0/60.0 //Delta time.
let radiusLowerBound: CGFloat = 1.0 //Minimum radius between nodes check.
let strength: CGFloat = 10000 //Make gravity less weak and more fun!
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVector()
for i in 1 ... 50 { //Create 50 random nodes.
let rndRadius = 15 + CGFloat(arc4random_uniform(20))
let rndPosition = CGPoint(x: CGFloat(arc4random_uniform(UInt32(self.size.width))), y: CGFloat(arc4random_uniform(UInt32(self.size.height))))
let node = SKShapeNode(circleOfRadius: rndRadius)
node.position = rndPosition
node.physicsBody = SKPhysicsBody(circleOfRadius: rndRadius)
self.addChild(node)
nodes.append(node)
}
}
override func update(currentTime: NSTimeInterval) {
for node1 in nodes {
for node2 in nodes {
let m1 = node1.physicsBody!.mass*strength
let m2 = node2.physicsBody!.mass*strength
let disp = CGVector(dx: node2.position.x-node1.position.x, dy: node2.position.y-node1.position.y)
let radius = sqrt(disp.dx*disp.dx+disp.dy*disp.dy)
if radius < radiusLowerBound { //Radius lower-bound.
continue
}
let force = (m1*m2)/(radius*radius);
let normal = CGVector(dx: disp.dx/radius, dy: disp.dy/radius)
let impulse = CGVector(dx: normal.dx*force*dt, dy: normal.dy*force*dt)

node1.physicsBody!.velocity = CGVector(dx: node1.physicsBody!.velocity.dx + impulse.dx, dy: node1.physicsBody!.velocity.dy + impulse.dy)
}
}
}
}

Sample Image

Instead of performing the calculation manually you could also add field nodes to your physics bodies to simulate the effect. Although be warned, field nodes are broken in certain versions of Sprite Kit.



Related Topics



Leave a reply



Submit