Breakout blocks at the beginning of the game – all in a row.
Update note: This tutorial was updated to Swift and iOS 8 by Zouhair Mahieddine. 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 a physics engine which looks a lot like Box2D. 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 tutorial, you are going to learn how to create a Breakout game using Sprite Kit step-by-step, complete with collision detection, ball bouncing using physics effects, dragging the paddle via touches, and win/lose screens.
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
Start by creating a new project. For this start up Xcode, go to File\New\Project… and choose the iOS\Application\Game template. Set the product name to BreakoutSpriteKitTutorial, select Language > Swift (obviously…), Game Technology > SpriteKit, Devices > iPhone and then click Next. Select a location on your hard drive to save your project and then click Create.
Your game will need some graphics. You probably want to have at least a graphic for the ball, the paddle, the bricks, and a background image. Start by downloading our starter image pack. Open Images.xcassets and drag and drop the files to the panel with the AppIcon asset in Xcode. Delete the default Spaceship asset that comes with the Sprite Kit template because it wont be used in this tutorial.
Open GameScene.swift. This class creates your game scene. The template includes some extra code that you will not need. So, replace the contents of the file with the following:
import SpriteKit
let BallCategoryName = "ball"
let PaddleCategoryName = "paddle"
let BlockCategoryName = "block"
let BlockNodeCategoryName = "blockNode"
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
}
} |
The code here is very basic. First, you define a few constants that will help you identify the game objects. Then, didMoveToView(_:)
gives you a starting point to access your sprites and work on them.
Now you have a barebones project to fill in with your masterpiece!
Note: By default the template code is configured to display the number of nodes in your
SKScene and its frame rate. You can easily remove this information from the screen – or add others, like some very useful physics-related debugging information – by opening
GameViewController.swift and changing these two lines to set the properties to
false
:
skView.showsFPS = true
skView.showsNodeCount = true |
However, the screen is still in portrait mode and you want Breakout to work in landscape mode instead. To fix that, select the project root in Xcode, then select the BreakoutSpriteKitTutorial target, make sure that the General tab is selected, and set the orientation to just landscape by ensuring that only the two landscape checkboxes are checked as shown below:
Build and run! Now the gray screen only shows up in landscape, even if you rotate the device to portrait.
Introducing the Sprite Kit Visual Editor
You will now add a background to the scene. 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 5 screen. You can do that in the Scene section of the SKNode inspector on the top right corner of the Xcode window. If you don’t see the SKNode inspector, you can access it via View\Utilities\Show SKNode Inspector. Set the scene’s size to 1136x640
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, just click on View\Utilities\Show Object Library and it will show up.
Using the SKNodeInspector, change the Size and Position of the sprite so that it fills the entire scene (position = {568,320} ; size = 1136x640
).
Finally, set its Texture to be bg.png
.
You can now build and run (targeting an iPhone 5, 6 or even 6 Plus) to see your game display only a background.
Note: At the time this tutorial is written, Xcode 6 has a few bugs in the Sprite Kit Visual Editor. Sometimes, you will not see your changes being reflected on your canvas. Just build and run your project and everything should be ok. If for some reason your canvas loses some of your settings… well then, there is really no better technique than doing it again while shouting and swearing at Xcode (any damage caused to your computer in these situations should not be considered the fault of yours truly…). Oh, and if these bugs are fixed by the time you are reading this… Well, it’s about time!
An-Ever-Bouncing Ball
Once you have a nice clean landscape scene with a background, it is time to add the ball! Again, you are going to do that using the visual editor. Still in GameScene.sks, drag a new Color Sprite into the scene, placing it wherever you want. Then, set its Texture to ball.png
and its Name to ball
.
Build and run and…! Your ball is static…
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 created 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 you like 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 SKNode Inspector until you reach the Physics Definition section. Select Body Type>BoundingCircle 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
Adding physics to the ball
No surprises here. You simply create a sprite and name it for later reference. Then, you create a volume-based body for the ball. This physics body is affected by forces or impulses, and collisions with other bodies. Here you create a physics body with the form of a circle that has exactly the same size as the ball sprite.
As for the properties of this physics body:
- AllowsRotation 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.
Note: Usually, it’s best to have the physics body be fairly similar to what the player sees. For the ball it’s very easy to have a perfect match. However, with more complex shapes you’ll have to get a bit more creative. This is where you’ll need to be careful since very complex bodies can exact a high toll on performance. Lucky for you, since iOS 8 and Xcode 6, Sprite Kit supports alpha masks body types, a good way to automatically take the shape of a sprite as the shape of its physics body, but be careful as it can cause performance issues.
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
No escape for the ball.
Open GameScene.swift and add the following line of code to the end of didMoveToView(_:)
(right after the call to super.didMoveToView(view)
) to create an invisible barrier around the screen (this will effectively cage the ball on screen, ensuring that it cannot escape):
// 1. Create a physics body that borders the screen
let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
// 2. Set the friction of that physicsBody to 0
borderBody.friction = 0
// 3. Set physicsBody of scene to borderBody
self.physicsBody = borderBody |
- 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.
Perfect reflection
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. And because you removed all kinds of frictions (from the contact with the cage and the environment) and set the restitution to be perfectly elastic, the ball will bounce like that indefinitely, not being slowed down by anything.
To be finished with the ball, let’s now remove the gravity and apply just a single impulse to the ball 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 = CGVectorMake(0, 0)
let ball = childNodeWithName(BallCategoryName) as SKSpriteNode
ball.physicsBody!.applyImpulse(CGVectorMake(10, -10)) |
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!
Warning: If you are following this tutorial using the first versions of Xcode 6, you might see your ball behave weirdly. That is because the Sprite Kit Editor is still new (and has some bugs). Some properties set in it might not be taken in account while running the app (in my case, it was ignoring the
linearDamping value…). To prevent that, you can rely on good old code and add this right after the previous lines in
GameScene.swift:
ball.physicsBody!.allowsRotation = false
ball.physicsBody!.friction = 0
ball.physicsBody!.restitution = 1
ball.physicsBody!.linearDamping = 0
ball.physicsBody!.angularDamping = 0 |
Try it out again, it should be cool now!
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
- Body Type > Alpha mask
- Uncheck Dynamic
- Friction:
0
- Restitution:
1
Most of this is similar to what you used when creating the ball. However, this time you use an Alpha mask to form the physics body as it will exactly match the rounded corners of the paddle.
Here you need to make sure that the paddle is 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 :]):
The most boring game ever :/
However, this isn’t much fun, because you can’t move the paddle yet!
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: NSSet, withEvent event: UIEvent)
override func touchesMoved(touches: NSSet, withEvent event: UIEvent)
override func touchesEnded(touches: NSSet, 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: NSSet, withEvent event: UIEvent) {
var touch = touches.anyObject() as UITouch!
var touchLocation = touch.locationInNode(self)
if let body = physicsWorld.bodyAtPoint(touchLocation) {
if body.node!.name == PaddleCategoryName {
println("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
.
Note: To do these checks, you use a new technique called Optional Binding. What this does is check if an object exists, and if so, unwrap and bind it to a new variable that can be used within the if statement.
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: NSSet, withEvent event: UIEvent) {
// 1. Check whether user touched the paddle
if isFingerOnPaddle {
// 2. Get touch location
var touch = touches.anyObject() as UITouch!
var touchLocation = touch.locationInNode(self)
var previousLocation = touch.previousLocationInNode(self)
// 3. Get node for paddle
var paddle = childNodeWithName(PaddleCategoryName) as SKSpriteNode!
// 4. Calculate new position along x for paddle
var paddleX = paddle.position.x + (touchLocation.x - previousLocation.x)
// 5. Limit x so that paddle won't leave screen to left or right
paddleX = max(paddleX, paddle.size.width/2)
paddleX = min(paddleX, size.width - paddle.size.width/2)
// 6. Update paddle position
paddle.position = CGPointMake(paddleX, 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: NSSet, 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.
Note: You might have noticed that the code directly manipulates the position of the paddle. You can do this because you made the paddle static. You should never change the position of a dynamic body directly, as it can break the physics simulation and lead to really weird behavior.
Don’t believe me? Make the paddle dynamic and set a low gravity, e.g. this
physicsWorld.gravity = CGVectorMake(-0.1, -0.1) |
Looks quite messed up, doesn’t it?
Sprite Kit Makes First Contact!
No! Not that type of first contact! (image by DeviantArt user hugoo13, Creative Commons Licensed)
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.
Note: Bit masks?!? In case you’ve never worked with bit masks, don’t panic! At first glance they might look complicated, but they are really useful.
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:
Bitmask power :]
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.
So how do you do all this in 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 // 00000000000000000000000000000001
let BottomCategory : UInt32 = 0x1 << 1 // 00000000000000000000000000000010
let BlockCategory : UInt32 = 0x1 << 2 // 00000000000000000000000000000100
let PaddleCategory : UInt32 = 0x1 << 3 // 00000000000000000000000000001000 |
The above defines four 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 probably 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. (Name the node containing the physics body bottom since you’ll be configuring that node in later steps.)
Solution Inside: Create an edge-based body that covers the bottom of the screen |
SelectShow> |
Add the following to didMoveToView(_:) in GameScene.swift:
let bottomRect = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 1)
let bottom = SKNode()
bottom.physicsBody = SKPhysicsBody(edgeLoopFromRect: bottomRect)
addChild(bottom) |
|
With the preparations out of the way, let’s get to the meat of establishing contact. First, set up the categoryBitMasks for the bottom, ball, and paddle 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 |
This is really straightforward code. It 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. Therefore, you set the contactTestBitMask
to BottomCategory
.
Next, you need to create an SKPhysicsContactDelegate
. As this is a rather simple game, you will just make GameScene
the delegate for all contacts.
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 = CGVectorMake(0, 0)
:
physicsWorld.contactDelegate = self |
Setting up the SKPhysicsContactDelegate
Finally, you need to implement didBeginContact(_:)
to handle the collisions. Add the following method to GameScene.swift:
func didBeginContact(contact: SKPhysicsContact) {
// 1. Create local variables for two physics bodies
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
// 2. Assign the two physics bodies so that the one with the lower category is always stored in firstBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
// 3. react to the contact between ball and bottom
if firstBody.categoryBitMask == BallCategory && secondBody.categoryBitMask == BottomCategory {
//TODO: Replace the log statement with display of Game Over Scene
println("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 in firstBody
. 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 the BallCategory
and whether secondBody
is in the BottomCategory
to figure out that the ball has touched the bottom of the screen, as you already know that secondBody
could not possibly be in the BallCategory
if firstBody
is in the BottomCategory
(because BottomCategory
has a higher bit mask than BallCategory
). 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:
First contact has been made :)
Adding a Game Over Scene
Unfortunately, your players cannot see log messages when they lose the game. Instead, when they lose, you want to show them some sort of visual indication on screen. You must create a little game over scene for that purpose.
Visually building the Scene
Go to File\New\File..., choose the iOS\Resource\SpriteKit Scene template and click Next. Name the file GameOverScene and click Create.
Now, just try to repeat by yourself the steps to put the scene to the right size and add the background to it. For the record, here are some things you will need:
- Scene size:
1136x640
- Background texture:
bg.png
Solution Inside: Mimic the GameScene via the Sprite Kit Visual Editor |
SelectShow> |
- Go to the Scene section of the SKNode Inspector (if you don't see the SKNode Inspector, access it via View\Utilities\Show SKNode Inspector) and set the scene's size to
1136x640
- Drag a Color Sprite from the Object Library panel (if you don't see the Object Library panel, access it via View\Utilities\Show Object Library)
- Using the SKNode Inspector, change the Size and Position of the sprite to be respectively
1136x640 and {568,320} .
- Finally, set its Texture to be
bg.png
|
You now have the basics of your GameOverScene's layout. Let's add a label to tell the player if he won or lost the game. In GameOverScene.sks, drag a Label from the Object Library panel to the scene and give it the following attributes using the SKNode Inspector:
- Name:
gameOverLabel
- Position:
{568,320}
- Horizontal Alignment:
Center
(Default)
- Vertical Alignment:
Center
- Font: Just choose a font you like and that looks big enough. To help you, you can also set the label's Text to something, like
Game Over
(even if you will make that dynamic in code in just...right now!)
- Color:
White
(Default)
Adding some code
Go to File\New\File..., choose the iOS\Source\Cocoa Touch Class class template and click Next. Name the class GameOverScene, make it a subclass of SKScene, select Language>Swift, click Next, and then Create.
Replace the code in GameOverScene.swift with the following:
import SpriteKit
let GameOverLabelCategoryName = "gameOverLabel"
class GameOverScene: SKScene {
var gameWon : Bool = false {
// 1.
didSet {
let gameOverLabel = childNodeWithName(GameOverLabelCategoryName) as SKLabelNode!
gameOverLabel.text = gameWon ? "Game Won" : "Game Over"
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if let view = view {
// 2.
let gameScene = GameScene.unarchiveFromFile("GameScene") as GameScene!
view.presentScene(gameScene)
}
}
} |
This code is pretty standard and similar to what you've already seen. Some of the parts marked with comments might be new to you:
- The
didSet
observer attached to the gameWon
property is a Swift particularity called Property Observer. With that you can observe changes in the value of a property and react accordingly. There are two property observers: willSet
is called just before a property value change occurs, whereas didSet
occurs just after.
- When the user taps anywhere in the GameOver scene, this code just presents the Game scene again. Note how it instantiates a new
GameScene
object by unarchiving the Sprite Kit Scene you built with the Visual Editor, referencing it by its name without the .sks extension.
Note: Property Observers have a parameter that allows you to check the new value of the property (in
willSet
) or its old value (in
didSet
) allowing value changes comparison right when it occurs. These parameters have default names if you do not provide your own, respectively
newValue
and
oldValue
. If you want to know more about this, check the
Swift Programming Language documentation here:
The Swift Programming Language: Declarations
The new scene is done! Time to add it to the game. Open GameScene.swift and replace the println
call in didBeginContact(_:)
with the following:
if let mainView = view {
let gameOverScene = GameOverScene.unarchiveFromFile("GameOverScene") as GameOverScene!
gameOverScene.gameWon = false
mainView.presentScene(gameOverScene)
} |
Now, build and run the game. If you lose (on purpose or not), you will observe a weird behavior: An exception is raised in GameViewController.swift in an SKNode
extension class called unarchiveFromFile(_:)
for the following line:
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene |
Looking closely at the line, you should easily understand what's going wrong: Xcode's SpriteKit Game template provides a built-in extension to SKNode that enables the use of the Sprite Kit Visual Editor and .sks files. However, hardcoded in this method is the name of the template-provided subclass of SKScene that you have been using as your game scene. This is probably an Xcode 6.0.2 (and earlier) error that will be corrected in future releases. In the meantime, let's make this method more generic by replacing the faulty line by the following:
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene |
Build and run the game again. Everything should work fine.
Adding Some Blocks – and They Are Gone...
Go to GameScene.swift and add the following code to didMoveToView(_:)
to introduce the blocks to the scene:
// 1. Store some useful constants
let numberOfBlocks = 5
let blockWidth = SKSpriteNode(imageNamed: "block.png").size.width
let totalBlocksWidth = blockWidth * CGFloat(numberOfBlocks)
let padding: CGFloat = 10.0
let totalPadding = padding * CGFloat(numberOfBlocks - 1)
// 2. Calculate the xOffset
let xOffset = (CGRectGetWidth(frame) - totalBlocksWidth - totalPadding) / 2
// 3. Create the blocks and add them to the scene
for i in 0..<numberOfBlocks {
let block = SKSpriteNode(imageNamed: "block.png")
block.position = CGPointMake(xOffset + CGFloat(CGFloat(i) + 0.5)*blockWidth + CGFloat(i-1)*padding, CGRectGetHeight(frame) * 0.8)
block.physicsBody = SKPhysicsBody(rectangleOfSize: block.frame.size)
block.physicsBody!.allowsRotation = false
block.physicsBody!.friction = 0.0
block.physicsBody!.affectedByGravity = false
block.name = BlockCategoryName
block.physicsBody!.categoryBitMask = BlockCategory
addChild(block)
} |
This code creates five blocks with some padding so that they are centered on the screen.
- Some useful constants like the number of blocks you want and their width.
- Here you calculate the x offset. This is the distance between the left border of the screen and the first block. You calculate it by subtracting the width of all the blocks and their padding from the screen width and then dividing it by two.
- Create the blocks, configure each with the proper physics properties, and position each one using blockWidth, padding, and xOffset.
The blocks are now in place. Build and run your game and check it out!
Wait! What? Running the game now you might not see the blocks, but if you try to direct the ball where they are supposed to be, you will see that they are there, or at least their physics body are there. Also, if you lose the game you might or might not see the text appear in the Game Over Scene. This is another "issue" caused by the template-provided code in Xcode 6.0.1 (and earlier).
To fix this, go to GameViewController.swift, locate the following line:
skView.ignoresSiblingOrder = true |
And replace the value of the ignoresSiblingOrder
property to false
. When true
, this property, for performance reasons, does not take in account the order in which you added your sprites to the scene (either via the Visual Editor or by calling addChild(_:)
) when displaying them. As you are using a Sprite to display the game's background, sometimes this property can put some elements behind the background resulting in the bad behavior we were having.
The blocks are now REALLY in place. Build and run your game and check it out!
Breakout blocks at the beginning of the game. Nice and orderly.
Not quite as expected...
Hmm... not quite what you wanted, right? The blocks move around instead of being destroyed when touched by the ball.
In order to listen to collisions between the ball and blocks, you must update the contactTestBitMask
of the ball. Go to GameScene.swift and edit the already existing line of code in didMoveToView(_:)
to add an extra category to it:
ball.physicsBody!.contactTestBitMask = BottomCategory | BlockCategory |
The above executes a bitwise OR
operation on BottomCategory
and BlockCategory
. The result is that the bits for those two particular categories are set to one while all other bits are still zero. Now, collisions between ball and floor as well as ball and blocks will be sent to to the delegate.
The only thing left to do is to handle the delegate notifications accordingly. Add the following to the end of didBeginContact(_:)
:
if firstBody.categoryBitMask == BallCategory && secondBody.categoryBitMask == BlockCategory {
secondBody.node!.removeFromParent()
//TODO: check if the game has been won
} |
The above lines check whether the collision is between the ball and a block. If this is the case, you remove the block involved in the collision.
You might have also noticed the ball losing momentum after hitting a block. What you really want is the blocks the be static so that they don't take any energy. To do this add the following line to the for loop where you create each block in didMoveToView(_:)
:
block.physicsBody!.dynamic = false |
Build and run. Blocks should now disappear when the ball hits them.
Hopefully, you have a good understanding of bit masks in general and the contactTestBitMask
in particular. As you read earlier, bodies also have a second bit mask – the collisionBitMask
. It determines whether two bodies interact with each other. This allows you to control whether the ball bounces off the bricks or flies right through them, for instance.
If you want to practice working with bit masks, set the collisionBitMask
of the ball so that it goes straight through blocks while destroying them.
Solution Inside: Using `collisionBitMask` |
SelectShow> |
ball.physicsBody!.collisionBitMask = paddleCategory |
Setting the `collisionBitMask` to paddleCategory means that the ball now only physically interacts with the paddle and not the blocks. For the barriers around the screen everything stays the same as they do not have any category assigned.
block.physicsBody!.collisionBitMask = 0 |
The above line sets the collisionBitMask for the block to zero. This sets the block to not react to other bodies hitting it.
|
Have you overcome the challenge and tasted the thrill of victory? Time to give your player the same satisfaction of tasting victory :]
Winning the Game
To let the player actually win the game, add this method to GameScene.swift.
func isGameWon() -> Bool {
var numberOfBricks = 0
self.enumerateChildNodesWithName(BlockCategoryName) {
node, stop in
numberOfBricks = numberOfBricks + 1
}
return numberOfBricks == 0
} |
The new method checks to see how many bricks are left in the scene by going through all the scene’s children. For each child, it checks whether the child name is equal to BlockCategoryName
. If there are no bricks left, the player has won the game and the method returns true
.
Now, go back to didBeginContact(_:)
and replace the //TODO
line in the final if condition with the following:
if isGameWon() {
if let mainView = view {
let gameOverScene = GameOverScene.unarchiveFromFile("GameOverScene") as GameOverScene!
gameOverScene.gameWon = true
mainView.presentScene(gameOverScene)
}
} |
The code checks whether the player has won by calling the new method you just implemented. If they have won, you show the GameOverScene with a message indicating victory.
Build and run. You now have a working game of Breakout! Congratulations!
Finishing Touches
There are a few finishing touches you can add to add a final bit of polish to the game.
Smoothing the ball's velocity
As you play the game, you may have noticed that sometimes the ball can get super-fast or super-slow, depending on how you hit it with the paddle.
You can prevent these speed variations by overriding the default update(_:)
method for GameScene.swift like so:
override func update(currentTime: NSTimeInterval) {
let ball = self.childNodeWithName(BallCategoryName) as SKSpriteNode!
let maxSpeed: CGFloat = 1000.0
let speed = sqrt(ball.physicsBody!.velocity.dx * ball.physicsBody!.velocity.dx + ball.physicsBody!.velocity.dy * ball.physicsBody!.velocity.dy)
if speed > maxSpeed {
ball.physicsBody!.linearDamping = 0.4
}
else {
ball.physicsBody!.linearDamping = 0.0
}
} |
update(_:)
is called before each frame is rendered. In the case of GameScene.swift, there is no update(_:)
method since the default implementation has been used until now. Here, you override it with your own implementation.
You get the ball and check its velocity, essentially the movement speed. If it’s too high, you increase the linear damping so that the ball will eventually slow down.
If you build and run after this change, you should see the ball go back to a normal speed level when the speed increases too much.
Adding a Start Scene
How many times since you implemented the Game Over scene have you lost the game at launch because your ball was just moving too fast for you to catch it with the paddle?
If it never happened you are either really fast or smart enough to have placed your ball at the right place and applied the right impulse to it so that it hits the paddle the first time without you having to do anything!
That's because the game misses something every good game should have: a Start Scene!
You are now going to build one in as few steps as possible by reusing your Game Over scene!
First, you will have to make your app load a GameOverScene
as its first scene. Go to GameViewController.swift and replace the following line:
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene { |
With this one:
if let scene = GameOverScene.unarchiveFromFile("GameOverScene") as? GameOverScene { |
Build and run. You now have a Start Scene! Unfortunately, it welcomes the player with a big "Game Over".
Open GameOverScene.sks and change the Text attribute of the label to say something like "Tap To Play".
Build and run! It works!
Note: Why was this so easy? Remember, you used a property observer on the gameWon
property to put the right text in the gameOverLabel
. As you are not setting the gameWon
property when instantiating the first GameOverScene
, the property observer is never called and the scene displays the text from the .sks file.
Where To Go From Here?
You can download the final project for the Sprite Kit Breakout Game that you’ve made in this tutorial.
Obviously, this is a quite simple implementation of Breakout. But now that you have this working, there’s a lot more you can do. You could extend this code to give the blocks hit points, have different types of blocks, and make the ball have to hit some of them (or all of them) a number of times before they are destroyed. You could add blocks which drop bonuses or power-ups, let the paddle shoot lasers toward the blocks, whatever you dream up!
Let me know if you have any tips or suggestions for better ways to do things, and hope this comes in handy!
How To Create a Breakout Game with Sprite Kit and Swift is a post from: Ray Wenderlich
The post How To Create a Breakout Game with Sprite Kit and Swift appeared first on Ray Wenderlich.