Welcome to my 69th day of Swift learning!
Yesterday I managed to catch up a little but I care very little about that. My only goal here is pure understanding, knowledge and that power feeling it gives you! I know, I have an inner Sith soul but they were not wrong in everything!
So, today it should be a game day. I am personally not fanatic with SpriteKit but by no mean I want to say anything against it as I am in no position for that.
Let’s get going. Today’s menu is: pixel-perfect collision detection, the Timer
class, linearDamping
among other things.
Setting up
Create a new Xcode Project in the form of an iOS Game App and, if you want to follow how I do things, place it under source control, create a remote repository and push it to GitHub. Then from the Hacking with Swift repository, import the images for project 17 in the assets folder and the two scene-kit files into the project (I had a fresh install of Xcode so, on import, the “copy items if needed” was deselected and the radio button was on “create folder reference”, so please invert all that if you are in my same situation).
Perform the same cleaning routine described in previous SpriteKit projects.
Space: the final frontier
Thanks to Star Trek™ we can finally experiment with spaceships. This looks very familiar to a recent Swift on Sundays stream, which is nice.
First step: properties
Add a starfield
implicitly unwrapped SKEmitterNode
, a player
implicitly unwrapped SKSpriteNode
, a scoreLabel
implicitly unwrapped SKLabelNode
and a score
integer property set to 0
with a didSet
property observer that will update our label’s text when changed.
Second step: the didMove
method
Set the background color to black, then initialise the three properties just declared above.
For the star field’s position set it to be (1024, 384)
, that is at the center of the right edge, and advance its simulation time by 10 seconds. Add that child and set its zPosition
to be -1
, behind everything else. Why this is done after adding the child is beyond me, really, we have always done it the other way around before.
For the player put it at (100, 384)
, that is towards the left edge but still in the center, set its physics body to have the texture:size:
initialiser so that its physical body tries to be as similar as possible to its real contours, set its physics body’s contact test bit mask to 1
so that we will be notified when other bodies collide with it and add the child.
For the score simply initialise it with any font you like, position it to (16, 16)
, set its text horizontal alignment mode to be left-aligned and add the child.
Set the score to 0
(why?) so that it triggers the label…
Set the physics world’s gravity to be .zero
, we are in space after all and make it be its own contact delegate (the one we should send notifications to). To make this not-problematic add conformance to the SKPhysicsContactDelegate
protocol.
Bring on the enemies: Timer
, linearDamping
, angularDamping
Add an array of strings to the properties section containing the string identifier of the images we added, an optional Timer
variable and a false boolean called isGameOver
.
The Timer
class is defined as “a timer that fires after a certain time interval has elapsed, sending a specified message to a target object”. So, inside didMove(to:)
set that variable to be equal to a Timer.scheduledTimer
with five parameters: a timeInterval
of 0.35 seconds, a target of self, a selector of the createEnemy
method we have not written yet, a userInfo
of nil
and set true
to its repeating parameter. We are reminded that this scheduledTimer
initialiser not only creates the timer, but it also fires it.
Create the createEnemy
@objc
method. Be sure that you have a random element of the possibleEnemies
array, create a sprite from an image named after that element and position it to the right edge of the screen at a random vertical position and add it to the parent view.
For its physics body we want to respect these priorities: its texture needs to be respected as well as its size, we want a category bit mask of 1, exactly as our player sprite, we want it to move very fast toward the player (with a .velocity
value of CGVector(dx: -500, dy: 0)
), with a good spinning (.angularVelocity = 5
) but we want it to never slow down nor stop spinning so we set both its linearDamping
and angularDamping
properties to 0
.
To make the app not become heavy as hell we need to remove the nodes when they are off screen and this is done in the update
delegate method with a looping over every node in the children’s array that will remove the node from its parent if its horizontal position will be less than -300
(that is way off screen).
Still inside this method, if the game isn’t over, we want to increase the score by 1. If we build and run now we see the score increasing crazily fast which makes me ask: “what is that time interval?”. It seems that the TimeInterval
is just a type alias of the Double
data type, interesting. The Documentation says it is always specified in seconds so, in theory, our score should go up by 1 each second but instead it is spinning crazily upwards… Why is it so?
Making contact: didBegin()
To handle the player’s movement we need to add the touchesMoved
method. First we check if there is a touch then we capture its location. We need to forbid the player from going off screen as that would be cheating so we are keeping the most external 100 points free. We then set the player’s position to the location we just captured.
When the player touches any debris the player should explode and be removed from the game.
Conclusions
That’s it, our game is finished.
I am a bit disappointed that we never ever polish down an app, they are all “useless” as they are. I mean, very interesting but if we need to learn how to make and then ship apps, we should also mindlessly repeat how to make the finishing touches! Right?
Anyway, here is the finished code.
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 don’t forget to visit the 100 Days Of Swift initiative page.
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!