meow.

RainCat: Lesson 4

How to make a simple SpriteKit game in Swift 3

Marc J Vandehey

--

Hello again! If you missed out on the previous lesson you can see it here. If you don’t have the code from the lesson 3, it is currently available here on GitHub.

In the last lesson we added in the bulk of our assets and finally positioned them correctly using the zPosition property. We added the cat as well as the functionality to respawn the cat when it falls offscreen to certain peril. Next we spawned the food at a random spot on screen, but with margins on either side of the screen, so it won’t be too close to the edge of the screen. We added collision for both of these sprites, and the cat is able to collect the food but only when pushed there with the umbrella. That is, until today!

In this lesson we will focus on:

  • Moving the cat toward the food
  • Animating the cat
  • Damaging the cat when it comes into contact with the rain
  • Adding sound effects
  • Adding music

Get your assets!

The assets for today are available here on GitHub. Drag the images into the Assets.xcassets again, just as we did in the previous three lessons. We will add in the sound effects and music later on.

Let’s Move!

Getting our cat to move won’t be too difficult. We won’t interact with the cat by touching the screen, which is how we currently move our umbrella around. Instead, the cat will move based on the current location of the food. Currently our food sprite is added to the scene and forgotten about, but we need to know the exact position of the food every time an update is called in order to point the cat in the right direction to move.

Moving back to our GameScene.swift,let’s add a variable to the top of the file, beneath our local cat variable:

private var food : FoodSprite!

Then we can update the spawnFood function to set this variable every time the food is spawned.

In the spawnFood function, replace:

let food = FoodSprite.newInstance()

with:

food = FoodSprite.newInstance()

This will change the scope of the food variable from the spawnFood function to that of the GameScene file. Now we need to edit the CatSprite file in order to know which direction it needs to travel toward the food. To get to the food, we need to know at what rate of speed we can move toward it. At the top of the CatSprite.swift file we can add the following line of code above the newInstance function.

private let movementSpeed : CGFloat = 100

This line is our movement speed, which is a very simple solution to a very complex problem. We are just using a simple linear equation here and ignoring any complexities that friction or acceleration can bring into the mix.

Now we need to do something with our update(deltaTime:) method. Since we will know where the food is, we need to move toward the location. Replace the update function in CatSprite.swift with the following code:

CatSprite.swift

We updated the signature of the function! We needed to give the cat the location of the food, so instead of only sending in the delta time, we added in the food location as well. Since many things can affect the location of the food, we need to update it constantly so we are always moving in the correct direction.

Now we will move toward the food dish location at a constant rate of speed. First we check which direction we need to move, then we move that direction on the x-axis. We should also update the xScale, because we want to face the correct direction while moving. Unless, of course, we want our cat to do the moon walk! Finally, we need to tell the cat to update in our game scene.

Moving to the GameScene.swift file, navigate to our update(_ currentTime:) function, and beneath the update umbrella call, enter the following line:

cat.update(deltaTime: dt, foodLocation: food.position)

Run the app, and we should have success! Mostly at least. Currently the cat does indeed move toward the food, but it can get into some interesting situations.

Just a normal cat doing normal cat things

Next we can add the walking animation! After that, we’ll circle back to fix the cat’s rotation after it gets hit. You may have noticed that there is an unused asset called “cat_two”. We need to pull this texture in and swap them out while moving to make it appear as if the cat is walking. To do this, we will add in our first SKAction!

Walking with style!

At the top of CatSprite we will add in a string constant so that we can add a walking action associated with this key. This way, we can stop the walking action without removing all of the actions that we may have on the cat later on. Add the following line above the movementSpeed variable:

private let walkingActionKey = "action_walking"

The string itself is not really important, but it is unique to this walking animation. I also like adding something meaningful to the key for debugging purposes down the line. For example, if I see the key, I will know that it is a SKAction and that specifically it is the walking action.

