A ‘Swift’ run through making an iOS game

After finally having finished a personal game project, for the purpose of learning the Swift programming language, I want to share my thoughts on a few fun and amusing challenges involved in creating an iOS game. This post will not be a detailed walkthrough, but more of a show-and-tell presentation of what I believe were the most interesting aspects of the overall project.

Skrevet av Joel Chelliah

icon-180.png

Learning Swift by making a game

Last year I decided to look into Swift, as it was a language I had only barely touched upon earlier, yet one that I found very intriguing. My main goal was to learn the programming language itself, but I also wanted to get familiar with Xcode IDE, and get a better understanding of iOS development in general. I decided to do all this by creating my very first iOS game, and six months later, Banana Catcher is now available on the App store.

Here I will be sharing some thoughts and experiences around what I found to be the most fun and amusing challenges involved in this project. I will however not be going into much implementation details. If you are interested in seeing more, feel free to check out the full source code available on github.

Go catch those bananas

If you have the time and opportunity, I recommend playing through the game once or twice before reading through the rest of this post. If not, here’s a small summary of the game.

Banana catcher.gif

Banana Catcher is a simple, touch-based game, where the player controls the movement of a box by pressing on the screen. In the sky, there is an evil monkey flying around, flinging bananas all over the place. The player needs to move the box around, catching as many bananas as possible, while at the same time avoiding different obstacles the monkey might decide to throw. The game progressively gets harder as the monkey starts throwing faster and faster, and the goal is to keep collecting bananas for as long as possible… before the monkey eventually kills you.

Now that we’re familiar with the game, lets move on to the development process.

Throw away the storyboard

When starting on a new iOS project, there are a number of ways to plan out and set up the basic structure of your app. One common approach is to use Xcode’s storyboard feature. The storyboard is a built-in tool in Xcode that lets us graphically lay out the path we want the user to follow when using the app. It basically provides us a way to create something that resembles an actual storyboard directly in the editor by dragging and dropping elements onto a blank canvas. Here’s a typical storyboard you might end up with when creating an app:

storyboard.png

First, you start by placing views, segues and various other components onto an empty canvas. The way you then bind these components together represent the path you want the user to follow throughout the app. Elements, such as buttons, labels and images can then be dragged and dropped inside the views and customized without have to write a single line of code. Finally you write some code for fetching data, populating the views, handlings events and so on.

The benefit of this approach is that your app gets the standard iOS app look straight out of the box, without you having to put much extra work into it. However, when creating something with a lot of dynamic content, such as a game, it might be best to avoid using the storyboard altogether. For example, you might want certain elements, such as monsters and items, to appear at random places on the screen. This can be hard to do on something as static as a predefined storyboard. Another example is if you want custom buttons and labels that float around, or otherwise behave differently than what the typical iPhone ones do.

Instead, we can go for a more responsive approach. In this case, our storyboard would just be a single view, in which we inject our dynamically created content into:

no_storyboard.png

While the game is running, the user will always be staying in the same view, but we dynamically change which scenes are shown to the user, depending on the outcome of various events in the game. For example, when starting the game we show the menu scene:

let gameScene = MenuScene(size: view.bounds.size)
let transition = SKTransition.doorsOpenHorizontalWithDuration(1.0)
let gameView = view as! SKView
// ...
gameView.presentScene(gameScene, transition: transition)

The code above is run just after the app has finished loading. Here we initialize the menu scene and dynamically present it to the main view. Similarly, when the user clicks on the play button we intitialize and present the game scene. When the main character dies, we move on to the game over scene, and so on. The creation and handling of the scenes are all done programatically, and there is no reason to even look at the storyboard at all.

What goes into a scene?

Now that we have a bunch of scenes, we need to populate them with some content which the user can interact with. There are three very important things that need to be addressed within a scene:

  • Sprites: They represent the different elements in the game, whether it’s a button, a controllable character or just some decoration in the background. All these need to be added to the scene, positioned, and hooked up to be able to interact with the user or other sprites in the game.
  • Physics: The physics bind the different elements together and make them come alive. Different physical properties need to be defined, such as gravity, movement speed and rotation. We also have to specify which elements can collide with each other, and trigger some sort of event when they do.
  • Event handling: These cover different actions that are either triggered by the user (e.g. clicking a button), collision detection events, or events triggered by conditions that are met in the game (e.g. x seconds have passed, the main character dies, etc).

Sprites

A sprite is any visible element in the game, that we need to add to the scene at the correct position, and set up to respond to the various interactions that come from either the user or the game itself.

