Welcome back to a new article in this series on learning how to code with Swift. We are resuming from where we stopped yesterday evening, and that is with the continuation of Project 23, the Swifty Ninja game for iPad.
Hacking with Swift – Learning Project 23, episode 2
Follow the sequence
We will finally start creating enemies for real now.
At the top of the class let’s create a new enumeration called
SequenceType and make it conform to the
CaseIterable protocol. Types that conform to the
CaseIterable protocol are typically enumerations without associated values. When using a
CaseIterable type, we can access a collection of all of the type’s cases by using the type’s
Inside this enumeration we create eight cases: one enemy that is not a bomb, a random single enemy, two enemies of which one is a bomb, two, three or four random enemies, a chain and a fast chain of random enemies.
To manage all this we need a few more properties: a popup time Double variable which represents the amount of time to wait between the last enemy being destroyed and a new one being created, an empty array of
SequenceTypes to manage what enemy to create, an integer sequence-position variable to keep track of where we are in the game, a double chain delay variable set to
3.0 that represents how long to wait before creating a new enemy with the
fastChaing game modes and a Boolean used to know when all the enemies are destroyed and we are ready to create more.
At the bottom of our class we need to create a new method called
tossEnemies which will handle the
createEnemy method we have just written. In order, it will slowly decrease the popup time and the chain delay in order to make the game harder as it goes and also increase the physics world’s speed by about 2% every time, incremental, for the same reason.
It will then store the current sequence element in a constant and switch on it to behave differently according to which element was stored inside. I will let you read the first few cases by yourself and concentrate on the
.chain case which will call the
createEnemy method asynchronously after an amount of time equal to the chain delay divided by 5 and then multiplied progressively by up to 4. The
.fastChain option will perform the same thing just double as fast.
After this method gets called we will increase the value of the sequence position property by
1 and set the
nextSequenceQueued Boolean to
false. Let’s now jump up to the
didMove(to:) method. After the three method calls we set the
sequence array to contain seven game modes that will represent how the game behaves at the beginning. Then, for about a thousand times, we will create a new random element from the
SequenceType enumeration using the
.allCases.randomElement() call and append it to the
sequence array. Then we will call the
tossEnemies() method asynchronously after 2 seconds.
Right now the game is creating one run of enemies and stopping afterwards. We need to fiddle with the
update(_ currentTime:) method. At its top we need to check if we have active enemies and, if so, loop through each of them. If any enemy is at a Y position of
-140 or lower, we will remove it from the game and from the active enemies array. We need to remember to call this loop using a tuple and the reversed and enumerated version of the active enemies array. If instead we do not have any active enemy and we haven’t already queued the next enemy sequence (that is the
nextSequenceQueued is set to
false) we will schedule the next enemy sequence and set the Boolean to
This last part is an absolute milkshake for my brain but I think it will feel better in a few days when things start to sink. Because they will sink, right?!
Slice to win
We need to modify the
touchesMoved method so, after the code we have already written, let’s grab the nodes at locations as an array of
for case let node as SKSpriteNode in nodesAtPoint (remember, if you cannot manage to say all this in a single breath you will never become an iOS developer), that is for every instance of the
nodesAtPoint array that is found as being an
SKSpriteNode (which being a subclass of
SKNode can be included in such an array) if the node’s name will be “enemy” we will destroy the penguin, otherwise if the node’s name will be “bomb” we will destroy the bomb and also make the game end as, usually, when you touch a bomb (I’m sure plenty of you have tried!), you are just wiped out of the existence!
This method is again absurdly long so I will just summarise what it does and, if new or important, how it does so and then just move on.
The penguin destroyer code (sounds menacing!) will create an emitter node with the “sliceHitEnemy” file at the node’s position and add it to the scene, then clear the node’s name (by changing it to an empty string), disable the dynamic of its physics body, create a group of actions to scale and fade it out then run it as a sequence with the remove-from-parent one, add
1 to the player’s score , find the node’s index inside the array (if present, using
if let) and playing a nice and appropriate sound.
The apocalypse code will have to reference the node’s parent because the bomb is made up of several parts (remember the
createEnemy method?) as an
SKSpriteNode, create a different emitter, wipe outfits name and its dynamic, scale and fade it out as before, find its index, play a sound and call the
endGame(triggeredByBomb: true) method (which we have not written yet.
Before moving on let’s move to the end of the class and create two empty methods:
subtractLife. Then, inside the
update method, we need to change what we want to do when the enemy is at a position of -140 Y of lower. So,
if node.position.y < -140 we will remove every action from the node then, if the node’s name is “enemy” we will change its name to an empty string (which should be valid only if we missed the target of course) and call the
subtractLife method, which means that if we miss a penguin we will lose a life (before of course calling
removeFromParent and removing from the array. If the node’s name is “bombContainer”, instead we will just remove it from the parent and from the array of active enemies.
Our code doesn’t compile now but we hope to solve this shortly.
Game over, man:
To check if the game is already over and therefore manage the other methods we need a new property called
isGameEnded and set up to
false. Then, in the
touchesMoved method we set a check so that if the game is already ended we return from said method with a
guard statement (the same thing needs to be done at the beginning of the
Finally, we need to write the
endGame(triggeredByBomb: Bool) method. Once more, we check against the game being already over, set this Boolean to
true so that now the game is over and all the app will just stop generating things and react to touches, via setting the physics world’s speed to
0 and the
isUserInteractionEnabled to false. We also need to stop the sound effect and to kill the AV Audio Player.
If the game over is triggered by a bomb we want all of the lives images to be the appropriate red cross.
In case we missed a penguin we need to subtract a life from the player so, in the
subtractLife method we will subtract
1 from the
lives property, run the appropriate “wrong” sound file without waiting for its completion, set a new
life and create a control flow statement that will manage the sprite to be stored into the
life property. But wait … this property was never added to the scene, so how are we managing the actual crosses? It all seems to work without making sense. We created the lives empty crosses in the
createLives method but then, here, we just … ok … I got it … we create a property so that, after, when called, it gets the cross and it gets the cross substituted with the proper image… so chaotic… I’m sure there is a logic behind this but … does it have to be so chaotic?
Anyway… the game is over and it works.
I still find that the bug of the slicing sound always playing without stop is not gone and, when the game ends, everything freezes hopelessly, as if the app blocked. Not really a nice UX but I fear that this will be our homework.
So, project 23 done, so long… You can find the repository for this project with all my comments and an order of methods slightly different from the one proposed by Paul (I need at least to keep some order in what I do) here.
Please don’t forget to drop a hello and a thank you to Paul for all his great work (you can find him on Twitter) and be sure to visit the 100 Days Of Swift initiative page. We are learning so much thanks to him and he deserves to know of our gratitude.
He has about 20 great books on Swift, all of which you can check about here.
The 100 Days of Swift initiative is based on the Hacking with Swift book, which you should definitely check out.
If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).
If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).
You can also support me by buying Paul Hudson’s books from this Affiliate Link.
Anyways, thank you so much for reading!
Till the next one!