In my previous post on SpriteKit, I went through the basics of the framework, namely
SKAction. I intentionally left out a key component to SpriteKit which deserves attention to itself: physics.
In SpriteKit, every node has a property called
physicsBody, which is an
SKPhysicsBody? (note the
?, which makes the
SKPhysicsBody optional, meaning the physics body attached to the node could be non-existent, i.e.
nil). Once you assign the
physicsBody of a node to an instance of
SKPhysicsBody, the body becomes attached to the node.
A physics body can be one of two kinds: a volume-based body or an edge-based body. A volume-based body has mass and volume while an edge-based body does not.
In the diagram above, the volume-based body is a circle, however, an
SKPhysicsBody can take any shape: a circle, rectangle, polygon from a
CGPath, or even an image (using an
SKTexture), using the initializers
init(polygonFrom: CGPath), and
init(texture: SKTexture, size: CGSize), respectively. While you can use any of these options for creating a volume-based physics body, there are costs to using more complex shapes.
The image above shows a sprite node with different physics bodies attached to it: (1) a circle, (2) a rectangle, (3) a polygon, and (4) texture-based body. It is more computationally difficult to determine the collisions of a complex polygon than it is a circle. In practice, this probably won’t make much of a difference unless you have a large number of physics bodies in your scene, but it something to be aware of.
Edge-based physics bodies on the other hand are paths with no mass, in effect they are non-solid. These bodies are not moved by the physics engine, but can collide with volume-based bodies. For example, you might want a boundary around the scene that the other physics bodies can’t cross. There’s a convenient initializer for creating these bodies:
init(edgeLoopFrom: CGRect). If we pass it the frame of the scene, for example
SKPhysicsBody(edgeLoopFrom: scene.frame), we can create an impassible boundary around the scene.
By default, every physics body will interact with every other physics body. To change this behavior, we set the properties
contactTestBitMask on each physics body. The category bit mask tells the collision engine what category a body belongs to. Using the example above you’d have a ball category (
b0001) and an edge category (
b0010). Since we want to the ball to collide with edge, we would assign the collision and contact test bit masks of each ball and edge in the scene.
let ballCategory: UInt32 = b0001
let edgeCategory: UInt32 = b0010
let ball = SKShapeNode(circleOfRadius: 10)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
ball.physicsBody?.categoryBitMask = ballCategory
ball.physicsBody?.contactTestBitMask = ballCategory | edgeCategory
ball.physicsBody?.collisionBitMask = ballCategory | edgeCategory
let edge = SKPhysicsBody(edgeLoopFrom: scene.frame)
edge.categoryBitMask = edgeCategory
edge.contactTestBitMask = ballCategory
edge.collisionBitMask = ballCategory
Each ball will now collide with the edge as well as every other ball , because we used the bitwise OR operator (
|) on the two ball and edge categories.
In order to get physics bodies to move around the scene, we use forces. The
SKPhysicsBody class has methods that allow you to give it a force vector and apply it the body. The method
applyForce(CGVector) accomplishes this task.
ball.applyForce(CGVector(dx: -20, dy: 30))
Think of this as pushing on the ball and letting go. The force will accelerate the ball in the direction of the vector given to the
applyForce method. If the
friction property on the physics body is greater than
0, then the ball will lose velocity due to friction. Another property on the physics on body,
restitution, will tell the physics engine how much velocity the body will lose when it collides with other bodies. You can see this in real life when you drop a bouncy ball on the ground: the ball will bounce back up to you but at a lower height due to the force of gravity and loss of momentum from hitting the ground.
Speaking of gravity: every
SKScene has a
physicsWorld property where you can define how the physics in your world work. The gravity in the physics world is defaulted to
CGVector(dx: 0, dy: -9.8), which is the gravitational acceleration on Earth. You can change this property to whatever you like though. If you wanted your nodes to fall to the right, you could set the gravity to
CGVector(dx: 5, dy: 0). Imagine you had a game where the player would tilt their device to make the objects move around the scene. You could use the accelerometer data to update to the gravity vector, which would apply gravitational force in the direction that the device is tilted.
This should get you started with the basics, but there is much more to SpriteKit physics than physics bodies and simple forces, so feel free to ask me questions here in the comments, tweet or PM me on Twitter, or check out Apple’s documentation on
SKPhysicsBody for more information: https://developer.apple.com/reference/spritekit/skphysicsbody
If you want to see a game that uses SpriteKit, check out Blueshift on the App Store!