Beneath the walkingActionKey, we will add in the frames. Since we will only be using two frames, we can do this at the top of the file without it looking too messy:

private let walkFrames = [
SKTexture(imageNamed: "cat_one"),
SKTexture(imageNamed: "cat_two")
]

This is just an array of the two textures that we will switch between while walking. To finish this off, we will update our update(deltaTime: foodLocation:) function to the following code:

CatSprite.swift

With this update, we added a check to see if our cat sprite is already running the walking animation sequence. If it is not, we will add an action to the sprite. This is a nested SKAction. First we create an action that will repeat forever. Then in that action we create the animation sequence for walking. The SKAction.animate(with: …) takes in the array of animation frames, along with the time per frame. The next variable in the function asks if one of the textures is a different size, and whether it should resize the SKSpriteNode when it gets to that frame. Restore asks if the sprite should return to its initial state after the action is completed. We set both of these to false, so we won’t get any unintended side effects. After we set up the walking action, we tell the sprite to start running it with the run() function.

Run the app again, and we will see our cat intently walking toward the food!

Yeah, on the catwalk, on the catwalk, yeah
I do my little turn on the catwalk

If the cat gets hit, it will rotate but still move toward the food. We need to show a damaged state for the cat so that we know we did something bad. Also, we need to correct the cat’s rotation while moving, so that it is not walking on its side or upside-down.

Let’s go over the plan. We want to show the user that the cat has been hit, other than just updating the score later on. Some games will make the unit invulnerable while flashing. We could also do a damage animation if we get the textures for this. For this game I want to keep things simple, so I will add in some functionality for flailing. This cat, when hit by rain, will become stunned and just sort of roll to its back in disbelief. The cat is SHOCKED that you would let this happen. To accomplish this we will setup a few variables. We need to know how long the cat will be stunned and how long it has been stunned. Add the following lines at the top of the file, below the movementSpeed variable:

private var timeSinceLastHit : TimeInterval = 2
private let maxFlailTime : TimeInterval = 2

The first variable, timeSinceLastHit is the variable holding how long it has been since the cat was hit last. We set it to two because of the next variable, maxFlailTime. This is a constant saying that the cat will only be stunned for two seconds. Both are set to two so that the cat does not start out stunned when spawned. You can tamper with these variables later to get the perfect stun-time.

Now we need to add in a function to let the cat know it’s been hit, and that it needs to react — by stopping moving. Add the following function below our update(deltaTime: foodLocation:) function:

CatSprite.swift

This just updates the timeSinceLastHit to zero, and removes the walking animation that we set up earlier. Now we need to do an overhaul on update(deltaTime: foodLocation:), so the cat won’t move while it is stunned. Update the function to the following code:

CatSprite.swift

Now our timeSinceLastHit will constantly be updated, and if the cat hasn’t been hit in the past two seconds, it will walk toward food. If our walking animation isn’t set, then we set it correctly. This is a frame-based animation that just swaps out the texture every 0.1 seconds to make it appear as though the cat is walking. It looks exactly like how real cats walk, right?

We need to move over to GameScene.swift to tell the cat that it has been hit. In handleCatCollision(contact:), we will call the hitByRain function. In the switch statement, look for the RainDropCategory and replace:

print(“rain hit the cat”)

with:

cat.hitByRain()

If we run the app now, the cat will become stunned for two seconds after rain touches it!

It works, but the cat seems to get to a rotated state and looks funny. Also, it looks like the rain really hurts — maybe we need to do something about that.

For the raindrop problem, we can make a slight tweak to its physics body. Under spawnRaindrop and beneath where we initialize the physicsBody, we can add the following line:

rainDrop.physicsBody?.density = 0.5

This will halve the density of the raindrop from its normal value of 1.0. This will not launch the cat quite as much as it has.

Moving to CatSprite, we can correct the rotation of the cat with a SKAction. Add the following into the update(deltaTime: foodLocation:) function:

