Hacking with Swift – Learning Project 14

Here we are, live with the “Trying to catch up on Swift” show!

This is Day 59 of my Swift learning path started on February 1st. Yes, I missed a day as last Friday was an awful day at work, really unbelievable.

Anyways… today we’ll be making our second game using SpriteKit.

Setting up

  1. Create a new Xcode project within the iOS category and using the Game template. Select iPad as its only target device and deselect Portrait and Upside Down orientations.
  2. Delete the Action.sks file; inside GameScene.sks delete the “Hello, World” label, change the size to be 1024 x 768 and the anchor point to be 0, 0; inside GameScene.swiftdelete all code except for the didMove method and add an empty touchesBegan method.
  3. Download the assets for this project from the Hacking with Swift GitHub repository; drag the @2x.png images into the Assets.xcassets folder; drag the two audio .caf files inside the project, just under Info.plist.

Getting up and running: SKCropNode

The nice thing of learning new stuff is that, usually, on the first run, you don’t understand a single thing. Well, that was my feeling today. I understand that we have already faced our first SpriteKit game, but this speed of explaining is really too much. Sure, I can go and grab the book and proceed at my own speed but… yes, never mind my rants.

  1. Add a gameScore property of type SKLabelNode! and a score property initialised to 0 to the GameScene class. This last property needs a property observer: didSet { gameScore.text = "Score: \(score)". Nothing to explain, right? We are iOS veterans… 😒… yeah…
  2. Inside the didMove method we need to initialise our background as an SKSpriteNode(imageNamed:) object, set its position to a CGPoint(x: 512, y: 384) (that is, in the center of our scene), set its blend mode to .replace (so that its colour replaces whatever is below it), set its zPosition to -1 (that is, behind everything else) and add it as a child to the parent view.
  3. After this create the game score with SKLabelNode(fontNamed: "Chalkduster"), set its text to “Score: 0”, its position to CGPoint(x: 8, y: 8) (that is, in the bottom left corner), its horizontal alignment mode to .left, the font size to 48 and, finally, add the child to the parent view.
  4. As the default behaviour of this Xcode template is to stretch the view so that the edges get cut off we need to replace the .aspectFill of the scene.scaleMode inside GameViewController.swift . This will gently stretch our scene so that it fits the device dimensions no matter what aspect ratio we are working with (this would be a problem with the iPad Pro 11-inch).
  5. Create a Cocoa Touch Class that subclasses SKNode named “WhackSlot” (for the love of Sparta, please pay attention to what you type!). This base class has the only purpose of sitting on a scene at a determined position and holding other nodes as children. Add import SpriteKit at the top of the file.
  6. Add a method to create a hole at its current position:
func configure(at position: CGPoint) {
    self.position = position

    let sprite = SKSpriteNode(imageNamed: "whackHole")
    addChild(sprite)
}
  1. Add a property to the GameScene class to store all the upcoming slots (var slots = [WhackSlot]()).
  2. Create a method that accepts a CGPoint position as its only parameter and that creates a slot, calls its configure(at:) method and then adds it to both the scene and the just created array.
  3. Create the four loops necessary to create the slots at the end of didMove(to:).
  4. Familiarise with the concept of an SKCropNode, a special kind of SKNode subclass that uses an image as a cropping mask. Unfortunately Apple’s Documentation of the SKCropNode class is only rendered in Objective-C. Understood or not, move on…
  5. In WhatSlot.swift add a SKSpriteNode! property. Then, just before the end of the configure(at:) method, initialise an SKCropNode, position it at CGPoint(x: 0, y: 15), give it a zPosition = 1 (that is, in front of everything else) and set its maskNode property to nil. After this initialise a new SKSpriteNode(imageNamed: "penguinGood"), position it to CGPoint(x: 0, y: -90), call it “character” and add it to the crop node! Finally, add the crop node the the parent scene! Running now will show many penguins just below each hole.
  6. Change the maskNode property to be = SKSpriteNode(imageNamed: "whackMask"). Now all the penguins are hidden.

I really do not understand why this is happening. After all, we are using a red graphic to crop nodes… Paul says:

everything with a color is visible, and everything transparent is invisible

But what is transparent here? Every image has a color, they are there and that’s it… I really do not get it, I’m sorry, and Apple’s Documentation doesn’t make it any easier on me. So… reading this again:

This is a special kind of SKNode subclass that uses an image as a cropping mask: anything in the coloured part will be visible, anything in the transparent part will be invisible.

Maybe I have some issues with English language, sure, but now I understand that putting this image on top of the whole will make it sure that if a penguin is in the coloured part it will be visible, otherwise not… but why using the word “transparent”? It is so misleading to me… Also, how does this cropping mask becomes invisible itself, while being red in the beginning?

Fascinatingly irritating, I would say!

Penguin, show thyself: SKActionmoveBy(x:y:duration:)

  1. At the top of the WhackSlot class add two properties of type Bool, both set to false, to indicate the fact that a penguin is visible or not and is hit or not.
  2. Create the show() method inside the WhackSlot class. First, check if the isVisible property is set to true, in which case just exit the method. Second, call charNode.run(SKAction.moveBy(x: 0, y: 80, duration: 0.05)) to run this moving action on the character node so that it moves up by 80 points (being placed down 90 this will look as if it is just popping out of the hole) very fast, 1/20th of a second!. Third: set isVisible to true and isHit to false. Fourth: once every three times set the charNode.texture = SKTexture(imageNamed: "penguinGood") and its .name property to “charFriend”. In the other two instances, set the name of the image to “penguinEvil” and the node name to “charEnemy”.
  3. In GameScene.swift create a proper to manage the triggering of the show method: var popupTime = 0.85.
  4. Inside didMove(to:) call upon the powers of GCD to schedule the execution of the penguins appearance like this: DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in self?.createEnemy() }
  5. Write the createEnemy() method. This will first decrease the popupTime slowly (by multiplying and reassigning to itself by 0.991). Second: it will shuffle the slots array and call show on its first element with a hideTime parameter of popupTime. Third: with an increasingly low probability (determined by a randomness between 0 and 13—why this number?!) extra penguins will be shown at the same time. Fourth: the minimum delay will be set to the half of the popup time while the maximum delay to its double. The real delay will be a random amount between those two maximums. Fifth: now for the genius hit. The method will call itself after the delay!
  6. Update the WhackSlot class to include a hide() method. If isVisible is false just get out because there is no reason to hide something that is not visible! Then run the contrary of the move action ran just before. Finally set isVisible = false.
  7. At the end of the show() method insert an asynchronous call to the hide() method.

