Category: Blog, iOS, Development

Maze Game with SpriteKit

Maze Game with SpriteKit

Do you remember days when maze games like Labyrinth 2 from Illusion Labs were one of the most popular titles on the App Store? In fact, it was a long time ago, when iPhone 3Gs and 4 had their glory time. I still think it was a really nice idea, so why not to create own Maze game, especially since iOS SDK has such a great framework like SpriteKit?

Labyrinth 2 screenshot

First of all, start Xcode (7.0+ is recommended, I used 7.2 in case of this project), create a new project (iOS > Application > Game), and setup like on the screenshot below. The most important is to choose Swift as a language and SpriteKit as used technology. Also we don’t need unit and UI tests right now, so you can uncheck these two checkboxes.

Zrzut ekranu 2015-12-10 o 09.44.59

Let’s start from analysis sample project created by Apple. Run the app, and then you should see a chalkboarded ‘Hello, World!’ label, nodes counter, and current framerate. That’s the best moment to find out what node means. In fact it’s SKSpriteNode class object that draws a textured image, a colored square, or a textured image blended with a color. Right now there’re two labels – chalkboarded one, and label with counters.

Tap your iPhone screen to add spaceships. Notice that nodes counter increases, and after a while your framerate should be sligthly lower.

It’s time to make some code cleanup. We don’t need spaceships anymore, so go to Assets.xcassets and remove Spaceship. Then open GameScene.swift and leave just what’s necessary:

If you have no previous experience with SpriteKit, you can consider didMoveToView(_:) as viewDidLoad(). Function update(_:) is executed before each frame is rendered. That means you can’t be sure it happens i.e. every single 1/60 second. Maximum framerate is around 60 fps, but there can be some drops. Good practice is not to use there constant time intervals, because of that.

As we’re going to use a gyroscope to control the ball, we should import CoreMotion framework. It can be done in a custom GyroManager class, but I added it at the top of GameScene.swift. Add some code to this source file to initialize CMMotionManager.

In theory it’s pretty safe to unwrap this using exclamation mark, because manager was initialized just line before unwrapping, but it’s always safer to use if let structure (in this case with where instead of nested condition).

When we use CoreMotion for detecting device’s position, it’s recommended to support only one (i.e. Portrait) device orientation, because we don’t want the game to be rotated during device’s motion. To do that find your target’s settings and uncheck other available options in Device Orientation.

Zrzut ekranu 2015-12-16 o 14.11.36

Right now we’ve got a motion manager instance, but there is still a long way to go: placing ball, detecting motions, walls, collisions, etc. Start from the beginning: the ball.

We need some assets: ball, black hole in which you would loose a ball, and place being your target. You can find it on your own or use ones from this project repository on GitHub. Add these and we can start.

Zrzut ekranu 2015-12-10 o 10.38.56

Go back to GameScene.swift and add these in didMoveToView(_:)

First create SKSpriteNode, then position it to frame’s center, and add physics body to the ball – there are multiple initializers, but ball is round, so circle with radius equal to half of it’s size would be perfect. Soon we will use it to add more physics interaction like inertia.

You can run your app and then find the ball in the view’s center. But this level is pretty boring. Go to GameScene.sks (‘sks’ is an acronym for ‘SpriteKit Scene’), set the camera object resolution to 1080 × 1620, which would let your level be compatible with older devices thanks to proper proportions. Also change scene.ScaleMode in GameViewController.swift to .AspectFit.

Bring back SpriteKit Scene and add multiple color sprites as walls, and set frames to create an interesting level. It will be a really simple game, so I preferred setting sprite’s color than texture. Remember to left free space in the middle, because the ball will be there. Also add some black holes as obstacles and one finish hole. Your level could looks like this:

Zrzut ekranu 2015-12-10 o 12.15.27

Now we should implement a device motion support. Doing that in update(_:) makes the animation smooth.

First use if let to safely unwrap manager and its deviceMotion (both optional). To be sure, check if ball is not nil. Then using applyImpulse(_:) we change the forces (with 200.0 multiplier) acting on the ball. There is also function named applyForce(_:), but in this case it would result in an unwanted effect.

applyForce(_:) adds a new force vector, when applyImpulse(_:) changes current. In fact we could use also SKAction.moveTo(_:duration:) with duration 0.0, but we need to interact with physicsBody for inertia. I left these two implementations commented out in the source code on GitHub, so you can check it out.

It’s time to add collisions. Implementation is pretty straightforward, but I believe there is more than one way to improve this. Think of it as your homework. Collisions and contacts are supported by bit masks in SpriteKit. That’s not the simplest thing, especially if you have no previous experiences with them.

Bit masking is useful when you want to store different data within a single data value. Each flag is a bit position that can be set on or off. They should be successive powers of two starting from zero.

In this game we’ve got four kinds of objects: ball, wall, black hole, and finish hole. We don’t need to do anything special when collision between ball and wall happens, so there is no reason to support it in our bit mask.

Then complete didMoveToView(_:) function. In the end it should looks like this:

Now ball can collide with black holes and finish hole. Add collision support in a different way. Open GameScene.sks and select all black holes, then set them in Attributes Inspector Category and Collision Mask to 2, and Contact Mask to 1. Then select finish hole and accordingly set Category and Collision to 4, and Contact Mask to 1.

Last step is to add SKPhysicsContact Delegate Method:

This method fires when contact begins. It checks if bodyA or bodyB has desired categoryBitMask. Right now there is no support for congratulations or punishment when ball is lost. Alert Controller with a kind word seems to be easy (and it’s done on GitHub), but how to bring the ball back to the screen’s center, when it’s lost? Just execute this function when one of the bodies’ categoryBitMask is Collision.BlackHole:

It stops the ball from moving – ball’s velocity vector is set to (0.0, 0.0) – then create and run action that moves ball to the center. Pretty easy, huh?

Right now that’s all. Of course this game is not completed, but it was a nice start. Ball’s lost animations can be nicer (maybe instead of immediately moving the ball to the center, try to reduce its size first?), a shadow under the ball could also be terrific. Try different things and share with us the effect of your work.

About the author

Piotr Sochalewski

Piotr Sochalewski

Droids On Roids iOS Developer