CatSprite.swift > update(deltaTime: foodLocation:)

This block of code will check to see if we are rotated, even in the slightest. Then we will also check currently running SKActions to see if we are already animating the cat to its standing position. If we are rotated, and not animating we run an action to rotate us back to zero radians. Note that we are hard coding the key here. This is because we currently don’t need to use this key outside this spot. In the future, if we need to check the animation of our rotation in another function or class, we would make a constant at the top of the file, exactly like the walkingActionKey.

Run the app, and you will see the magic happening. Cat gets hit, cat probably rotates, cat fixes itself, cat is then happy to eat more. There is still a problem though. Because we are using a circle for the cat’s physicsBody, after we correct ourselves for the first time, you may notice that the cat will be jittery. It is constantly rotating and correcting itself. To get around this, we need to reset the angularVelocity. Basically the cat is rotating from being hit, and we never correct the velocity that was added. The cat also does not update its velocity after being hit. If the cat is hit and tries to move in the opposite direction you may notice that it is going slower than normal. We can fix this by updating our movement to the following code:

CatSprite.swift > update(deltaTime: foodLocation:)

Run the app yet again, and much of the herky jerky action will be corrected.

And now we add sound!

Before we start the programming, we should look into finding sound effects. Generally, when looking for sound effects, I just google a phrase like cat meow royalty free. First hit is usually soundbible.com, and they generally have a good selection of royalty free-sound effects. Make sure to read up on the licenses. If you never plan to release the app, then pay no concern to licensing since this will be for personal use. However if you wish to sell this in the app store, distribute it, etc,, make sure to use Attribution 3.0 Creative Commons or something similar. There are a lot of licenses out there, so make sure you know what the license for a sound/image is before using someone else’s work.

All of these RainCat sound effects are Creative Commons and are free to use. For the next step, move the SFX folder that we downloaded earlier into RainCat folder.

Add in your sound effects to your file system

After you add in the files to your project, add them to your project in Xcode. Create a group under Support called SFX. Right click on group and click Add Files to “RainCat”…

Adding in your SFX

Find your SFX folder, select all of your sound effect files, and click the Add button. Now you have sound effects to play with. Moving to CatSprite.swift, we can add in an array of the sound effect file names so we can play them on raindrop hit. Add the following array below the walkFrames variable up at the top of the file:

private let meowSFX = [
"cat_meow_1.mp3",
"cat_meow_2.mp3",
"cat_meow_3.mp3",
"cat_meow_4.mp3",
"cat_meow_5.wav",
"cat_meow_6.wav"
]

We can have the cat make sounds by adding two lines to the hitByRain function:

let selectedSFX = Int(arc4random_uniform(UInt32(meowSFX.count)))run(SKAction.playSoundFileNamed(meowSFX[selectedSFX], waitForCompletion: true))

The above code selects a random number with the minimum being zero and max being the size of the meowSFX array. Then we pick the sound effect name from the string array, and play the sound file. We will get to the waitForCompletion variable in a little bit. Also we use SKAction.playSoundFileNamed for our short and sweet sound effects. If you want to get some background music, you will need to look into AVFoundation.

And we have SOUND! We have too much sound. We have sounds playing over other sounds. Right now we play one of the sound effects every time the cat hits rain. This gets annoying fast. We need to add more logic around when to play the sound and also not to play two clips at the same time.

Add these variables to the top of the CatSprite.swift file below the maxFlailTime variable:

private var currentRainHits = 4
private let maxRainHits = 4

The first variable currentRainHits is a counter for how many hits have hit the cat, and maxRainHits is the number of hits it will take to meow.

Now we will update the hitByRain function. We need to apply the rules for currentRainHits and maxRainHits. Replace the hitByRain function with the following:

CatSprite.swift

