Update 05/11/2016: Updated for Xcode 7.3 by Michael Briscoe. Original post by Tutorial Team member Barbara Reichart.
Sprite Kit is Apple’s game development framework for iOS and OS X. Not only does it come with some great graphics capabilities, but it also includes an easy to use physics engine. Best of all, you do all the work using the tools you are familiar with: Swift, Xcode and Interface Builder!
There’s a lot you can do with Sprite Kit, and a great way to start learning about how it works is to create a simple game.
In this 2-part tutorial series, you are going to learn how to create a Breakout game using Sprite Kit, complete with collision detection, ball bouncing using physics effects, dragging the paddle via touches, and game states.
If you are new to Sprite Kit, you should go through the Sprite Kit Swift Tutorial for Beginners before proceeding with this tutorial.
Getting Started
Be sure to download the starter project for this tutorial. This project was created using the standard Xcode game template. The assets and state classes, have already been imported to save a bit of time. You’ll learn more about game states as you go along.
Go ahead and take a moment to familiarize yourself with the project. Build and run, and you’ll see a gray screen in landscape mode.
Introducing the Sprite Kit Visual Editor
Let’s start by configuring the scene file. To do that, open the GameScene.sks file. This is a visual editor already linked to your Sprite Kit Scene and every element dropped in there will be accessible in your game and from GameScene.swift.
First, you will have to resize the scene so it fits your targeted screen for this tutorial: an iPhone 6 screen. You can do that in the Scene section of the Attributes inspector on the top right corner of the Xcode window. If you don’t see the Attributes inspector, you can access it via View\Utilities\Show Attributes Inspector. Set the scene’s size to 568×320 as shown in the screenshot below.
Now to the game’s background. As shown in the screenshot below, drag a Color Sprite from the Object Library panel on the bottom right corner of the Xcode window. If you don’t see the Object Library panel, select View\Utilities\Show Object Library from the menubar.
Using the Attributes inspector, change the Position to 284,160 and set it’s Texture to be bg.
You can now build and run to see your game display it’s background.
Once you have a nice clean landscape scene with a background, it is time to add the ball! Still in GameScene.sks, drag a new Color Sprite into the scene. Then, set it’s Name to ball, it’s Texture to ball, and it’s Position to 284,220. Also set it’s Z Position to 2 to ensure that the ball appears over the background.
Build and run and you’ll see your ball appear on the screen:
However, it doesn’t move yet. That is because you need to add some physics to it.
Physics
In Sprite Kit you work in two environments: the graphical world that you see on the screen and the physics world, which determines how objects move and interact.
The first thing you need to do when using Sprite Kit physics is to change the world according to the needs of your game. The world object is the main object in Sprite Kit that manages all of the objects and the physics simulation. It also sets up the gravity that works on physics bodies added to it. The default gravity is -9.81 thus similar to that of the earth. So, as soon as you add a body it would “fall down”.
Once you have configured the world object, you can add things to it that interact according to the principles of physics. For this the most usual way is to create a sprite (graphics) and set its physics body. The properties of the body and the world determine how it moves.
Bodies can be dynamic objects (balls, ninja stars, birds, …) that move and are influenced by physical forces, or they can be static objects (platforms, walls, …) that are not influenced by those forces. When creating a body you can set a ton of different properties like shape, density, friction and many more. Those properties heavily influence how the body behaves within the world.
When defining a body, you might wonder about the units of their size and density. Internally Sprite Kit uses the metric system (SI units). However within your game you usually do not need to worry about actual forces and mass, as long as you use consistent values.
Once you’ve added all of the bodies to your world, Sprite Kit can take over and do the simulation.
To set up your first physics body, select the ball node you just added and scroll down the Attributes inspector until you reach the Physics Definition section. Select Body Type > Bounding Circle and set the newly appeared properties to the following:
- Uncheck Allows Rotation
- Set the Friction to
0
- Set the Restitution to
1
- Set the linear Damping to
0
- Set the Angular Damping to
0
Here you’ve created a volume-based physics body with the form of a circle that has exactly the same size as the ball sprite. This physics body is affected by forces or impulses, and collisions with other bodies.
As for it’s properties:
- Allows Rotation does exactly what the name implies. It either allows rotation of the body or not. Here you do not want the ball to rotate.
- Friction is also quite clear – it simply removes all friction.
- Restitution refers to the bounciness of an object. You set the restitution to 1, meaning that when the ball collides with an object the collision will be perfectly elastic. In plain English, this means that the ball will bounce back with equal force to the impact.
- Linear Damping simulates fluid or air friction by reducing the body’s linear velocity. In the Breakout game the ball should not be slowed down when moving. So, you set the damping to 0.
- Angular Damping is the same as Linear Damping but for the angular velocity. Setting this is optional as you don’t allow rotation for the ball.
Build and run. If you’re quick enough you should see the ball just fall through the scene and disappear at the bottom of the screen.
Two reasons for this: first, the default gravity of a scene simulates that of the earth – 0 along the x-axis and -9.8 along the y-axis. Second, your scene’s physics world has no boundaries that would act as a cage enclosing the ball. Let’s start with that!
Caging the ball
Open GameScene.swift and add the following line of code to the end of didMoveToView(_:)
to create an invisible barrier around the screen:
// 1 let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame) // 2 borderBody.friction = 0 // 3 self.physicsBody = borderBody |
Let’s review this line by line:
- You create an edge-based body. In contrast to the volume-based body you added to the ball, an edge-based body does not have mass or volume, and is unaffected by forces or impulses.
- Set the friction to
0
so that the ball will not be slowed down when colliding with the border barrier. Instead, you want to have a perfect reflection, where the ball leaves along the same angle that it hit the barrier. - You can set a physics body for every node. Here, you attach it to the scene. Note: The coordinates of the
SKPhysicsBody
are relative to the position of the node.
Running your project, you should now see your ball fall as before but it now bounces on the bottom-edge of the cage you just added. Because you removed the friction from the contact with the cage and the environment, and set the restitution to be perfectly elastic, the ball will bounce like that indefinitely.
To finish up with the ball, let’s remove the gravity and apply a single impulse so that it will bounce around the screen forever.
Forever bouncing
It’s time to get the ball rolling (bouncing, actually). Add the following code right after the previous lines in GameScene.swift:
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0) let ball = childNodeWithName(BallCategoryName) as! SKSpriteNode ball.physicsBody!.applyImpulse(CGVector(dx: 2.0, dy: -2.0)) |
This new code first removes all gravity from the scene, then gets the ball from the scene’s child nodes using the name you set in the Visual Editor and applies an impulse. An impulse applies an immediate force to a physics body to get it moving in a particular direction (in this case, diagonally down to the right). Once the ball is set in motion, it will simply bounce around the screen because of the barrier you just added!
Now it’s time to try it out! When you compile and run the project, you should see a ball continuously bouncing around the screen – cool!
Adding the Paddle
It wouldn’t be a Breakout game without a paddle, now would it?
Go to GameScene.sks and build the paddle (and its companion physics body) via the Visual Editor just like you did for the ball by placing a Color Sprite at the bottom middle of the scene and setting its properties to:
- Name = paddle
- Texture = paddle.png
- Position = 284,30
- Z Position = 3
- Body Type > Bounding rectangle
- Uncheck Dynamic
- Friction:
0
- Restitution:
1
Most of this is similar to what you used when creating the ball. However, this time you use a Bounding rectangle to form the physics body as it will better match the rectangular paddle.
By turning off Dynamic here you are setting the paddle to be static. This ensures that the paddle does not react to forces and impulses. You will soon see why this is important.
If you build and run, you’ll see your paddle in the scene, and the ball will bounce off it (if you can wait long enough for it to get there):
So far so good – but now let’s allow the player to move the paddle!
Moving the Paddle
Time to get moving! Moving the paddle is going to require detecting touches. You can detect touches in GameScene
by implementing the following touch handling methods:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) |
But, before that, you need to add a property. Go to GameScene.swift and add the following property to the class:
var isFingerOnPaddle = false |
This defines a property that stores whether the player tapped the paddle or not. You will need it to implement the dragging of the paddle.
Now, go ahead and add the code to implement touchesBegan(_:withEvent:)
to GameScene.swift as follows:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { let touch = touches.first let touchLocation = touch!.locationInNode(self) if let body = physicsWorld.bodyAtPoint(touchLocation) { if body.node!.name == PaddleCategoryName { print("Began touch on paddle") isFingerOnPaddle = true } } } |
The above code gets the touch and uses it to find the location on the scene where the touch occurred. Next, it uses bodyAtPoint(_:)
to find the physics body associated with the node (if any) at that location.
Finally, it checks whether there was a node at the tap location and if yes, whether that node is the paddle. This is where the object names you set up earlier come into play – you can check for a specific object by checking its name. If the object at the tap location is a paddle, then a log message is sent to the console and isFingerOnPaddle
is set to true
.
Now you can build and run the project again. When you tap the paddle, you should see a log message in the console.
Now, go ahead and add the code for touchesMoved(_:withEvent:)
:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { // 1 if isFingerOnPaddle { // 2 let touch = touches.first let touchLocation = touch!.locationInNode(self) let previousLocation = touch!.previousLocationInNode(self) // 3 let paddle = childNodeWithName(PaddleCategoryName) as! SKSpriteNode // 4 var paddleX = paddle.position.x + (touchLocation.x - previousLocation.x) // 5 paddleX = max(paddleX, paddle.size.width/2) paddleX = min(paddleX, size.width - paddle.size.width/2) // 6 paddle.position = CGPoint(x: paddleX, y: paddle.position.y) } } |
This is where most of the paddle movement logic comes in.
- Check whether the player is touching the paddle.
- If yes, then you need to update the position of the paddle depending on how the player moves their finger. To do this, you get the touch location and previous touch location.
- Get the
SKSpriteNode
for the paddle. - Take the current position and add the difference between the new and the previous touch locations.
- Before repositioning the paddle, limit the position so that the paddle will not go off the screen to the left or right.
- Set the position of the paddle to the position you just calculated.
The only thing left in touch handling is to do some cleanup in touchesEnded(_:withEvent:)
as follows:
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { isFingerOnPaddle = false } |
Here, you set the isFingerOnPaddle
property to false
. This ensures that when the player takes their finger off the screen and then taps it again, the paddle does not jump around to the previous touch location.
Perfect! When you build and run the project now, the ball will bounce around the screen and you can influence its movement using the paddle.
w00t – it’s feeling fun already!
Making Contact
So far, you have a ball that bounces around the screen and a paddle you can move around via touch. While this is really fun to toy around with, to make it a game you need a way for your player to win and lose the game. Losing should happen when the ball touches the bottom of the screen instead of hitting the paddle. But how do you detect this scenario using Sprite Kit?
Sprite Kit can detect the contact between two physics bodies. However, for this to work properly, you need to follow a few steps to set things up a certain way. Here’s a short overview. Each of the steps will be explained in more detail later. So, here you go:
-
Set up physics body bit masks: In your game you might have several different types of physics bodies – for example, you can have the player, enemies, bullets, bonus items etc. To uniquely identify these different types of physics bodies, each physics body can be configured using several bit masks. These include:
- categoryBitMask: This bit mask identifies the category a body belongs to. You use categories to define a body’s interaction with other bodies. The categoryBitMask is a 32-bit integer, where each bit represents one category. So you can have up to 32 custom categories in your game. This should be enough for most games to set up a separate category for each object type. For more complex games it can be useful to remember that each body can be in several categories. So through smart design of the categories you could even overcome the limitation of 32 categories.
- contactTestBitMask: Setting a bit in this bitmask causes Sprite Kit to notify the contact delegate when the body touches another body assigned to that particular category. By default, all bits are cleared – you are not notified about any contacts between objects. For best performance you should only set bits in the contacts mask for interactions you are really interested in.
- collisionBitMask: Here, you can define which bodies can collide with this physics body. You can use this, for example, to avoid collision calculations for a very heavy body when it collides with a much lighter body as this would only make negligible changes to the heavy body’s velocity. But you can also use it to allow two bodies to pass right through each other.
- Set and implement the contact delegate: The contact delegate is a property of
SKPhysicsWorld
. It will be notified when two bodies with the proper contactTestBitMasks begin and end colliding.
So what is a bitmask? A bitmask is a multi-digit binary number. Something like this:
1011 1000
. Not so complicated at all.
But why are they useful? Well, they allow you to get state information out of a binary number and give you the ability to change a specific bit in a binary number to set a specific state. You can do this with the binary operators AND
and OR
, like so:
This allows you to store a lot of information in a really compact way using just one variable and still be able to access and manipulate the stored information.
3, 2, 1, Contact: Code
First, create constants for the different categories. Do this by adding the following lines below the other constants for the category names in GameScene.swift:
let BallCategory : UInt32 = 0x1 << 0 let BottomCategory : UInt32 = 0x1 << 1 let BlockCategory : UInt32 = 0x1 << 2 let PaddleCategory : UInt32 = 0x1 << 3 let BorderCategory : UInt32 = 0x1 << 4 |
The above defines five categories. You do this by setting the last bit to 1 and all other bits to zero. Then using the <<
operator you shift this bit to the left. As a result, each of the category constants has only one bit set to 1 and the position of the 1 in the binary number is unique across the four categories.
For now you only need the category for the bottom of the screen and the ball, but you should set up the others anyway as you will need them later on as you expand the gameplay.
Once you have the constants in place, create a physics body that stretches across the bottom of the screen. Try to do this by yourself since this uses principles you've already learned when creating the barriers around the screen edges.
Now, let’s get to the meat of establishing contact. First, set up the categoryBitMasks for the game objects by adding the following code to didMoveToView(_:):
let paddle = childNodeWithName(PaddleCategoryName) as! SKSpriteNode bottom.physicsBody!.categoryBitMask = BottomCategory ball.physicsBody!.categoryBitMask = BallCategory paddle.physicsBody!.categoryBitMask = PaddleCategory borderBody.categoryBitMask = BorderCategory |
This code simply assigns the constants you created earlier to the corresponding physics body’s categoryBitMask
.
Now, set up the contactTestBitMask
by adding this (again to didMoveToView(_:)
):
ball.physicsBody!.contactTestBitMask = BottomCategory |
For now you only want to be notified when the ball makes contact with the bottom of the screen.
Next, make GameScene
the delegate for all physics contact.
Change this line:
class GameScene: SKScene { |
To this line:
class GameScene: SKScene, SKPhysicsContactDelegate { |
This makes it official: GameScene
is now an SKPhysicsContactDelegate
(as it conforms to the SKPhysicsContactDelegate
protocol) and will receive collision notifications for all configured physics bodies. Hurrah!
Now you need to set GameScene
as the delegate in the physicsWorld. So, add this to didMoveToView(_:)
right below physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
:
physicsWorld.contactDelegate = self |
Finally, you need to implement didBeginContact(_:)
to handle the collisions. Add the following method to GameScene.swift:
func didBeginContact(contact: SKPhysicsContact) { // 1 var firstBody: SKPhysicsBody var secondBody: SKPhysicsBody // 2 if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask { firstBody = contact.bodyA secondBody = contact.bodyB } else { firstBody = contact.bodyB secondBody = contact.bodyA } // 3 if firstBody.categoryBitMask == BallCategory && secondBody.categoryBitMask == BottomCategory { print("Hit bottom. First contact has been made.") } } |
Let's walk through the method step by step:
- Create two local variables to hold the two physics bodies involved in the collision.
- Check the two bodies that collided to see which has the lower
categoryBitmask
. You then store them into the local variables, so that the body with the lower category is always stored infirstBody
. This will save you quite some effort when reacting to contacts between specific categories. - Profit from the sorting that you did just before. You only need to check whether
firstBody
is in theBallCategory
and whethersecondBody
is in theBottomCategory
to figure out that the ball has touched the bottom of the screen, as you already know thatsecondBody
could not possibly be in theBallCategory
iffirstBody
is in theBottomCategory
(becauseBottomCategory
has a higher bit mask thanBallCategory
). For now react with a simple log message.
It’s time to try out your code again. Build and run your game and if you’ve done everything correctly, you should see the log message in the console every time the ball misses the paddle and hits the bottom of the screen. Like this:
Allright! Now the hard part is done - all that's left is adding the bricks and gameplay logic, which you'll do in part 2.
Where To Go From Here?
Here is the example project up to this point.
Keep reading to part 2 of this series, where you'll add some bricks and finish up the game.
In the meantime, if you have any questions or comments, please join the forum discussion below!
The post How To Create a Breakout Game with Sprite Kit and Swift: Part 1 appeared first on Ray Wenderlich.