Create Space Invaders with Swift and Sprite Kit: Implementing Gameplay

Final product image
What You'll Be Creating

In the previous part of this series, we implemented the stubs for the game's main classes. In this tutorial, we will get the invaders moving, bullets firing for both the invaders and player, and implement collision detection. Let's get started.

1. Moving the Invaders

We will use the scene's update method to move the invaders. Whenever you want to move something manually, the update method is generally where you'd want to do this.

Before we do this though, we need to update the rightBounds property. It was initially set to 0, because we need to use the scene's size to set the variable. We were unable to do that outside any of the class's methods so we will update this property in the didMoveToView(_:) method.

Next, implement the moveInvaders method below the setupPlayer method you created in the previous tutorial.

We declare a variable, changeDirection, to keep track when the invaders need to change direction, moving left or moving right. We then use the enumerateChildNodesWithName(usingBlock:) method, which searches a node’s children and calls the closure once for each matching node it finds with the matching name "invader". The closure accepts two parameters, node is the node that matches the name and stop is a pointer to a boolean variable to terminate the enumeration. We will not be using stop here, but it is good to know what it is used for.

We cast node to an SKSpriteNode instance which invader is a subclass of, get half its width invaderHalfWidth, and update its position. We then check if its position is within  the bounds, leftBounds and rightBounds, and, if not, we set changeDirection to true.

If changeDirection is true, we  negate invaderSpeed, which will change the direction the invader moves in. We then enumerate through the invaders and update their y position. Lastly, we set changeDirection back to false.

The moveInvaders method is called in the update(_:) method.

If you test the application now, you should see the invaders move left, right, and then down if they reach the bounds that we have set on either side.

2. Firing Invader Bullets

Step 1: fireBullet

Every so often we want one of the invaders to fire a bullet. As it stands now, the invaders in the bottom row are set up to fire a bullet, because they are in the invadersWhoCanFire array.

When an Invader gets hit by a player bullet, then the invader one row up and in the same column will be added to the invadersWhoCanFire array, while the invader that got hit will be removed. This way only the bottommost invader of every column can fire bullets.

Add the fireBullet method to the InvaderBullet class in InvaderBullet.swift.

In the fireBullet method, we instantiate an InvaderBullet instance, passing in "laser" for imageName, and because we don't want a sound to play we pass in nil for bulletSound. We set its position to be the same as the invader's, with a slight offset on the y position, and add it to the scene.

We create two SKAction instances, moveBulletAction and removeBulletAction. The moveBulletAction action moves the bullet to a certain point over a certain duration while the removeBulletAction action removes it from the scene. By invoking the sequence(_:) method on these actions, they will run sequentially. This is why I mentioned the waitForDuration method when playing a sound in the previous part of this series. If you create an SKAction object by invoking playSoundFileNamed(_:waitForCompletion:) and set waitForCompletion to true, then the duration of that action would be for as long as the sound plays, otherwise it would skip immediately to the next action in the sequence.

Step 2: invokeInvaderFire

Add the invokeInvaderFire method below the other methods you've created in GameScence.swift.

The runBlock(_:) method of the SKAction class creates an SKAction instance and immediately invokes the closure passed to the runBlock(_:) method. In the closure, we invoke the fireInvaderBullet method. Because we invoke this method in a closure, we have to use self to call it.

We then create an SKAction instance named waitToFireInvaderBullet by invoking waitForDuration(_:), passing in the number of seconds to wait before moving on. Next, we create an SKAction instance, invaderFire, by invoking the sequence(_:) method. This method accepts a collection of action that are invoked by the invaderFire action. We want this sequence to repeat forever so we create an action named repeatForeverAction, pass in the SKAction objects to repeat, and invoke runAction, passing in the repeatForeverAction action. The runAction method is declared in the SKNode class.

Step 3: fireInvaderBullet

Add the fireInvaderBullet method below the invokeInvaderFire method you entered in the previous step.

In this method, we call what seems to be a method named randomElement that would return a random element out of the invadersWhoCanFire array, and then call its fireBullet method. There is, unfortunately, no built in randomElement method on the Array structure. However, we can create an Array extension to provide this functionality.

Step 4: Implement randomElement

Go to File > New > File... and choose Swift File. We are doing something different than before so just make sure you are choosing Swift File and not Cocoa Touch Class. Press Next and name the file Utilities. Add the following to Utilities.swift.

We extend the Array structure to have a method named randomElement. The arc4random_uniform function returns a number between 0 and whatever you pass in. Because Swift doesn't implicitly convert numeric types, we must do the conversion ourselves. Finally, we return the element of the array at index index.

This example illustrates how easy it is to add functionality to the structure and classes. You can read more about creating extensions in the The Swift Programming Language.

Step 5: Firing the Bullet

With all this out of the way, we can now fire the bullets. Add the following to the didMoveToView(_:) method.

If you test the application now, every second or so you should see one of the invaders from the bottom row fire a bullet.

3. Firing Player Bullets

Step 1: fireBullet(scene:)

Add the following property to the Player class in Player.swift.

We want to limit how often the player can fire a bullet. The canFire property will be used to regulate that. Next, add the following to the fireBullet(scene:) method in the Player class.

We first make sure the player is able to fire by checking if canFire is set to true. If it isn't, we immediately return from the method.

