Search code examples
swiftsprite-kitskspritenodeskphysicsbody

Ball shot too fast the first time


I have a sprite that represents a cannon ball, and I shoot it in this method:

func shoot() {
    let ball = Ball(radius: 8.0)
    ball.position = CGPointMake(size.width / 2.0, 72.0)
    self.addChild(ball)


    // Explosion is just a simple particle system
    let explosion = Explosion() 
    explosion.position = CGPointMake(size.width / 2.0, 64.0)
    self.addChild(explosion)

    let force = CGVectorMake(0.0, 20000.0)
    ball.physicsBody!.applyForce(force)
}

Ball is a body with mass 1.0, created this way:

init(radius: CGFloat) {
    super.init()

    let path = UIBezierPath(arcCenter: CGPointZero, radius: radius, startAngle: 0.0, endAngle: 2.0 * PI, clockwise: false)
    self.path = path.CGPath
    self.strokeColor = SKColor.blackColor()
    self.fillColor = SKColor.blackColor()

    self.name = "Ball \(self)"

    let body = SKPhysicsBody(circleOfRadius: radius)
    body.dynamic = true
    body.categoryBitMask = BallCategory
    body.contactTestBitMask = EnemyCategory
    body.collisionBitMask = EnemyCategory
    body.mass = 1.0
    body.affectedByGravity = false
    physicsBody = body
}

The problem is that the first time (and only the first time) that I shoot the ball it's super-fast. All the other times they have a different, lower speed, why?


Solution

  • It looks like you are using applyForce in the wrong way. From the docs :

    A force is applied for a length of time based on the amount of simulation time that passes between when you apply the force and when the next frame of the simulation is processed. So, to apply a continuous force to an body, you need to make the appropriate method calls each time a new frame is processed. Forces are usually used for continuous effects

    Means that you have to call applyForce: inside update: method. And from your code, it can be seen that you are not doing that (shoot() method is probably called inside touchesBegan).

    Also from the docs related to applyForce method:

    The acceleration is applied for a single simulation step (one frame).

    About super-fast ball issue... I have a theory, but can't say for sure whats going on, especially because you are not using applyForce like its meant, which I think is the main problem.

    So, here is the theory:

    A time difference between each frame can vary, especially when you are starting an app and things are loading for the first time, and because force is multiplied by that time difference you are getting weird results.

    On the other hand, applyImpulse: is perfect for your situation. Shooting a ball is an instantaneous action, and applyImpulse is not dependant on the simulation time like applyForce (see quote from docs I've posted).

    Solution:

    Use applyImpulse to shoot a ball. This is sanctioned way. If you want to move a ball with applyForce, do that through update method in each frame.

    Also consider resource preloading (preloading sounds, emitters, textures) before starting an actual gameplay which will save you from fps drops when game starts.

    Hope this helps!