100 Days of Swift – Day 84

Reflections on Day 83

Yesterday was a crazy day, absolutely but this morning, thanks also to the help of the always present and immensely kind Rob Baldwin (here on Twitter) I was able to find a solution to yesterday’s challenge 2!

Starting from where I had left it yesterday I cancelled everything after the if isAlertShown == false statement so that there would be no alternative. Then, inside the update method, I modified the default case so that it would change the beacon identifier label’s text back to “No beacon detected” and, then, I used GCD to set the Boolean back to false only after 10 seconds, which seems the delay needed to have the system realise a beacon has disappeared from the radars.

The interesting thing is that as soon as I turn off the beacon the screen turns grey and the text label changes, meaning that the system has detected the beacon to have disappeared.

But never mind, this is really nice now, such a little change and such a great result!

Hacking with Swift – Learning Project 23, episode 1

… success is not final, failure is not fatal: it is the courage to continue that counts (Winston Churchill).

With this quote we start what is one of the longest project in the series! The approach Paul is using (three steps forward, one step back, as he calls it) is for sure very effective but, personally, I would have preferred a 2+1 instead of a 3+1 approach. Sure this, would have taken 150 days instead of 100 but that is probably how realistically it will end for me and for many other beginners!

He hopes that when we finish this we will be able to do something new without thinking twice about it. The first thing I want to do when I finish is to purchase SnippetsLabs and go over all projects we have done and create a library of code where I will store how to do things. Remembering which project had what code could be quite lethal, I suppose.

But let’s get started…

Setting up

This is the usual set-up for any SpriteKit project (cleaning, iPad-only, etc. …). We need to import the audio files and the scene files to the project, just under the Info.plist file and, inside the asset catalogue, the images contained in this repository at the Project23-files folder.

Basics quick start: SKShapeNode