Now if the currentRainHits is less than the maximum, we just increment the currentRainHits, and return before we play the sound effect. Then we check to see if we are currently playing the sound effect by the key we provided: action_sound_effect. If we are not running the action, we select a random sound effect then play it. We set waitForCompletion to true because the action will not complete until the sound effect is completed. If we set it to false, it counts the sound effect action as completed as soon as it begins to play.

Add the music!

Before we create a way to play music in our app, we need something to play. Similarly to searching for sound effects, you can search Google for Royalty Free Music and you can generally find something. Additionally, you can go to SoundCloud and talk to the artists. See if you can reach an agreement, either by using the music for free with attribution or by paying for a license to use it in your game. For this app I happened across bensound.com and they had some, music that I could use, using the Creative Commons license. To use it you must follow their licensing agreement. Pretty straight forward: you must either credit them or pay for a license.

Download all four tracks 1 2 3 4, or move them over from the Music folder that we downloaded earlier. We will use them and cycle between each track to keep things fresh. Another thing to consider, these tracks don’t loop correctly, so you will know when it starts and ends. Good background music can loop or morph into another track really well.

Once you download the tracks, create a folder called Music under the RainCat folder, the same way you created the SFX folder earlier. Then move the tracks to the folder.

Adding in some music tracks

Then create a group in the Support group in our project structure called Music. Add our tracks to our project by right clicking the Music group and click Add Files to “RainCat”.

Adding the tracks to the project

Next we will create a new file called SoundManager, as you may have seen in the above picture. Finally we can implement the SoundManager:

SoundManger.swift

Going through the new SoundManager class, we are making a singleton class that handles playback of the large track files and continuously plays them in order. For longer format audio files, we need to use AVFoundation. It is built for this, and SKAction cannot load the file in quickly enough to play it the same way it could load in a small SFX file. Since this library has been around forever, the delegate still depends on NSObjects. We need to be the AudioPlayer delegate to detect when the audio player completes playback. We hold class variables for the current audioPlayer for now; we will need it later to mute playback.

Now we have the current track location, so we know the next track we need to play, followed by an array of the names of the music tracks we have in our project. We attribute it to BenSound.com to honor our licensing agreement.

We need to implement the default init function. Here we choose a random track to start with, so we don’t always hear the same track first. From then on, we wait for our program to tell us to start playing. In startPlaying, we check to see if the current audio player is playing. If it is not we attempt to start playing the selected track. We attempt to start the audio player, which can fail, so we need to surround it in a try/catch block. After that we prepare playback, play the audio clip, and then set index for the next track. This line is pretty important:

trackPosition = (trackPosition + 1) % SoundManager.tracks.count

This sets the next position of the track by incrementing it and then performing a modulo on it to keep it within the bounds of the tracks’ array. Finally in audioPlayerDidFinishPlaying(_ player:successfully flag:), we implement the delegate method that lets us know when the track finishes. Currently we don’t care if it succeeded or not — we just play the next track when this is called.

Just press play

Now that we are done explaining the SoundManager, we just need to tell it to start and we have music playing on a loop forever. Quickly run over to GameViewController.swift and place the following line of code below where we set up the scene for the first time:

SoundManager.sharedInstance.startPlaying()

We do this in GameViewController because we want the music to be independent of the scene. If we run the app at this point, and everything is added to the project correctly, we will now have background music for our game!

In this lesson we touched on two major topics; sprite animation and sound. We used a frame-based animation to animate our sprite, used SKActions to animate, and methods to correct our cat after it is hit by rain. We added sound effects using SKActions and assigned them to play when the cat gets hit by rain. Finally, we added initial background music for our game.

For those who made it this far in the lessons, congratulations! Our game is nearing completion! If you missed a step or you got confused along the way, please check out the final code for this lesson on GitHub.

How did you do? Did your code look almost exactly like mine? What changed? Did you update the code for the better? Let me know in the comments below.

Lesson 5 coming up next!

Find us on Facebook and Twitter or get in touch at thirteen23.com.

--

--