Some sprites, such as the background in the game scene, are fairly simple and only require a few lines of code.

background_preview.png
let background = SKSpriteNode(texture: Textures.background)
background.position = CGPointMake(hWidth, background.size.height / 2)

scene.addChild(background)

Other sprites have a much more complex structure, that include custom physics rules and animations. The evil monkey character has its own custom sprite class containing all this extra logic.

evil_monkey.gif
monkey_angry.gif
class EvilMonkey: SKSpriteNode, ItemThrower {
private let flyingTextures = Textures.monkeyFlying
private let angryTextures = Textures.monkeyAngry
// etc...

init() {
let texture = flyingTextures.first!
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
initPhysics()
animate()
// ...
}
// ...

private func animate() {
let animation = SKAction.animateWithTextures(flyingTextures, timePerFrame: 0.06)
self.runAction(SKAction.repeatActionForever(animation))
}
}
// ...

let monkey = EvilMonkey()
monkey.position = CGPoint(x: hWidth, y: height - offset)
scene.addChild(monkey)

A fair amount of code has been removed from the above example for clarity’s sake. What we are left with is the initialization of the sprite. This includes initializing some physical properties, which we will look into a bit later, and starting the monkey’s main animation cycle. Finally, just as we did for the background, we are creating a sprite object, which we can then add to the scene, position, and hook up to respond to any additional events if necessary.

Physics

Configuring the rules and properties of the built-in physics engine is fairly simple. Here we set the gravity for the game scene, and then create a rectangular edge around this scene, to keep certain sprites from disappearing off the screen:

self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVectorMake(0, -5)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody?.categoryBitMask = CollisionCategories.EdgeBody

All we need to do then is to run this code whenever the user is sent to the game scene.

Additionally, we also need to provide each of our sprites with some physics rules of their own, to ensure that they behave correctly in regards to the scene and the other sprites around them. Here are the physics rules for Basket man, the main character in the game:

blink_menu.gif
basket_man.gif
ouch.gif
class BasketMan: SKSpriteNode {
//...
init() {
//...
self.physicsBody = SKPhysicsBody(texture: self.texture!,size:self.size)
self.physicsBody?.dynamic = true
self.physicsBody?.usesPreciseCollisionDetection = false
self.physicsBody?.allowsRotation = false

self.physicsBody?.categoryBitMask = CollisionCategories.BasketMan
self.physicsBody?.contactTestBitMask = CollisionCategories.Banana
self.physicsBody?.collisionBitMask = CollisionCategories.Ground | CollisionCategories.EdgeBody
}
//...
}

The most important parts to note are the three bottom lines. Here we configure some attributes of the sprite’s physical body, called bitmasks. This will help us more easily detect and handle collisions:

  • Category bitmask: This is for grouping sprites into specific collision categories, so that we can provide seperate logic based on which category they belong to.
  • Contact test bitmask: Here we specify what kind of contact with other collision categories should trigger a contact event.
  • Collision bitmask: Finally we specify which types of other sprites it should be allowed to collide with.

Here we see that when the character hits a banana, it will provide a contact event which we can test for and respond to in our collision event handling. When the character hits the ground, or the edge of the screen, which we defined in the previous code block, the game will treat that as a regular collision, and the in-game physics will handle that appropriately. In this case, the in-game physics will keep the character from falling through ground or disappearing off the edges of the screen.

Similarly, we must also set the appropriate bitmask properties for the bananas, the ground, and any other sprites we want to handle collision events for.

Event handling

There are many diverse types of events that must handled in each scene. One of which is the interaction from the player, for example, when the player presses a button in the menu scene.

Screen Shot 2016-01-30 at 17.20.02.png

We handle this by pattern matching the touched element in the game with all elements on the scene that we have defined as button nodes. If there is a match we run the code for that touch event, such as toggling the sound, or moving to another scene in the game:

let touchLocation = touches.first!.locationInNode(scene)
let touchedNode = scene.nodeAtPoint(touchLocation)
if let nodeName = touchedNode.name {
switch nodeName {
case ButtonNodes.sound: toggleSound(touchedNode)
case ButtonNodes.howTo: gotoTutorialScene(touchedNode)
case ButtonNodes.play: gotoGameScene(touchedNode)
// etc...
}
}

For certain scenes, we also need to handle collision detection events that are created by the physics engine. This is done by filtering through the different contact events based on our preset collision categories, and writing specific rules for each type of contact we wish to handle. Earlier we defined, in the physics for Basket Man, that a contact event should be generated when hitting a banana:

self.physicsBody?.categoryBitMask = CollisionCategories.BasketMan
self.physicsBody?.contactTestBitMask = CollisionCategories.Banana
Screen Shot 2016-02-02 at 23.31.27.png
Screen Shot 2016-02-02 at 23.32.04.png

In the game scene, we need to pinpoint this event and provide some logic for how it should be handled:

// Within the code for handling contact events:
// ...
if matchesCategory(sprite1.categoryBitMask, CollisionCategories.Banana) {
if matchesCategory(sprite2.categoryBitMask, CollisionCategories.BasketMan) {
let points = GamePoints.BananaCaught

updateScore(points)
basketMan.collectAnimation()
// etc...
} else if // Test for other types of contact
// and so on...

Other types of events that we also need to address include:

  • OnLoad and OnView events: Here we put initialisation code for the different scenes, such as generating and positioning the sprites, change music, ads placement, etc.
  • Update events: These are run once for each frame. Here we can evaluate the game state at a specific time or time intervall, and perform necessary actions accordingly. Some examples include deciding when the monkey should throw the next banana based on the given time frame, or moving the main character by checking where on the screen the user is pressing at that exact moment.

Finishing touches

Finally, there are lot of additional features that can be added to a game to give it a more polished look and feel, such as sound, music or even special visual effects.

extension SKNode {
func playSound(name: String) {
if soundEnabled {
let fileName = // ...
self.runAction(SKAction.playSoundFileNamed("\(fileName).wav", waitForCompletion: false))
}
}
// ...
}

Here, we extend a common parent class and give it a playSound function, which can then be called from any of its child classes. In this particular case, all sprites and scenes are children of this class, making this function almost globally available. We can then call it from our EvilMonkey class, for example when the monkey goes into a fit of rage:

func throwTantrum() {
playSound(Sounds.angry)

let textures = angryTextures
let animation = SKAction.animateWithTextures(textures, timePerFrame: 0.05)
// ...
burstIntoFlames()
}

Special visual effects can be achieved through the use of particle emitters. Some examples of these are the rain of bananas we can see in the menu scene, or when the monkey (spoiler alert!) explodes into a burst of flames during the later stages of the game.

Screen Shot 2016-01-30 at 17.47.45.png

Particle emitter files can be easily created in the Xcode IDE by dragging some sliders around, and then integrated into the game with just a few lines of code:

particleemitter.png
particleemittersettings.png
private let angerEmitter = SKEmitterNode(fileNamed: "Anger")!
private let angerEffect = SKEffectNode()
// ...

private func initAngerEffects() {
angerEmitter.hidden = true
angerEffect.addChild(angerEmitter)
addChild(angerEffect)
// ...
}

private func burstIntoFlames() {
angerEmitter.hidden = false
angerEmitter.resetSimulation()
}

Here, still inside the EvilMonkey class, we attach the particle emitter to an effect node, which we again add as a child of our monkey sprite. The anger emitter is configured to create 600 fiery-looking particles, at an explosive rate of 455 per second, before dying out. So each time we need the explosion effect to occur, we simply need to reset the simulation.

My thoughts on Swift?

During these last few months I feel like I’ve learned a lot about Swift, and become very comfortable with the syntax and semantics of this language. As someone who frequently dabbles in both object oriented and functional programming languages, my opinion of Swift is that it provides a clean, multi-paradigm approach to iOS development. I found it both effective and extremely useful to be able to combine OOP concepts such as inheritance, generics and protocol oriented programming, with functional programming ones, like mapping, filtering and pattern matching.

Swift also provides many modern language features, such as tuples, chainable optional types, closures and traits. This, coupled with some fancy syntactic sugar here and there, is what made me enjoy working with this language so much. Code readability is also something I value very highly, and I found it quite easy to write elegant and beautiful lines of code in Swift.

Conclusion

This has been a very brief overview of what I consider the most fun and interesting challenges involved in creating this game. Fun, is also the word I would use to summarize this whole experience of learning Swift, and I would definitely recommend this approach to learning the language.

It’s hard to point out which parts of the process were easy or difficult, as the whole experience has been one progressive, educational journey from zero to a final product. In the beginning I kept to just writing the basic logic and structure of the game. Then as I got more comfortable with the language, I gradually ventured into newer territory, fleshing out the game here and there with more advanced features, as well as refactoring my older code.

There is of course a lot more going on behind the scenes that are not covered in this blog post, so if you found this interesting, feel free to take a look at the full source code on github. Finally, if you get the chance to try out the game, I would love to hear what you think.

Like what you read? Give Bekk Consulting a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.