We start off by creating the background for our scene with an SKSpriteNode with an image named “sliceBackground”; we position it in the center of the screen (CGPoint(x: 512, y: 384), set its blendMode to .replace (which means that the source colour replaces the destination colour), set its .zPosition to be -1 (i.e.: behind everything else) and we add it to our main scene.

Next, we set our physics world’s gravity to be a Core Graphic vector with a delta x of 0 (that is, the gravity doesn’t influence the horizontal movement of bodies) and a delta y of -6, so that the gravity is quite less than the Earth’s one (which is -9.81). To be precise (and picky as me!) the gravity property of the physics world class property is defined as a vector that specifies the gravitational acceleration applied to physics bodies in the physics world. The components of this property are measured in meters per second and the default value is (0.0, -9.8), which represent’s Earth’s gravity. As a final check, this means that, in our world, an object that falls and that is influenced by Earth’s gravity will fall at a speed of 9.8 m/s, while, in our app, the same object will fall at 6 m/s.

We then set the speed of our physics world to be 0.85. This value measures the rate at which the simulation executes and has a default value of 1.0, which means that the simulation runs at normal speed. A value other than the default changes the rate at which time passes in the physics simulation. For example, a speed value of 2.0 indicates that time in the physics simulation passes twice as fast as the scene’s simulation time. A value of 0.0, instead, pauses the physics simulation.

At the end of the didMove(to:) method we call the three methods that we are going to create in a very short while: createScore, createLives and createSlices.

Create score

This is quite simple (because we have already done it many times). We set the game score label to be an SKLabelNode with a font named “Chalkduster”, set its text’s horizontal alignment mode to be .left, its font size to 48 and its position to be (8, 8) in the scene. We also set the score property to be 0 and add the child to the scene.

Interesting is the order in which things are done: we first add the child to the scene and then set its position. I wonder if doing the things in reverse order would have changed the final result.

Create lives

The player will have three lives before losing the game so we create a loop from 0 up to and not including 3 where we create a new SKSpriteNode with an image named “sliceLife”, we set its position to be x: (834 + (i * 70)) and y: 720. We then add the child to the scene and to the array.

In this occasion we set the position first and added the child afterwards … interesting to say the least.

Create slices

Here I took the freedom to reorganise Paul’s creation code because otherwise my very limited brain would have been quite lost: first of all we create two global variables of type SKShapeNode called, respectively, “activeSliceBG” (BG for background) and “activeSliceFG” (for foreground). But what is an SKShapeNode? An SKShapeNode is a mathematical shape that can be stroked or filled. It allows us to create on-screen graphical elements from mathematical points, lines and curves. The advantage of this over rasterised graphics, such as those displayed by textures, is that shapes are rasterised dynamically at runtime to produce crisp detail and smoother edges (I guess this is enough of an introduction, but if you feel like knowing more I have prepared a playground with some code inside that reproduces—or tries to—Apple’s documentation).

At this point, inside the createSlices method we can instantiate the two shape nodes which will have, respectively starting from the background one, a .zPosition of 2 and 3, a .strokeColor of UIColor(red: 1, green: 0.9, blue: 0, alpha: 1) (which should be some kind of yellow) and UIColor.white and a .lineWidth of 9 and 5, so that the second looks like if inside the first and making it glow!

Shaping up for action: CGPath and UIBezierPath

Touch methods

First thing, we need to implement some touch methods. Some we have already encountered, others we have not. Let’s begin with touchesBegan, which tells this object that one or more new touches occurred in a view or window. Inside it, we need to check that there are some touches and grab the first one of them, remove all the points from the activeSlicePoints array (while keeping the capacity, even if I do not yet understand why this is—maybe to avoid the action of reallocating memory?), grab the location of the touch and append it to our now emptied array. We will then call the redrawActiveSlice() method (not written yet), remove all actions from our two active slice properties (the reason will become clear in just a moment) and set their alpha to 1 so that they are fully visible.

The next method is the touchesMoved one, which tells the responder when one or more touches associated with an event changed. UIKit calls this method when the location or force of a touch changes. Inside it, we need to check that there are some touches and pick out the first one of them, then grab its location and append it to our array of active slice points, before calling a method called redrawActiveSlice(). After that we will say:

if !isSwooshSoundActive {

…of course we need to create the appropriate Boolean, set it to false and, in our Helper methods section, create the playSwooshSound one (don’t worry, we’ll complete it soon enough).

Finally we need the touchesEnded method, which will make the two active slices fade out in a quarter of a second. Now it is clear why we had to remove all actions from them before. If not, we would have them in the middle of a fade-out action and being created at the same time.

More helper methods

The redrawActiveSlice needs to do several things: first, if we have fewer than two points in our active points array, we need to clear the shapes and exit the method (you will remember from the mathematics class that, to have a line, we need at least two points!); second, if we have more than 12 of them we need to remove the extra ones so that it looks that, while we are drawing, the first points are disappearing. This is done calling a nice new method I had never encountered on the array called .removeFirst(k: Int). Third we need to start the line at the position of the first swipe point, then go through each of the other drawing lines by each successive point. This is accomplished by creating a new UIBezierPath, moving it to the first element of our active points array and then, looping over its elements from second to last, add a line from the last point where the line stopped to the next point in the array. Fourth and finale: set the path.cgPath to be the .path property of our active slices.

Finally, we implement our playSwooshSound method which sets the isSwooshSoundActive to true, generates a random number between 1 and 3, assigns the correct sound file to the soundName local property, creates a swooshSound action to play the sound file named soundName (which should also wait for completion) and then run said action with a closure to asynchronously reset the Boolean to false.

This all works very nicely but I have already found a little bug: if instead of swiping on the screen I just keep my finger on the screen and move it slowly without ever lifting it, sounds get created very annoyingly without stop. Another thing is that if I touch with one of my finger-bones instead of with the fleshy part, the sound gets played but no line is created.

Enemy or bomb: AVAudioPlayer

This part of the app is about 80 lines of code in a single method plus a little extra thing, so it is quite hard.

The first thing we have to do is to create a new enumeration called ForceBomb, which will determine when we need to force the creation of a bomb or not. This will make the game a bit kinder for players at the beginning. This enumeration will have three cases: never, always and random, which will be called almost all the time.

Then, somewhere toward the end of our helper methods we create a new method: func createEnemy(forceBomb: ForceBomb = .random). Inside it, we create a new sprite node enemy property and a random number stored within the new enemyType property. We then check if the force bomb parameter is equal to .never, in which case we set the enemy type forcefully to 1, else if it is set to .always we will force the enemy type to be 0. All this will come clear much later but, by now, we can say that if we have the 0 we will create a bomb, while with the 1 we will create a penguin.

Now we have a huge if-else statement. First we manage the if enemyType == 0: we begin with created a new SKSpriteNode container that will hold the fuse and the bomb image as children, setting its z position to be 1 (in front of the rest). We also call this container “bombContainer”. Second we create the bomb image, name it “bomb” and add it to the container. Third, we import the AVFoundation framework, create a new property var bombSoundEffect: AVAudioPlayer? and, if this property is not equal to nil, we call stop() on it and set it to nil actively! Fourth, we create a new bomb fuse sound and then play it. This is done by extracting the audio file from our Bundle, exactly how we extracted files in the first projects (if let path = Bundle.main.url(forResource:withExtension:), then we optionally try to set a sound property to the contents of the just extracted path of our AVAudioPlayer and, if all this succeeds, we set this sound property inside the bombSoundEffect and we then call sound.play(). Fifth and final: we create an emitter node with the file named “sliceFuse”, set its position to be (76, 64) so that it is at the top of the bomb and then add this node to the enemy node.

At the end of this method we have the else statement where we create a new sprite note from an image called “penguin”, run the action to play the sound file named “launch.caf” without waiting for its completion and set the name of the enemy to be “enemy”.

The last thing we need to do is to create code for the positioning of the enemy: first, we give the enemy a random position off the bottom edge of the screen, by using a CGPoint with a random x value and a fixed y value just off the bottom of the screen and assigning this random position to the enemy’s position.

Second we need to create a random angular velocity, which is how fast something should spin. To increase the unpredictable feeling of this game we also create this as a random CGFloat between -3 and 3. The Documentation doesn’t specify which values should be good for the angular velocity, nor Paul explains them so we will just have to take them for granted (at least I tried, right?).

Third, we need to create a random X velocity (that will determine how far to move the body horizontally) that takes into account the enemy’s position. Instead of writing here some values you can see for yourself, I will just tell you that, if an enemy is created next to any of the edges, it will move in the opposite direction at a quite dramatic speed, which if it will be nearer to the center, it will move at a more moderate pace (all of this, completely random!).

Fourth, we need to create a random Y velocity, which Paul has determined as a random value between 24 and 32. A physics body’s velocity is measured in meters per second, and, looking below in code, we see Paul multiplying this value by 40!! I am sure there must be some other catch because if we even get 24 as a random value, that is 24 x 40 = 960 m/s, which means 3456 km/h!! That’s Mach 3! We should theoretically hear a sound wall breach!!! But then this is a CGVector and … my math memory is not enough for this… there is a x and y component which should bring a result of a speed in meter per second … I have asked for help on the Slack, possibly someone will explain this to all of us because even Apple’s Documentation assumes one should know this.

Fifth and final: we should give all enemies a circular physics body where the collisionBitMask is set to 0 so they don’t collide with each other. So, in short, we set the enemy’s physicsBody to be an SKPhysicsBody with a circle of radius 64 (that’s half the size of the image used for the sprite), its velocity to this supposed vector which has been very kindly pre-calculated for us, its angularVelocity to the random angular velocity we approached earlier and its collisionBitMask to 0. We then add the enemy to the scene and to the active enemies array.

But we are not over yet: we need to implement the update method because otherwise the sound would continue to play even if there would be no bombs on screen. This method gets called once every frame, which means just absurdly often! So, every time this gets called, we declare a variable called bombCount and set it equal to 0 then, for every node in our active enemies array, if the node’s name is “bombContainer” we increase the bomb count by 1 and break out of the loop because there is no need to go any further. If the bomb count has instead stayed at 0 we stop the sound effect and set it back to nil.

I would like to delve a bit into AVFoundation but its 1.35am and I really need some sleep! Till tomorrow, folks!

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!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: