Gravity value in SpriteKit game scene

429 views Asked by At

I'm trying to create a game using Apple's SpriteKit game engine.

While implementing some physics-based calculations in the game, I noticed that the calculated results differ from what effectively then happens to objects.

Example: calculating a body's trajectory through projectile motion's equations causes the body to actually fall down much sooner/quicker than what calculated.

How can I make the physics engine match the real-world physics laws when calculating something gravity-related?

2

There are 2 answers

4
Steve Ives On BEST ANSWER

I think I know what's going on with the sample code you have supplied on GitHub, which I'll reproduce here as questions on SO should contain the code:

//
//  GameScene.swift
//  SpriteKitGravitySample
//
//  Created by Emilio Schepis on 17/01/2020.
//  Copyright © 2020 Emilio Schepis. All rights reserved.
//

import SpriteKit
import GameplayKit

class GameScene: SKScene {

    private var subject: SKNode!

    override func didMove(to view: SKView) {
        super.didMove(to: view)

        // World setup (no friction, default gravity)
        // Note that this would work with any gravity set to the scene.
        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
        physicsBody?.friction = 0

        subject = SKShapeNode(circleOfRadius: 10)
        subject.position = CGPoint(x: frame.midX, y: 30)

        subject.physicsBody = SKPhysicsBody(circleOfRadius: 10)
        subject.physicsBody?.allowsRotation = false

        // Free falling body (no damping)
        subject.physicsBody?.linearDamping = 0
        subject.physicsBody?.angularDamping = 0

        addChild(subject)

        // Set an arbitrary velocity to the body
        subject.physicsBody?.velocity = CGVector(dx: 30, dy: 700)

        // Inaccurate prediction of position over time
        for time in stride(from: CGFloat(0), to: 1, by: 0.01) {
            let inaccuratePosition = SKShapeNode(circleOfRadius: 2)
            inaccuratePosition.strokeColor = .red

            // These lines use the projectile motion equations as-is.
            // https://en.wikipedia.org/wiki/Projectile_motion#Displacement
            let v = subject.physicsBody?.velocity ?? .zero
            let x = v.dx * time
            let y = v.dy * time + 0.5 * physicsWorld.gravity.dy * pow(time, 2)

            inaccuratePosition.position = CGPoint(x: x + subject.position.x,
                                                  y: y + subject.position.y)
            addChild(inaccuratePosition)
        }

        // Actual prediction of position over time
        for time in stride(from: CGFloat(0), to: 1, by: 0.01) {
            let accuratePosition = SKShapeNode(circleOfRadius: 2)
            accuratePosition.strokeColor = .green

            // These lines use the projectile motion equations
            // as if the gravity was 150 times stronger.
            // The subject follows this curve perfectly.
            let v = subject.physicsBody?.velocity ?? .zero
            let x = v.dx * time
            let y = v.dy * time + 0.5 * physicsWorld.gravity.dy * 150 * pow(time, 2)

            accuratePosition.position = CGPoint(x: x + subject.position.x,
                                                y: y + subject.position.y)
            addChild(accuratePosition)
        }

    }

}

What you've done is to:

  1. Created an object called subject with a physicsBody and placed it on screen with a initial velocity.
  2. Plotted predicted positions for an object with that velocity under gravity via the inaccuratePosition node, using Newton's laws of motion (v = ut + 1/2at²)
  3. Plotted predicted positions for an object with that velocity under gravity * 150 via the accuratePosition node, using Newton's laws of motion

All this is is didMoveTo. When the simulation runs, the path of the node subject follows the accuratePosition path accurately.

I think what's happening is that you are calculating the predicted position using subject's physicsBody's velocity, which is in m/s, but the position is in points, so what you should do is convert m/s into point/s first.

So what's the scale factor? Well from Apple's documentation here; it's.... 150 which is too much of a coincidence , so I think that's the problem.

Bear in mind that you set the vertical velocity of your object to 700m/s - that's 1500mph or 105000 SK point/s. You'd expect it to simply disappear out through the top of the screen at high speed, as predicted by your red path. The screen is somewhere between 1,000 and 2,000 points.

8
Emilio Schepis On

Edit - I created a sample project to demonstrate the calculated paths with and without the multiplier.

https://github.com/emilioschepis/spritekit-gravity-sample


TL;DR - When calculating something gravity-related in SpriteKit multiply the gravity of the scene by 151 to obtain an accurate result.


When trying to solve this issue I first started reading the SpriteKit documentation related to gravity:

https://developer.apple.com/documentation/spritekit/skphysicsworld/1449623-gravity

The documentation says:

The components of this property are measured in meters per second. The default value is (0.0,-9.8), which represent’s Earth’s gravity.

Gravity, however is calculated in m/s^2 and not in m/s.

Thinking that was an error in the implementation of gravity in SpriteKit I began thinking that maybe real-world-based physics laws could not be applied in the scene.

I did, however, come across another documentation page about the linear gravity field that correctly reported that gravity is measured in m/s^2 in SpriteKit.

https://developer.apple.com/documentation/spritekit/skfieldnode/1520145-lineargravityfield

I then setup a simple free falling scene where I applied an initial velocity to a physics body and then calculated the expected trajectory, while comparing it to the actual trajectory.

The x-axis calculations were accurate from the start, suggesting that the only problem was with the gravity's value.

I then tried manually modified the gravity in the scene until the actual trajectory matched the predicted one.

What I found is that there is a "magic" value of ~151 that has to be factored in when using the physics world's gravity property in the game.

Modifying, for example, the y-axis calculations for the trajectory from

let dy = velocity.dy * time + 0.5 * gravity * pow(time, 2)

to

let dy = velocity.dy * time + 0.5 * 151 * gravity * pow(time, 2)

resulted in accurate calculations.

I hope this is useful to anyone who might encounter the same problem in the future.