SpriteKit Project — AbsolutelyBrickingIt — Part 7: Add the paddle
In this article, we’ll add the paddle that the user will control to the game scene.
Add game assets
Drag and drop game images to add to the Assets:
Add the background image and the paddle image as GameScene
sprite nodes:
class GameScene: SKScene {
...
override func didMove(to view: SKView) {
let backgroundNode = SKSpriteNode(imageNamed: "background.menu")
backgroundNode.position = CGPoint(x: 0.5 * size.width, y: 0.5 * size.height)
addChild(backgroundNode)
let paddleNode = SKSpriteNode(imageNamed: "paddle")
paddleNode.position = CGPoint(x: 0.5 * size.width, y: 0.1 * size.height)
addChild(paddleNode)
}
...
}
Run in the simulator to see that the background and paddle are now shown in the game scene:
Move paddle with user touches
We’ll move the paddle when the user touches anywhere in the game scene and drags their finger left or right. First let’s add the remaining touch handling methods to GameScene
and remove the coordinator method call in touchesEnded(_: with:)
:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
We’ll keep a reference to the paddle node so that we can change its position later in the touch handling methods:
class GameScene: SKScene {
...
private var paddleNode: SKSpriteNode?
override func didMove(to view: SKView) {
...
addChild(paddleNode)
self.paddleNode = paddleNode
}
...
}
In order to move the paddle, we’ll keep a copy of the last touch location and update the paddle location when one of the touch events occurs, resetting the last touch location when the touchesBegan(_: with:)
method occurs:
class GameScene: SKScene {
...
private var paddleNode: SKSpriteNode?
private var lastTouchLocation: CGPoint?
override func didMove(to view: SKView) {
...
addChild(paddleNode)
self.paddleNode = paddleNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchLocation = touches.first?.location(in: self)
}
...
}
Now in touchesMoved(_: with:)
and touchesEnded(_: with:)
methods, we can update the paddle position based on how much the touch location has changed horizontally:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let firstTouchLocation = touches.first?.location(in: self) else { return }
touchLocation(movedTo: firstTouchLocation)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let firstTouchLocation = touches.first?.location(in: self) else { return }
touchLocation(movedTo: firstTouchLocation)
}
private func touchLocation(movedTo point: CGPoint) {
guard let paddleNode,
let lastTouchLocation else { return }
paddleNode.position = CGPoint(
x: paddleNode.position.x + point.x - lastTouchLocation.x,
y: paddleNode.position.y)
self.lastTouchLocation = point
}
Now running the app in the Simulator, the paddle can be moved left and right by dragging a finger across the screen:
Note that the paddle can be actually moved all the way off the screen, however, so we’ll address this behaviour next.
Keep the paddle on the screen
Instead of just calculating the new horizontal position in this way:
x: paddleNode.position.x + point.x - lastTouchLocation.x
We’ll create a method to clamp this value to ensure it stays between a minimum and maximum:
private func clamp(value: CGFloat, min: CGFloat, max: CGFloat) -> CGFloat {
if value < min {
return min
} else if value > max {
return max
} else {
return value
}
}
With this method, we can change the paddle update to:
let destinationX = clamp(
value: paddleNode.position.x + point.x - lastTouchLocation.x,
min: paddleNode.size.width * 0.5,
max: size.width - paddleNode.size.width * 0.5)
paddleNode.position = CGPoint(x: destinationX, y: paddleNode.position.y)
Now when the app is run, we can see that the paddle is kept inside the screen:
Clamping a value, in this case a CGFloat
, is not functionality that is specific to GameScene
. We can create a CGFloat
extension to add it as a utility method for CGFloat
:
extension CGFloat {
func clamp(min: CGFloat, max: CGFloat) -> CGFloat {
if self < min {
return min
} else if self > max {
return max
} else {
return self
}
}
}
(Note the change as this is moved to a CGFloat
extension - we no longer pass a value, but use self
instead.)
This helps keep GameScene
cleaner - removing code that doesn't logically belong there - and hopefully easier to understand. GameScene
needs to be updated to use this change:
let positionUpdateX = paddleNode.position.x + point.x - lastTouchLocation.x
let destinationX = positionUpdateX.clamp(
min: paddleNode.size.width * 0.5,
max: size.width - paddleNode.size.width * 0.5)
paddleNode.position = CGPoint(x: destinationX, y: paddleNode.position.y)
Running the app again, the behaviour is identical to before, but the code has been cleaned up a bit.
Code
This article corresponds to the merge commit Add paddle (#7)
in the GitHub repository AbsolutelyBrickingIt