Introduction
Before I go on with this tutorial I just want to say a few words. Unlike the slot reviews you can read on pvplive, the game is supposed to be easy to play and that’s true. But the struggle in making a game is quite the opposite. You may want to know how virtual reality affects online slot games, click this link to see how can a game become more flexible to go with the future trend. The tutorial is getting quite long and for a few good reasons – I am trying not to leave a lot out and I am writing in in a raw format, meaning I am not refining it, rather making it in a way someone might create a game. Basically instead of showing you a huge block of code and explain it, I prefer going back and forth, adding small pieces of code, which is pretty much how coding a game works. To do this, it’s best to look at 666 casino and take it as a positive example, I use it as the basis for doing this. There are many ways of accomplishing almost everything, some could be better for specific situations and sometimes there is not much difference.
I am trying to give you as much options as possible and open your way for any changes you might want to do on your own.
We will continue along with our SpriteKit tutorial from where we left off in Part2 .
What we have at the moment is :
- creating characters ( hero and enemy )
- creating a background
- created some projectiles
- move characters
- some protocols for shooting and targetting
To be honest we might end up not using the protocols much, as it will just be a waste of time for a simple game like that, but I’ll figure this out at a later time.
The Plan
What I have in mind to accomplish in this Part 3 of the tutorial is :
- projectiles do damage
- characters die and re-spawn
- projectile clean up
Of course we might add things here and there.
A few words about collision detection. If you are not familiar with it – this is basically how in game or an app you will detect if two objects collide. There are different ways of doing and in fact Apple have their own built in method that you could use. However I’ve found out that since Swift 2.0 it’s been kinda buggy, especially when used in more complex games, where you could have a lot of object colliding at the same time. So rather than using the built in method we’ll create our own, but I will show you Apple’s built in method as well, just so you get familiar with it.
So let’s go over the idea of collision and object clean up, which is pretty standard for any game.
Apple’s built in contact delegate is pretty neat ( if it was working all the time 🙂 ) – they have built in functions like didBeginContact, which ( once your scene conforms to the contact delegate ) will be called 60 times per second ( or as many times as FPS you have ) . It will check if any physicsBodies contact and then depending on the bitMasks( more about that later ) you can define different actions. If you set up your own contact and collision detection you have to run a function( again as many times as FPS you have ) that will iterate over all objects of some sort, calculate distances and then if the distance is less than some threshold – do something.
Whenever you want to iterate over some objects you would usually put those in an array or in a separate layer.
So let’s say you want to check if any of the bullets is hitting the Hero – we’ll iterate ( enumerate ) through all the children in a layer, calculate the distances of each child ( bullet ) to the Hero and then do something if any of them has a distance, let’s say less some value. It’s a bit more work and it’s a bit more heavy on resources, but you have a lot more fine control over it.
Contacts and Collisions, Apple’s way
So first thing first – you need to make sure that the objects you want to collide have a physicsBody.
If you go back to the hero.swift and enemy.swift files you will see that we did define a physicsBody for each:
1 2 3 4 |
self.physicsBody = SKPhysicsBody(texture: texture, size: size) self.physicsBody?.dynamic = false self.physicsBody?.affectedByGravity = false // ( physical body stuff ) self.physicsBody?.mass = 1.0 |
This is all gonna help us with the collision detection.
Now if you want to use the Apple’s built in method it’s recommended to use something called BitMasks.
Each physicsBody has 3 BitMasks: categoryBitMask, collisionBitMask, contactTestBitMask.
categoryBitMask will define your object, so other objects can refer to it, collisionBitMask will help with physics – for example if you want objects to bounce off of other objects, push them away and so on, contactTestBitMask will help you in executing your own actions upon collisions between object – basically it will be called but it will not invoke the physics.
In other words objects objects that interact with each other, based only on their contactTestBitMask will not collide, bounce or push each other, but a method will be called, in which you could put your own action. I know it sounds a bit complicated and crazy if you’ve never used BitMasks, but no worries, it’s way less complicated than it sounds.
We’ll do some preparation, so we could later use the Apple’s collision methods with the BitMasks.
Inside our global.swift we’ll add a new struct to contain a few masks. Masks need to be UInt32 ( 32 bit numbers ):
1 2 3 4 5 6 7 8 9 |
struct bitMasks { // Bit Masks static let hero: UInt32 = 0x1 << 0 static let enemy: UInt32 = 0x1 << 1 static let projectileHero: UInt32 = 0x1 << 2 static let projectileEnemy: UInt32 = 0x1 << 3 static let noContact: UInt32 = 0x1 << 4 } |
Then we need to go back and add the masks to each of the classes. For our Hero it will be:
1 2 3 4 |
// COLLISION STUFF self.physicsBody?.categoryBitMask = bitMasks.hero self.physicsBody?.collisionBitMask = bitMasks.projectileEnemy self.physicsBody?.contactTestBitMask = bitMasks.projectileEnemy |
What that means is that the Hero’s category is ‘hero’ and we want the hero to interact with the Enemy’s projectiles – both collisions and contacts( we might not need collision actually… )
Add this for the Enemy:
1 2 3 4 5 |
// COLLISION STUFF self.physicsBody?.categoryBitMask = bitMasks.enemy // ship self.physicsBody?.collisionBitMask = bitMasks.projectileHero self.physicsBody?.contactTestBitMask = bitMasks.projectileHero |
See if you can finish the BitMasks on your own, adding them to the 2 projectiles.
One thing we’ll do extra is we’ll set the collision mask on both the Hero and the Enemy’s projectile to bitMasks.noContact , as we don’t want the projectiles bouncing off of the Hero ( at least for now )
In order to be able to detect Contact and Collisions we need to have our scene conform to Apple’s PhysicsContactDelegate, so add the protocol back next to the class declaration like this:
1 |
class GameScene: SKScene, SKPhysicsContactDelegate { |
Then right in the didMoveToView method we’ll declare these two:
1 2 |
physicsWorld.contactDelegate = self physicsWorld.gravity = CGVectorMake(0.0, 0.0) |
, first one is to say that we our Scene will be receiving all the events from the delegate and the second line is to define the gravity – for now none.
We are pretty much ready to start using the contact delegate. We need to call the didBeginContact function, which is predefined by Apple.
Please note how much pseudo code I put in my projects – it’s imperative for troubleshooting and I usually remove it completely only after everything has been tested out. A lot of times I even leave the print statements in, I just comment them out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// CONTACT CONTACT CONTACT CONTACT func didBeginContact(contact: SKPhysicsContact) { // HERO + ENEMY PROJECTILE print("Did BeginContact invoked") if contact.bodyA.categoryBitMask == bitMasks.hero && contact.bodyB.categoryBitMask == bitMasks.projectileEnemy || contact.bodyB.categoryBitMask == bitMasks.hero && contact.bodyA.categoryBitMask == bitMasks.projectileEnemy { if let projectileBody = contact.bodyA.node as? projectile { // body A is the projectile projectileBody.physicsBody?.categoryBitMask = bitMasks.noContact } else { if let projectileBody = contact.bodyB.node as? projectile { // body B is the projectile projectileBody.physicsBody?.categoryBitMask = bitMasks.noContact } } print("Hero got hit by a projectile !!!") } } |
Run the project and you should see the output in the console showing each of the contacts.
Now the enemy bullets should be going through the Hero.
Now let’s see what we are going to do about removing them, once they hit.
You could just remove them from the didBeginContact, but it might cause problems and it’s not recommended.
Instead we are going to declare an array of SKNodes, add them to it once hit and then clear that array every frame.
So, we’ll declare the array right after the class:
1 2 3 4 |
class GameScene: SKScene, SKPhysicsContactDelegate { var objectsToRemove = [SKNode]() |
Then we need to add the projectiles to the array in case they hit the Hero.
See if you can figure this out on your own.( if not just expand the solution below )
Next we need to create the cleaning up, as we don’t want the bullets that hit something to continue through the target, we want them to disappear.
We’ll add a new function to our scene:
1 2 3 4 |
func cleaning() { objectsLayer.removeChildrenInArray(objectsToRemove) // remove all Nodes marked for removal } |
Please note that we call the ‘removeChildrenInArray‘ on the objectsLayer, because the projectiles are children of it. If you were adding the projectiles to a different Node or to the scene itself you would need to change that.
Then we just need to call it. You should already have the update function in your scene ( if not you can always add it ), so just call the cleaning() in it like this :
1 2 3 4 5 6 |
override func update(currentTime: CFTimeInterval) { cleaning() } |
Please be careful of what you put in the update method as it’s been called every frame.
So if you run your game now you should have this:
One more thing that we need to do is clean up all the projectiles that go off screen, otherwise your app will keep consuming more and more memory, the FPS will drop and finally the app will crash. While the game is running you’ll notice that the number of the NODES displayed in the bottom right corner never goes beyond 18-20, however that counter only shows what’s visible on the screen. If you want to see all nodes in the scene you can put this line in your update method:
1 |
print(objectsLayer.children.count) |
You’ll see that the number of the nodes keeps increasing and that’s not good.
Let’s add a way of cleaning those up in our cleaning method:
1 2 3 4 5 6 7 8 9 |
objectsLayer.enumerateChildNodesWithName("projectile", usingBlock: { bullet, stop in if bullet.position.y < 0 || bullet.position.y > frameH || bullet.position.x < 0 || bullet.position.x > frameW { self.objectsToRemove.append(bullet) } } ) |
Basically we go over each item in the objectsLayer that is named ‘projectile’ ( we named them back in our projectile.swift ) and then checking if any of them is off the screen – if so add them to the objectsToRemove array.
Run the project again and you’ll see that the number of the objects now stays down.
Good job! That’s a major performance improvement.
Now let’s have the bullets do some damage to our Hero.
We already have everything we need, we just need to add this line in our didBeginContact method, right after ‘print(“Hero got hit by a projectile !!!”)’ :
1 |
spawnedHero.takeDamage(1) |
We also need to add something to our die() method, located back in our character.swift:
1 |
self.removeFromParent() |
It is a best practice to not actually remove a node from within itself, rather adding it to an array as we did with the projectiles, however I think it will be safe for killing our Hero and also I wanted you to see this way as well.
Run your project and you should see that the Hero disappears after the 10-th hit.
So far so good.
Lets add some labels to display the Health.
Add this right after the scene class declaration, before the didMoveToView:
1 2 |
var healthHero = SKLabelNode(text: "Hero HP: 10") var healthEnemy = SKLabelNode(text: "Enemy HP: 10") |
Now we’ll create a few SKColors for later use, just add them to your global.swift:
1 2 3 4 5 6 |
// COLORS let blueColor = SKColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1) let whiteColor = SKColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1) let greenColor = SKColor(red: 0.2, green: 1.0, blue: 0.2, alpha: 1) let redColor = SKColor(red: 1.0, green: 0.2, blue: 0.2, alpha: 1) |
Next we want to set up the labels in a separate function, so we can call it again, whenever we want to reset the game.
Create this new function at the bottom of our scene:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func setupLabels() { healthHero.position = CGPoint(x: 100, y: 150) healthHero.fontColor = whiteColor healthHero.fontName = "Copperplate" healthHero.text = "Hero HP: 10" healthHero.fontSize = 22 healthHero.zPosition = layers.projectiles objectsLayer.addChild(healthHero) healthEnemy.position = CGPoint(x: frameW - 100, y: 150) healthEnemy.fontColor = whiteColor healthEnemy.fontName = "Copperplate" healthEnemy.text = "Enemy HP: 10" healthEnemy.fontSize = 22 healthEnemy.zPosition = layers.projectiles objectsLayer.addChild(healthEnemy) } |
Now we need to update the Hero’s HP after each hit, add this line right after the ‘spawnedHero.takeDamage(1)‘ in the didBeginContact:
1 2 3 4 5 |
if spawnedHero.health > 0 { healthHero.text = "Hero HP: \(spawnedHero.health)" } else { healthHero.text = "Hero is Dead :/" } |
Run your project and you should see the labels and the Hero label updating properly.
We also need to add a way to restart the scene and re-spawn our Hero after it dies.
You can create a button, using a SKNode or SKSpriteNode, for now I am just gonna use a SKLabelNode, so let’s add another one to the scene:
1 |
var restartLabel = SKLabelNode(text: "RESTART") |
Then add it’s set up in the setupLabels method:
1 2 3 4 5 6 |
restartLabel.position = CGPoint(x: frameW/2, y: 100) restartLabel.fontColor = whiteColor restartLabel.fontName = "Copperplate" restartLabel.text = "RESTART" restartLabel.fontSize = 35 restartLabel.zPosition = layers.projectiles |
And you can see that I am not adding it to the objectsLayer just yet, as we won’t need that visible for now.
We’ll add this line in the didBeginContact, right after we change the text for our Hero, when our Hero is dead:
1 |
objectsLayer.addChild(restartLabel) |
That will show the label, once our Hero dies.
Now we have to add our restart method:
1 2 3 4 5 6 7 8 |
func restartGame() { objectsLayer.removeAllChildren() spawnedHero = spawnHero(heroPosition) spawnedEnemy = spawnEnemy(enemyPosition, level: 2) setupLabels() } |
Now we need to tie up the Label to the action. In SpriteKit there is no native way of creating buttons, however you can detect if there is a tap/touch on any object, so we’ll use that.
Find the built in touchesBegan method ( if missing just create it ) and change it to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { if let touch = touches.first as UITouch! { let location = touch.locationInNode(self) if restartLabel .containsPoint(location) { restartGame() } } } |
Start your project and after your Hero dies you should be able to restart it, tapping on the RESTART label.
Now we need a way for our Hero to shoot back.
Currently we have him shoot one bullet upon spawning.
However we need to be able to repeat that action somehow.
I want to create a way for the user to shoot, but let’s say every 2 seconds only, so we need to create a button, that after use get’s disabled for 2 seconds.
First let’s create a new SKLabel in our scene:
1 |
var shootLabel = SKLabelNode(text: "SHOOT") |
Then add this to our setupLabels() method, so we can set the new label up:
1 2 3 4 5 6 7 |
shootLabel.position = CGPoint(x: frameW/2, y: 125) shootLabel.fontColor = blueColor shootLabel.fontName = "Copperplate" shootLabel.text = "SHOOT" shootLabel.fontSize = 35 shootLabel.zPosition = layers.projectiles objectsLayer.addChild(shootLabel) |
Can you link the new Label to the shooting action of our Hero ?
Try and figure it out, if not – check the solution below.
Try running your project and you should be able to shoot, using the new Button/Label.
However, there are a few problems. You can shoot too quickly, bullets are bouncing off of the Enemy and they are not passing through. Let’s deal with the first problem.
There are multiple ways of solving this problem, but here is how we are going to do this – we’ll create a Boolean flag called ‘shootingAllowed‘ and then set it to false after each shooting action, along with that we’ll start a delayed action, that will set it to true after 2 seconds.
Inside the the touchesBegan we’ll slightly change our latest method as well.
Let’s add the Boolean first, at the top of the scene:
1 |
var shootingAllowed: Bool = true |
Add this line
1 |
shootingAllowed = true |
to our restartGame() method, just to make sure you can shoot when you restart the game.
We’ll need to add the delayed function, I have this ready, so you can just add this to the scene:
1 2 3 4 5 6 7 |
func delay(delay:Double, closure:()->()) { dispatch_after( dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure) } |
You’ll see how to use that function in a second.
Go back to the touchesBegan method and change our shooting action to this:
1 2 3 4 5 6 7 8 9 10 11 |
if shootLabel .containsPoint(location) && shootingAllowed == true { spawnedHero.shoot() shootingAllowed = false shootLabel.fontColor = redColor delay(2.0, closure: { () -> () in self.shootingAllowed = true self.shootLabel.fontColor = blueColor }) } |
Basically we are saying – when the SHOOT label is tapped AND if shootingAllowed is true -> then shoot, forbid the shooting and re-allow it in 2 secs.
We are also changing the color of the button, so it’s visually obvious if you can shoot or not.
Make sure to change the collision mask of Hero’s bullet:
1 |
self.physicsBody?.collisionBitMask = bitMasks.noContact |
, so the bullets can pass through the Enemy freely.
Ok, so far so good, we got to making our bullets hurt the enemy.
Contacts , a custom way
So, let’s see about this, we can make it from the point view of the bullets, the Enemy or the scene.
For our little project it doesn’t really matter, but I would like to make it from the point of the scene.
I found that we need to change the Hero’s shooting method a bit to look like this:
1 2 3 4 5 6 7 |
func shoot() { let newBullet = bulletHero() newBullet.position = CGPoint(x: self.position.x + 10, y: self.position.y) objectsLayer.addChild(newBullet) newBullet.physicsBody?.velocity = CGVector(dx: 250, dy: 0) } |
We are pretty much just mirroring the way the Enemy bullets are set up.
However it’s going to be hard to filter for the Hero bullets from the enemy Bullets, since they are both named “projectile“.
Let’s rename them, add an extra line after the super.init() in each subclass, for each projectile, enemy:
1 |
self.name = "projectileEnemy" |
and hero:
1 |
self.name = "projectileHero" |
However we already used the name “projectile” in our cleanup method.
We’ll split our method, as we need to now search for 2 differently named Nodes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func cleaning() { objectsLayer.removeChildrenInArray(objectsToRemove) // remove all Nodes marked for removal objectsLayer.enumerateChildNodesWithName("projectileEnemy", usingBlock: { bullet, stop in if bullet.position.y < 0 || bullet.position.y > frameH || bullet.position.x < 0 || bullet.position.x > frameW { self.objectsToRemove.append(bullet) } } ) objectsLayer.enumerateChildNodesWithName("projectileHero", usingBlock: { bullet, stop in if bullet.position.y < 0 || bullet.position.y > frameH || bullet.position.x < 0 || bullet.position.x > frameW { self.objectsToRemove.append(bullet) } } ) } |
We’ll use the second enumerate action in our Enemy, but we’ll just wrap it inside an action, so we can repeat it over and over again.
We’ll add one more function to make our lives easier, just add it before the class declaration of our scene, right after the import:
1 2 3 |
func distanceBetweenPoints(first: CGPoint, second: CGPoint)-> CGFloat { return hypot(second.x - first.x, second.y - first.y); } |
This will take two CGPoints and calculate the distance between them.
We placed it outside the class, so we can use it from within different classes and for our little project that will work like a charm.
Here is our method for checking if the enemy got hit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
func checkIfHit() { let checkAction = SKAction.runBlock { () -> Void in print("check action") objectsLayer.enumerateChildNodesWithName("projectileHero", usingBlock: { bullet, stop in let distance = distanceBetweenPoints(spawnedEnemy.position, second: bullet.position) print("Distance is: \(distance)") if distance < 100 { self.objectsToRemove.append(bullet) print("Enemy got Hit") spawnedEnemy.takeDamage(4) if spawnedEnemy.health > 0 { self.healthEnemy.text = "Enemy HP: \(spawnedEnemy.health)" } else { self.healthEnemy.text = "Enemy is Dead :)" objectsLayer.addChild(self.restartLabel) } } } ) } let delayAction = SKAction.waitForDuration(0.5) let sequenceAction = SKAction.sequence([checkAction,delayAction]) let sequenceRepeater = SKAction.repeatActionForever(sequenceAction) objectsLayer.runAction(sequenceRepeater) } |
If you have never used actions it might look a bit overwhelming, but it’s quite simple.
If you examine how the Actions are nested you’ll figure it out pretty easily. The checkAction is what’s interesting, as we do all the calculations there. We’ll check our objectsLayer for any Nodes, named projectileHero, then calculate the distance to the Hero. I decided that for me the threshold will be 100, but you can adjust that. If the distance is < than that – we are having the Enemy take 4 damage and changing the labels appropriately.
Now just call the method at the bottom of the didMoveToView:
1 |
checkIfHit() |
I also noticed that we were setting the Health labels statically, so the Enemy Health was showing to be starting at 10, even though the enemy had 16hp. Let’s change that in our setupLabels methods, change the lines to be like that:
1 |
healthHero.text = "Hero HP: \(spawnedHero.health)" |
and
1 |
healthEnemy.text = "Enemy HP: \(spawnedEnemy.health)" |
One more thing – we need to disable the SHOOT button, once our Hero is dead.
Generally is good to have a Boolean flag for GameOver, so we’ll just create one right before the class declaration of the scene, as we need to be able to access it from the scene and from the character class:
1 |
var gameOver: Bool = false |
Then set it to true in the die() method in the character.swift:
1 2 3 4 |
func die() { gameOver = true self.removeFromParent() } |
and then set it back to false in the restartGame() method:
1 |
gameOver = false |
I will actually set it in the takeDamage method inside each character as well, just to be safe:
1 2 3 4 5 6 7 |
... if health <= 0 { print("You are dead now") gameOver = true die() } ... |
Then in our shooting action in touchesBegan, let’s check if the game is over as well:
1 |
if shootLabel .containsPoint(location) && shootingAllowed == true && !gameOver { |
An easier way would’ve been to just remove the SHOOT label like this ( you can still do that as well ), just add the removing line to the top of our checkAction ( as this one is repeating every .5 seconds ):
1 2 3 4 5 6 7 8 |
func checkIfHit() { let checkAction = SKAction.runBlock { () -> Void in if spawnedHero.health <= 0 { self.shootLabel.removeFromParent() } ... |
There is even a better way of doing this. We are already setting the gameOver = true in our die() method, regardless of which character dies, so we could just re-work our whole method to check if the game is over:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
func checkIfHit() { let checkAction = SKAction.runBlock { () -> Void in if gameOver { self.shootLabel.removeFromParent() } objectsLayer.enumerateChildNodesWithName("projectileHero", usingBlock: { bullet, stop in let distance = distanceBetweenPoints(spawnedEnemy.position, second: bullet.position) print("Distance is: \(distance)") if distance < 100 { self.objectsToRemove.append(bullet) print("Enemy got Hit") spawnedEnemy.takeDamage(4) if !gameOver { self.healthEnemy.text = "Enemy HP: \(spawnedEnemy.health)" } else { self.healthEnemy.text = "Enemy is Dead :)" objectsLayer.addChild(self.restartLabel) } } } ) } let delayAction = SKAction.waitForDuration(0.5) let sequenceAction = SKAction.sequence([checkAction,delayAction]) let sequenceRepeater = SKAction.repeatActionForever(sequenceAction) objectsLayer.runAction(sequenceRepeater) } |
I am also changing the shooting delay for the Enemy, to make it a bit more interesting and give myself a chance at winning 🙂 – feel free to play around with this:
1 |
let shootingDelay = SKAction.waitForDuration(2.3, withRange:2.0 ) |
Last thing we are going to do is make a little change to our touchesBegan method, we need to make sure that you can restart the game only if the game is over, so we’ll change this line:
1 |
if restartLabel .containsPoint(location) { |
to:
1 |
if restartLabel .containsPoint(location) && gameOver { |
This will conclude part 3 of this tutorial.
If you need to review, please go back to part 1 or part 2.
Here is how your final project should look like:
Here you can find all the files on GitHub ( created a new branch for part 3 ):
SpriteKit Game from Scratch, part 3, GitHub
Any questions or comments – please comment below. Cheers !