Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a sprite jump to a specific height with SpriteKit?

I am trying to use applyImpulse to make a sprite jump to a specific height. In the example code below, the dynamic black circle jumps to the same height as the static red circle, but it only works with a kludge.

If my physics is right, the required initial vertical momentum to launch a projectile to height Y is given by mass * sqrt(2 * gravity * Y). Yet this formula results in the black circle moving very little.

Through trial and error, I have discovered that I can make the red circle jump more or less accurately by multiplying the vertical component of the impulse vector by 12.3, as illustrated in the code below.

This seems completely arbitrary and is driving me crazy. I am obviously doing something wrong. What's the right way to do this?

Here's what I think is the relevant bit of code:

let dy = mass * sqrt( 
    2 * -self.physicsWorld.gravity.dy 
    * (fixedCircle.position.y-jumpingCircle.position.y)) 
    * 12.3

jumpingCircle.physicsBody?.applyImpulse(CGVectorMake(0, CGFloat(dy)))

Here's the GameScene.swift class in its entirety, should you wish to copy and paste...

import SpriteKit

class GameScene: SKScene {

var jumpingCircle = SKShapeNode()
var fixedCircle = SKShapeNode()

override func didMoveToView(view: SKView) {

    self.scaleMode = .AspectFit

    // Create an exterior physical boundary
    self.physicsBody = SKPhysicsBody(edgeLoopFromRect:self.frame)
    self.physicsWorld.gravity = CGVectorMake(0, -5);


    // Create the jumping circle
    jumpingCircle = SKShapeNode(circleOfRadius: 50)
    jumpingCircle.position = CGPoint(x: 100,y: 0)
    jumpingCircle.strokeColor = .blackColor()
    jumpingCircle.physicsBody = SKPhysicsBody(circleOfRadius: 50)
    jumpingCircle.physicsBody?.linearDamping = 0.0
    self.addChild(jumpingCircle)


    // Create the fixed circle
    fixedCircle = SKShapeNode(circleOfRadius: 50)
    fixedCircle.position = CGPoint(x: 100,y: 384)
    fixedCircle.strokeColor = .redColor()
    self.addChild(fixedCircle)

}

override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {

    // to reach certain height, initial y velocity should by sqrt(2 * gravity * Y)
    // momentum required to reach this velocity is mass * velocity
    // therefore, vertical impulse should be given by mass * sqrt(2 * gravity * Y)

    if let mass = jumpingCircle.physicsBody?.mass{
        // calculate vertical impulse

        // this formula "should work" but does not
        // let dy = mass * sqrt(2 * -self.physicsWorld.gravity.dy * (fixedCircle.position.y-jumpingCircle.position.y))

        // this formula "works", but has the arbitrary multiplier
        let dy = mass * sqrt(2 * -self.physicsWorld.gravity.dy * (fixedCircle.position.y-jumpingCircle.position.y)) * 12.3

        // apply the Impulse
        jumpingCircle.physicsBody?.applyImpulse(CGVectorMake(0, CGFloat(dy)))
     }
     }
 }
like image 711
Michael Ames Avatar asked Apr 13 '15 02:04

Michael Ames


1 Answers

Figured it out.

SpriteKit apparently has an undocumented pixel-to-"meter" ratio of 150.

I discovered this when I realized the mass SpriteKit was automatically calculating for my circles was not 50*pi*r^2 as it should be. I worked backward from mass to calculate the radius SpriteKit was using, and it was 7500, which happens to be 50*150.

And 12.3? It just happens to be (approximately) the square root of 150.

So to make these physics simulations work, you have to consider this ratio. I'm calling it "pixel to unit" (PTU) because it has nothing to do with meters in spite of Apple's insistence that SpriteKit uses SI units. But because it's undocumented it seems possible to change, so I'm kicking off my simulation using the following line of code to determine the true PTU:

let ptu = 1.0 / sqrt(SKPhysicsBody(rectangleOfSize: CGSize(width:1,height:1)).mass)

This is an expensive operation, so it should only be called once. I'm calling it when setting up the initial Scene.

My impulse calculation is now as follows:

let dy = mass * sqrt(2 
    * -self.physicsWorld.gravity.dy 
    * (fixedCircle.position.y-jumpingCircle.position.y 
    * ptu))

And now the black circle jumps to perfect alignment with the red circle and I can go back to sleeping at night.

like image 139
Michael Ames Avatar answered Nov 01 '22 00:11

Michael Ames