100 Days of Swift – Day 86

Reflections on Day 85

Seldom we had a project that took so long to complete.

Sometimes I found the reasoning behind the project to be a bit bizarre and chaotic but hey!, I’m the one who is learning so I guess it is fair to say that things can be not clear to me on the first pass.

Today we have a review and three challenges to complete, of which only the seconds look to me as a hard one (especially because it requires fiddling with extra files, naming and so on…).

As I said I found this game to have a few unpleasant logic flaws in both sound management and in how it ends but I am not here to judge other people’s work.


Project 23 – Review

Here is what we learned about in this huge lesson:

  1. SpriteKit has an SKAction to make a node fade out. This action is called fadeOut(withDuration:).
  2. The removeFirst() method of arrays removes one or more items from the start of an array. If you call it with no parameters it removes just one. This is a tricky way of wording the question because, in this project, we call this method to remove the first k amount of elements from the start of the array so this got me confused at first.
  3. A UIBezierPath can contain dozens or more points on a path. It can potentially hold thousands, although it might be rather slot to draw!
  4. It’s possible to run an SKAction directly on the game scene. Game scenes are actually a subclass of SKNode! This actually strikes me as new… when have we ever run an action directly on a scene???
  5. A physics body with isDynamic set to false will no longer be affected by gravity. Non-dynamic physics bodies take less CPU time to calculate in the physics simulator, and still respond to collisions.
  6. It is possible to make arrays of enum cases. As long as all the cases come from the same enum, this is fine.
  7. SKTexture lets us change the image of an SKSpriteNode without having to delete and recreate it. This makes it helpful for changing how something looks in place – we can just change the property.
  8. The xScale and yScale properties let us adjust the size of a node freely. These two values don’t need to be the same, so we can stretch and squash objects if we want.
  9. We can stop an AVAudioPlayer from playing its sound by calling its stop() method. This lets us control playback as much as we want.
  10. Making an enum conform to CaseIterable will give it an allCases array. This property is automatically generated by the compiler, and features all cases in the order they were listed in your own.
  11. We can adjust the stroke color and line width of an SKShapeNode. This lets us draw lines and borders around shapes.
  12. SpriteKit scenes have a default gravity vector. The default value is Y:-9.8, which matches the gravity on Earth.

Challenges

Challenge 1: try to remove the magic numbers in the createEnemy() method. Instead define them as constant properties of your class, giving them useful names.

I created the following constants:

let minCreationX = 64
let maxCreationX = 960
let creationY = -128
let minAngularVelocity: CGFloat = -3
let maxAngularVelocity: CGFloat = 3
let minFastXVelocity = 8
let maxFastXVelocity = 15
let minSlowXVelocity = 3
let maxSlowXVelocity = 5
let minYVelocity = 24
let maxYVelocity = 32
let physicsVelocityMultiplier = 40

… so that the createEnemy() method becomes this (pasting only the position code):

[...]
// POSITION CODE
// 1. give the enemy a random position off the bottom edge of the screen
let randomPosition = CGPoint(x: Int.random(in: minCreationX...maxCreationX), y: creationY)
enemy.position = randomPosition

// 2. create a random angular velocity, which is how fast something should spin.
let randomAngularVelocity = CGFloat.random(in: minAngularVelocity...maxAngularVelocity)
let randomXVelocity: Int

// 3. create a random X velocity (how far to move horizontally) that takes into account the enemy's position
if randomPosition.x < 256 {
    randomXVelocity = Int.random(in: minFastXVelocity...maxFastXVelocity)
} else if randomPosition.x < 512 {
    randomXVelocity = Int.random(in: minSlowXVelocity...maxSlowXVelocity)
} else if randomPosition.x < 768 {
    randomXVelocity = -Int.random(in: minSlowXVelocity...maxSlowXVelocity)
} else {
    randomXVelocity = -Int.random(in: minFastXVelocity...maxFastXVelocity)
}

// 4. create a random Y velocity just to make things fly at differend speeds
let randomYVelocity = Int.random(in: minYVelocity...maxYVelocity)

// 5. give all enemies a circular physics body where the `collisionBitMask` is set to 0 so they don't collide
enemy.physicsBody = SKPhysicsBody(circleOfRadius: 64)
enemy.physicsBody?.velocity = CGVector(dx: randomXVelocity * physicsVelocityMultiplier, dy: randomYVelocity * physicsVelocityMultiplier)
enemy.physicsBody?.angularVelocity = randomAngularVelocity
enemy.physicsBody?.collisionBitMask = 0 // bounce against nothing in the game

addChild(enemy)
activeEnemies.append(enemy)

Done!

Challenge 2: create a new, fast-moving type of enemy that awards the player bonus point if they hit it.

This was a nice one.

As the biggest enemy of us all is, obviously, the TV!, I added a “tv” image to the assets catalogue, an “explode.sks” particle and an “explosion.wav” file to our project. I took them from other projects because I am awfully low on time today and I have to try and do my best in as little time as possible.

First: in createEnemy, I added an extra possibility to the randomness of the enemyType so that now it is a random number between 0 and 7.

In the creation I added an extra else if statement with enemyType == 2 as condition, loading the “tv” image as the sprite node, playing the launch sound and giving it the “bonusEnemy” name.

Second: down below, in the position code, I changed the random Y velocity to be a bit faster than before, like this:

let randomYVelocity: Int
if enemy.name == "bonusEnemy" {
    randomYVelocity = Int.random(in: 33...40)
} else {
    randomYVelocity = Int.random(in: 24...32)
}

I had tried higher speeds but it would have become almost impossible to hit.

Third: in the touchesMoved method I added an extra check to the for case let bla-blaness so that if the node’s name is “bonusEnemy” I would attach to it the “explode” emitter , changed its name to “”, deactivated its dynamics and ran the scaling-fading out sequence.

I then increased the score by 5 and performed a scale-up and down sequence on the label to let the player know that they hit something important. Finally I removed the node from the active enemies array and played the new “explosion.wav” file.

Fourth and last but most important: add an || node.name == "bonusEnemy" in the loop of the update method. This is important because otherwise after the first TV nothing else would ever spawn.

That’s it!

Challenge 3: add a “Game over” sprite node to the game scene when the player loses all their lives.

Add this code to the end of the endGame method:

let gameOver = SKSpriteNode(imageNamed: "gameOver")
gameOver.position = CGPoint(x: 512, y: 384)
gameOver.zPosition = 1
gameOver.alpha = 0
gameOver.run(SKAction.fadeIn(withDuration: 1.5))
        
addChild(gameOver)

That’s it, challenges completed!

Here is the code for these challenges (2nd and 3rd one are in the “Challenge23-2” branch)


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: