Reflections on Day 75
Yesterday was a good day, we learned a lot about SpriteKit once more and we are adding more tools to our belt.
The main thing I feel it’s missing from this course is a lesson on the mental and theoretical approach of app building, that is, when the screen is still blank, written code lines still at zero, how does one approach that phase? How do we plan an app?
Just going back through the tutorials is not really optimised time-wise, and it is not guaranteed to deliver good results. I have the feeling that we have to make thousands of mistakes in order to learn basically anything. I am the first saying to my students that mistakes are our greatest teachers, but I always add that you need to understand what is being taught and, together, understand what you did wrong. That is what a teacher is for.
The teacher is there to take your money and to give you knowledge in exchange. He/she is there to cut your road so that you can get from A to B in one tenth of the time you would take alone, all the while understanding all the reasons that brought you there.
I will never stress enough how the learning resources for iOS and macOS are not complete, how they are not thorough and that your best bet is still looking for people who—mostly by chance—solved your same problems and posted about that on Stack Overflow.
As people who follow this blog should know by now, I am a musician, so no IT or CS background and I desperately need to understand things to go on, not just mindlessly do and repeat, do and repeat. I checked the Computer Science degree in Torino (the nearest to where I live) and no Swift, no Objective-C is contemplated in the 5 years of course, only Java, Python, C#, and few other things according to your specialisation of choice.
This brings my thoughts to a bitter conclusion: all this “everyone can code” is sheer and fresh bullshit because one will never go beyond the very beginner level if another professional course has not been faced before. There are exceptions of course but I do not care, as exceptions simply confirm the rule.
I will keep digging in the Documentation every day more, until I find what I need, until I understand what I want to understand.
Now, let’s move to today’s review.
Project 20 – Review
- When looping over an array and removing objects, it’s best to loop backwards rather than forwards. If we loop forwards, removing items causes untouched items to move down, which often causes problems. For example, if we have five items in an array and we remove the item at index 3, the one which was item at index 4 (the last one) becomes item at index 3. If we know this is about to happen we can circumvent it but it can still cause issues as we need dedicated code for each iteration of the loop. In this way, using the
.reversed()
method, we can loop backwards directly, removing the item we need safely. - If we use
for case let
as a loop we can conditionally typecast each element of our loop.The body of the loop will be run only if the typecast is successful. This is a very interesting technique and I would really like to see it in more practical examples as, once more, we are not really given a general situation so that we can understand when to use it, but just this particular scenario which, already 24h later, I do not recall anymore and have to go back and reread. - We can add one SpriteKit node as a child of another.
SKNode
has achildren
property just for this purpose. I would really like to see a graphical representation of this, because just visualising it in the theatre of my mind is quite unpractical. - When following a path, SpriteKit can turn nodes so they always face the direction of movement. This lets them face forwards no matter what kind of path you specify. This is all very nice and wonderfully romantic but who is going to try to convince me that such a complex subject as Bezier curves could be understood in one day? I mean, starting from next SpriteKit project, we will all be assumed to know how to call this, how to use it and so on… really not realistic.
- To use a
UIBezierPath
with anSKAction.follow()
action we must pass in itscgPath
property. SpriteKit is a cross-platform framework, and UIKit types likeUIBezierPath
aren’t available on macOS. Wait a minute, wait a minute… what does this mean? Oh boy… let’s open Xcode and the Documentation and get to the bottom of this.
Summary
Creates an action that moves the node at a specified speed along a path.
Declaration
Discussion
When the action executes, the node’s
position
andzRotation
properties are animated along the provided path. The duration of the action is determined by the length of the path and the speed of the node.This action is reversible; the resulting action creates a reversed path and then follows it, with the same speed.
Parameters
path A path to follow.
offset If true, the points in the path are relative offsets to the node’s starting position. If false, the points in the node are absolute coordinate values.
orient If true, the node’s
zRotation
property animates so that the node turns to follow the path. If false, thezRotation
property of the node is unchanged.speed The speed at which the node should move, in points per second.
Returns
A new action object.
Now, if you recall the project from yesterday, we are told that a good speed is 200
, found after trial and error. But 200 what? Bananas for minute? Pizzas per day? Why aren’t we told that that speed parameter is calculated in points per second? Are we risking to learn too much? I recall a couple of my teachers that were refusing to answer my questions about cello technique and the clear reason was because they were afraid I would eventually learn too much and possibly take their place one day. And this was not only about me, this is a very typical attitude from teachers over here.
Anyway, what about that cgPath
property? Let’s option-click on it:
Summary
The Core Graphics representation of the path.
Declaration
Discussion
This property contains a snapshot of the path at any given point in time. Getting this property returns an immutable path object that you can pass to Core Graphics functions. The path object itself is owned by the UIBezierPath object and is valid only until you make further modifications to the path.
You can set the value of this property to a path you built using the functions of the Core Graphics framework. When setting a new path, this method makes a copy of the path you provide.
I mean, I do not want to sound arrogant, but it is quite clear that I do not understand everything that is written here but, at least, this is something! It doesn’t have to be Documentation material all the time, but I need a definition of what I use! Period!
But now I am puzzled: why is macOS not using UIBezierPath
? What is its equivalent? Is it not using Core Graphics? If not what is it using? Anyway, the explanation provided in the review is not enough: how can a learner benefit from knowing that “we have to pass .cgPath
because UIBezierPath
is not available on macOS”? WTH? This doesn’t explain anything to me… Bah…
- Calling
nodes(at:)
on a SpriteKit scene will return an array ofSKNode
. If we’re looking for something more specific, such as sprite nodes, we need to typecast the array elements. Nothing to say, surprised?! - Drawing a sprite with the blend mode
.replace
is faster than the default blend mode. The.replace
blend mode ignores transparency, which makes it faster to draw. Again, let’s try to dive in a bit more:.blendMode
is an Instance Property used to draw the sprite into the parent’s framebuffer1. This property is of typeSKBlendMode
which is one of SpriteKit enumerations and is defined as the modes that describe how the source and destination pixel colours are used to calculate the new destination color. In the documentation it is defined asenum SKBlendMode : Int
, which means that this enumeration stores raw integer values for each of its cases. For our bottomless curiosity here are the cases of this enumeration, of which.alpha
is the default one:
case alpha — The source and destination colours are blended by multiplying the source alpha value.
case add — The source and destination colours are added together.
case subtract — The source colour is subtracted from the destination colour.
case multiply — The source colour is multiplied by the destination colour.
case multiplyX2 — The source colour is multiplied by the destination colour and then doubled.
case screen — The source colour is added to the destination colour times the inverted source colour.
case replace — The source colour replaces the destination colour.
There is still an eight case called
multiplyAlpha
which doesn’t have a description. I will call it the mystery case.
- A
colorBlendFactor
value of1
adds maximum recolouring to anSKSpriteNode
. There is no performance cost to recolouring sprites like this. I got this wrong but never mind, let’s dive in to learn whatcolorBlendFactor
is:
Summary
A floating-point value that describes how the color is blended with the sprite’s texture.
Declaration
Discussion
The value must be a number between 0.0 and 1.0, inclusive. The default value (0.0) indicates the color property is ignored and that the texture’s values should be used unmodified. For values greater than 0.0, the texture is blended with the color before being drawn to the scene.
Much clearer, right? I know, it is a very technical language but we are here to learn it, or ain’t we?
- We can compare the color of one SpriteKit node with the color of another SpriteKit node. Colors like
.red
or.green
are shared values, so we can compare them freely. …and the meaning of “shared values” is, your grace? … - The
location(in:)
method ofUITouch
tells us where the user touched the screen. It’s useful so we can track what they tapped on. I am sure this is not hard stuff but I want to understand because I think we have never delved intoUITouch
. This page in the Documentation contains all the needed information but I will summarise them here in any case.
Class
UITouch
An object representing the location, size, movement, and force of a touch occurring on the screen.
Declaration
Overview
You access touch objects through
UIEvent
objects passed into responder objects for event handling. A touch object includes accessors for:A touch object also contains a timestamp indicating when the touch occurred, an integer representing the number of times the user tapped the screen, and the phase of the touch in the form of a constant that describes whether the touch began, moved, or ended, or whether the system canceled the touch.
To learn how to work with swipes, read Handling Swipe and Drag Gestures in Event Handling Guide for UIKit Apps.
A touch object persists throughout a multi-touch sequence. You may store a reference to a touch while handling a multi-touch sequence, as long as you release that reference when the sequence ends. If you need to store information about a touch outside of a multi-touch sequence, copy that information from the touch.
The
gestureRecognizers
property of a touch contains the gesture recognizers currently handling the touch. Each gesture recognizer is an instance of a concrete subclass ofUIGestureRecognizer
.
I do not want to go too much deeper than this right now but I still want to check the location(in:)
method before moving on:
Summary
Returns the current location of the receiver in the coordinate system of the given node.
Declaration
Parameters
node
A node that is a descendant of a scene presented in the window that received the touch event.
Returns
The location of the touch in the node’s coordinate system.
- Colouring a sprite means setting its
color
andcolorBlendFactor
properties. This lets us control the degree of coloration precisely. - A
UIBezierPath
describes a line that might be curved or straight. We can add as many points as we need to describe complex shapes. Let’s check this more before moving on to the challenges:
Summary
A path that consists of straight and curved line segments that you can render in your custom views.
Declaration
Discussion
You use this class initially to specify just the geometry for your path. Paths can define simple shapes such as rectangles, ovals, and arcs or they can define complex polygons that incorporate a mixture of straight and curved line segments. After defining the shape, you can use additional methods of this class to render the path in the current drawing context.
A
UIBezierPath
object combines the geometry of a path with attributes that describe the path during rendering. You set the geometry and attributes separately and can change them independent of one another. After you have the object configured the way you want it, you can tell it to draw itself in the current context. Because the creation, configuration, and rendering process are all distinct steps, Bézier path objects can be reused easily in your code. You can even use the same object to render the same shape multiple times, perhaps changing the rendering options between successive drawing calls.You set the geometry of a path by manipulating the path’s current point. When you create a new empty path object, the current point is undefined and must be set explicitly. To move the current point without drawing a segment, you use the
move(to:)
method. All other methods result in the addition of either a line or curve segments to the path. The methods for adding new segments always assume you are starting at the current point and ending at some new point that you specify. After adding the segment, the end point of the new segment automatically becomes the current point.A single Bézier path object can contain any number of open or closed subpaths, where each subpath represents a connected series of path segments. Calling the
close()
method closes a subpath by adding a straight line segment from the current point to the first point in the subpath. Calling themove(to:)
method ends the current subpath (without closing it) and sets the starting point of the next subpath. The subpaths of a Bézier path object share the same drawing attributes and must be manipulated as a group. To draw subpaths with different attributes, you must put each subpath in its ownUIBezierPath
object.After configuring the geometry and attributes of a Bézier path, you draw the path in the current graphics context using the
stroke()
andfill()
methods. Thestroke()
method traces the outline of the path using the current stroke color and the attributes of the Bézier path object. Similarly, thefill()
method fills in the area enclosed by the path using the current fill color. (You set the stroke and fill color using theUIColor
class.)In addition to using a Bézier path object to draw shapes, you can also use it to define a new clipping region. The
addClip()
method intersects the shape represented by the path object with the current clipping region of the graphics context. During subsequent drawing, only content that lies within the new intersection region is actually rendered to the graphics context.
Does all this sound useless to you? To me at least it doesn’t because at least I feel I have something under my feet. Let’s move on to the challenges.
Challenge 20
Challenge 1: for an easy challenge try adding a score label that updates as the player’s score changes.
Add a score label variable of type SKLabelNode
(implicitly unwrapped) and complete the didSet
part of the score
property so that it gets updated properly.
Inside didMove(to:)
initialise the score label node with a nice font, set its text property to be “Score: 0”, set its position to be a CGPoint(x: 16, y: 16)
if you want it to be in the bottom left corner like me, set its horizontal alignment mode to .left
and add the child to the parent node.
Challenge 2: make the game end after a certain number of launches. You will need to use the invalidate()
method of Timer
to stop it from repeating.
Create an integer global property called launchesCompleted
and set it equal to 0
. Inside launchFireworks
add a call to launchesCompleted += 1
at the end of the method and a control flow line so that the game timer will be invalidated when 5 launches are completed. I tried that and it works and I will most probably increase this to at least 10.
I had tried a different approach before, writing the control flow statement in the update
method but this caused an infinite loop (😂 !)…
I do not know if this is the best way to solve this challenge but, by now, my brain says that this works.
Challenge 3: use the waitForDuration
and removeFromParent
actions in a sequence to make sure explosion particle emitters are removed from the game scene when they are finished.
I added these three lines to the explode
method:
let wait = SKAction.wait(forDuration: 3.0)
let sequence = SKAction.sequence([wait, .removeFromParent()])
firework.run(sequence)
This was not explained in the course but was covered in the Zaptastic app of the Swift on Sundays series.
This completes our challenges for today. They were pretty straightforward which is a very welcome feeling after all these days of struggle.
You can find the code for these challenges here.
Please don’t forget to drop a hello and a thank you to him for all his great work (you can find him on Twitter) and be sure to visit the 100 Days Of Swift initiative page.
If you would like to give a look at his SpriteKit book, you can check this link. 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.
Thank you for reading.
Till the next one!
- A framebuffer is a portion of RAM containing a bitmap that drives a video display. It is a memory buffer containing a complete frame of data. Modern video cards contain framebuffer circuitry in their cores. This circuitry converts an in-memory bitmap into a video signal that can be displayed on a computer monitor. In computing, a screen buffer is a part of computer memory used by a computer application for the representation of the content to be shown on the computer display. ↩
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!