Follow along at https://www.hackingwithswift.com/100/45.
This day covers the first part of
Project 11: Pachinko
in Hacking with Swift.I have a separate repository where I've been creating projects alongside the material in the book. And you can find Project 11 here. However, I also copied it over to this day's folder so I could extend from where I left off.
With that in mind, Day 45 focuses on several specific topics:
- Coordinate Systems and Positioning
- Falling boxes: SKSpriteNode, UITouch, SKPhysicsBody
- Bouncing balls: circleOfRadius
SpriteKit doesn't mess around. It respects the Grammar of Graphics and zeroes its Y-coordinate at the bottom, increasing upwards. This is notable, because UIKit's Y-coordinate goes the other way: top to bottom — more like a Grammar of Page Layout... or something.
Furthermore, nodes
— the objects placed in a scene — are positioned from their center: The point (0, 0) refers to the horizontal and vertical center of a node.
These differences can be confusing at first, but they exist for a reason. When we start to introduce physics and other effects, it's handy to have a coordinate and positioning system that lets us think in terms of worlds, universes, and the bodies within them 💫.
Despite being in its own world, SpriteKit can and does still let us interface with UIKit. Handling the touchesBegan
method is a perfect example. Because the GameScene
is still driven by a GameViewController
, which is a subclass of UIViewController
, we can respond to touchesBegan
, and use the location information to... say... place a node:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self)
// drop a ball from the top of the screen at the corresponding x position
addChild(makeBall(at: CGPoint(x: location.x, y: frame.maxY)))
}
Nodes placed in the scene can have an instance of SKPhysicsBody
set on them. This is an optional property — an SKLabelNode
probably won't need it — but when we use it, look out.
Physics bodies are dynamic by default, meaning that Sprite Kit goes about applying, among many things, the force of gravity in the scene's physicsWorld
to them.
We can influence the way our nodes react to these forces with the properties we define on a node's physicsBody
— including the overall form we initialize it with:
func makeBall(at position: CGPoint) -> SKNode {
let ball = SKSpriteNode(imageNamed: "ballRed")
let ballPhysicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2.0)
ballPhysicsBody.restitution = 0.4
ball.physicsBody = ballPhysicsBody
ball.position = position
return ball
}
circleOfRadius
is one of the many arguments we could provide here. For more complex bodies, it seems like the (texture:size:)
initializer would be handy. For circles, however, SpriteKit can spare the performance hit and use geometry instead 💯.