Whack to win: SKAction sequences

  1. In the WhackSlot class add the hit() method: this will set the isHit property to true, create a delay action in the form of SKAction.wait(forDuration: 0.25), hide the penguin by moving the node down by 80 points in half a second, then set it to be not visible in this sequence SKAction.run { [weak self] in self.isVisible = false }. It will then run this sequence of actions one after the other.
  2. Finally write the touchesBegan method. First: check that there is a touch and gets its location. Second: capture the nodes found at that location. Third: for every node in those capture node, it will execute something according to whether the player tapped the good penguin or the bad penguin.
  3. At the top of the loop be sure that by tapping a penguin you actually reached its grand-parent node, like this: guard let whachSlot = node.parent?.parent as? WhackSlot else { continue }. Then if that slot is invisible or is hit just continue. Assuming those two properties false, just call the whackSlot.hit(). Then if the good one was hit, decrease the score by 5, else increase it by 1 and decrease the scale of the node to 85%. At the end of each condition call the action .playSoundFileNamed to play the correct sound for each occasion.
  4. Inside the show method of the GameScene.swift file, before the run() call, just restore the original size of the sprites.
  5. Last but not list, create a property called numRounds = 0 to keep track of how many penguins are being called.
  6. Inside the createEnemy() method, just before the popupTime assignment, increase the number of rounds by 1 and, if that amount passes 30, hide all of the slots and create a game over node! Don’t forget to return at the end of this sub-part.

This game is done.

You can find the finished repository 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 don’t forget to visit the 100 Days Of Swift initiative page.

Thanks for reading!

Till the next one!


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 )

Twitter picture

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

Facebook photo

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

Connecting to %s

%d bloggers like this: