Creating a Tutorial for a SpriteKit Game
A step-by-step approach
Currently, my wife and I are working on a small game in SpriteKit. The player controls a fish that needs to dodge obstacles on its way up from the deep ocean. This fish can only swim within four lanes, as you can see in this image:
The player can switch lanes by swiping left or right. You would guess that this concept is not hard to grasp, and so did we. But when test users played, some of them didn’t understand what they needed to do. Thus we decided to transform the first level into a tutorial. Since I couldn’t find a tutorial on how to add a tutorial, I’ll describe the approach we took.
Each step of the tutorial level consists of two parts. First, the fish needs to stop at a specific point while the user gets a hint of what they have to do. Second, after the user completes the swipe gesture, the fish needs to perform the required movement.
1. Setup of Tutorial Nodes
We marked points where the fish should stop by adding nodes to the scene. The name of each of these nodes starts with
tutorial_node_, followed by the number of the step in the tutorial. These nodes are prepared in the method
setupNodes, which is called when the scene is moved to a view.
- After the nodes are added to the scene editor, we need to access them in code. We can do this by getting the surrounding
worldnode from the scene and looping over its children.
- Now that we have access to the nodes, we need to prepare them. We make them clear, so they can’t be seen in the game, and we create a
SKPhysicsBodyfor each of them so they can be hit by the fish. Each body has the same size and position as the node itself and gets a
categoryBitMaskvalue to mark them as a node involved in the tutorial. Finally, by setting the
false, the nodes are not affected by gravity and will stay where they are.
2. Handling Contact With a Tutorial Node
To detect contact between the player and a tutorial node, we need to implement the
didBegin method, which is required by the
- The contact object, which is passed as a parameter, contains both elements involved in the contact as
bodyB. By combining both in
categoryBitMask, we can check which nodes did collide.
- In the case of the fish and a tutorial block, we need to show the next part of the tutorial.
- First we stop the fish. It will only swim on after the user completes this part of the tutorial.
- We extract the tutorial block from the contact by using a little helper function called node
(withCategory:). It’s an extension of
SKPhysicsContactthat checks whether bodyA or bodyB has the tutorial
categoryBitMaskand returns the correct body. Now we can check the name of the tutorial block. Since we enumerated them in the scene editor, we know at which step of the tutorial the player currently is. In this example, we’ll just explore the first step.
- Each time the fish starts contact with a tutorial node, we determine the next movement the fish has to perform. In the first step, the fish needs to swim to the left-most lane. Also we show an animation to visualize which action the user has to do.
You can see that we do this by using the two functions
showSwipeNeededAnimation(for:). Let’s look at how they work.
The lanes are represented by the following enumeration:
A movement consists of a direction in which the fish should move and a number of line switches the fish has to do in the given direction.
To construct an instance of Movement, we pass the current
SwimmingColumn of the fish and the destination to which the fish should move to the method
calculateNeededMovement(current:destination:). It switches over both and returns the corresponding Movement object, e.g. if the fish is currently in the left-most lane and needs to move to the middle-left lane, it needs to move one step to the right. This movement is stored in the variable
calculatedMovement to be executed once the user swipes.
Note: An alternative approach would be to postpone calculating the next movement of the player to the point where the user already triggers the resume. This would remove the need to separate calculating and executing the next movement, which may seem like a better way. However, there is still state that needs to be stored, mainly which point of the tutorial was triggered. Also, separating these steps provides some benefits. When the tutorial point is hit, SpriteKit has nothing more to do than show the Animation and thus has enough resources to calculate the next step. This may seem irrelevant in such a small game, but imagine that you have multiple enemies and the player needs to move in the best possible way around them. There may even be an AI involved to find the shortest path to the next spot. This can be perfectly done while the game waits for the player to make the next move. If this calculation happens after the user performs the needed action to resume the game, there may be performance issues.
As you can see in the
didBegin(contact:) method, after the next movement is determined, we want to show an animation to the user to explain what they need to do. In other cases, you may also want to show a text giving more information.
Here is how we added the animation step by step:
- Since we want to remove the animated node later when the user swipes, we store it in a variable.
- When setting up the animation, we first make sure that we have received a valid movement. Otherwise, we will just not show any animation. When the user needs to swipe to the left, we want our indicator to start at the right side of the display. Otherwise, it should start at the left side. Thus we create its position with an x offset of -100 or 100, depending on the direction. The position is stored in a variable to reset the node in the animation to its starting point.
- Now we create a filled white circle, which will be the animated node to indicate that the user needs to swipe in a specific direction.
- Finally we can set up the animation. This animation consists of four different animations performed in a sequence that will be repeated forever. First, we move the indicator in the direction the user needs to swipe. Once finished, the node will fade out, move back to its original position, and fade back in.
3. Handling a Swipe by the User
We can detect the swipe by using an
UIGestureRecognizer. We first remove the previously created and stored indicator node to stop the animation. Next, it uses a method called
executeMovement which actually performs the movement we previously determined. The last step is to restart the fish.
executeMovement only moves the fish in the given direction. This is repeated
numberOfSteps times. In our game, moving left and right involves an animation to switch lines, changing the sprite of the fish and other things that are left out.
That’s how we structured our tutorial level. In this image you can see a summary of all the steps we’ve gone through to show the user what he has to do:
I hope you can use this example if you want to add a tutorial level to your game! If you have questions or want to suggest a better approach, please leave a comment.