If the player can fire, we set canFire to false so they cannot immediately fire another bullet. We then instantiate a PlayerBullet instance, passing in "laser" for the imageNamed parameter. Because we want a sound to play when the player fires a bullet, we pass in "laser.mp3" for the bulletSound parameter.

We then set the bullet's position and add it to the screen. The next few lines are the same as the Invader's fireBullet method in that we move the bullet and remove it from the scene. Next, we create an SKAction instance, waitToEnableFire, by invoking the waitForDuration(_:) class method. Lastly, we invoke runAction, passing in waitToEnableFire, and on completion set canFire back to true.

Step 2: Firing the Player Bullet

Whenever the user touches the screen, we want to fire a bullet. This is as simple as calling fireBullet on the player object in the touchesBegan(_:withEvent:) method of the GameScene class.

If you test the application now, you should be able to fire a bullet when you tap the screen. Also, you should hear the laser sound every time a bullet is fired.

4. Collision Categories

To detect when nodes are colliding or making contact with each other, we will use Sprite Kit's built-in physics engine. However, the default behavior of the physics engine is that everything collides with everything when they have a physics body added to them. We need a way to separate what we want interacting with each other and we can do this by creating categories to which specific physic bodies belong.

You define these categories using a bit mask that uses a 32-bit integer with 32 individual flags that can be either on or off. This also means you can only have a maximum of 32 categories for your game. This should not present a problem for most games, but it is something to keep in mind.

Add the following structure definition to the GameScene class, below the invaderNum declaration in GameScene.swift.

We use a structure, CollsionCategories, to create categories for the Invader, Player, InvaderBullet, and PlayerBullet classes. We are using bit shifting to turn the bits on.

5. Player and InvaderBullet Collision

Step 1: Setting Up InvaderBullet for Collision

Add the following code block to the init(imageName:bulletSound:) method in InvaderBullet.swift.

There are several ways to create a physics body. In this example, we use the init(texture:size:) initializer, which will make the collision detection use the shape of the texture we pass in. There are several other initializers available, which you can see in the SKPhysicsBody class reference.

We could easily have used the init(rectangleOfSize:) initializer, because the bullets are rectangular in shape. In a game this small it does not matter. However, be aware that using the init(texture:size:) method can be computationally expensive since it has to calculate the exact shape of the texture. If you have objects that are rectangular or circular in shape, then you should use those types of initializers if the game's performance is becoming a problem.

For collision detection to work, at least one of the bodies you are testing has to be marked as dynamic. By setting the usesPreciseCollisionDetection property to true, Sprite Kit uses a more precise collision detection. Set this property to true on small, fast moving bodies like our bullets.

Each body will belong to a category and you define this by setting its categoryBitMask. Since this is the InvaderBullet class, we set it to CollisionCategories.InvaderBullet.

To tell when this body has made contact with another body that you are interested in, you set the contactBitMask. Here we want to know when the InvaderBullet has made contact with the player so we use CollisionCategories.Player. Because a collision shouldn't trigger any physics forces, we set collisionBitMask to 0x0.

Step 2: Setting Up Player for Collsion

Add the following to the init method in Player.swift.

Much of this should be familiar from the previous step so I will not rehash it here. There are two differences to notice though. One is that usesPreciseCollsionDetection has been set to false, which is the default. It is important to realize that only one of the contacting bodies needs this property set to true (which was the bullet). The other difference is that we also want to know when the player contacts an invader. You can have more than one contactBitMask category by separating them with the bitwise or (|) operator. Other than that, you should notice it is just basically opposite from the InvaderBullet.

6. Invader and PlayerBullet Collision

Step 1: Setting Up Invader for Collision

Add the following to the init method in Invader.swift.

This should all make sense if you've been following along. We set up the physicsBody, categoryBitMask, and contactBitMask.

Step 2: Setting Up PlayerBullet for Collision

Add the following to the init(imageName:bulletSound:) in PlayerBullet.swift. Again, the implementation should be familiar by now.

7. Setting Up Physics for GameScene

Step 1: Configuring Physics World

We have to set up the GameScene class to implement the SKPhysicsContactDelegate so we can respond when two bodies collide. Add the following to make the GameScene class conform to the SKPhysicsContactDelegate protocol.

Next, we have to set up some properties on the scene's physicsWorld. Enter the following at the top of the didMoveToView(_:) method in GameScene.swift.

We set the gravity property of physicsWorld to 0 so that none of the physics bodies in the scene are affected by gravity. You can also do this on a per body basis instead of setting the whole world to have no gravity by setting the affectedByGravity property. We also set the contactDelegate property of the physics world to self, the GameScene instance.

Step 2: Implementing SKPhysicsContactDelegate Protocol

To conform the GameScene class to SKPhysicsContactDelegate protocol, we need to implement the didBeginContact(_:) method. This method is called when two bodies make contact. The implementation of the didBeginContact(_:) method looks like this.

We first declare two variables firstBody and secondBody. When two objects make contact, we don't know which body is which. This means that we first need to make some checks to make sure firstBody is the one with the lower categoryBitMask.

Next, we go through each possible scenario using the bitwise & operator and the collision categories we defined earlier to check what is making contact. We log the result to the console to make sure everything is working as it should. If you test the application, all contacts should be working correctly.

Conclusion

This was a rather long tutorial, but we now have the invaders moving, bullets being fired from both the player and invaders, and contact detection working by using contact bit masks. We are on the home stretch to the final game. In the next and final part of this series, we will have a completed game.

Tags:

Comments

Related Articles