Chapter 1. Laying Out a Game
Games are software, and the best software has had some thought put into it regarding how it’s going to work. When you’re writing a game, you need to keep in mind how you’re going to handle the individual tasks that the game needs to perform, such as rendering graphics, updating artificial intelligence (AI), handling input, and the hundreds of other small tasks that your game will need to deal with.
In this chapter, you’ll learn about ways you can lay out the structure of your game that will make development easier. You’ll also learn how to organize the contents of your game so that adding more content and gameplay elements is easier, and find out how to make your game do multiple things at once.
Laying Out Your Engine
Solution
The biggest thing to consider when you’re thinking about how to best lay out your game is how the game will be updated. There are three main things that can cause the state of the game to change:
- Input from the user
- The game may change state when the user provides some input, such as tapping a button or typing some text. Turn-based games are often driven by user input (e.g., in a game of chess, the game state might only be updated when the user finishes moving a piece).
- Timers
- The game state may change every time a timer goes off. The delay between timer updates might be very long (some web-based strategy games have turns that update only once a day), or very short (such as going off every time the screen finishes drawing). Most real-time games, like shooters or real-time strategy games, use very short-duration timers.
- Input from outside
The game state may change when information from outside the game arrives. The most common example of this is some information arriving from the network, but it can also include data arriving from built-in sensors, such as the accelerometer.
Sometimes, this kind of updating is actually a specific type of timer-based update, because some networks or sensors need to be periodically checked to see if new information has arrived.
Discussion
None of these methods are mutually exclusive. You can, for example, run your game on a timer to animate content, and await user input to move from one state to the next.
Updating every frame is the least efficient option, but it lets you change state often, which makes the game look smooth.
Creating an Inheritance-Based Game Layout
Problem
You want to use an inheritance-based (i.e., a hierarchy-based) architecture for your game, which is simpler to implement.
Solution
First, define a class called GameObject
:
class
GameObject
:
NSObject
{
func
update
(
deltaTime
:
Float
)
{
// 'deltaTime' is the number of seconds since
// this was last called.
// This method is overriden by subclasses to update
// the object's state - position, direction, and so on.
}
}
When you want to create a new kind of game object, you create a subclass of the GameObject
class, which inherits all of the behavior of its parent class and can be customized:
class
Monster
:
GameObject
{
var
hitPoints
:
Int
=
10
// how much health we have
var
target
:
GameObject
?
// the game object we're attacking
override
func
update
(
deltaTime
:
Float
)
{
super
.
update
(
deltaTime
)
// Do some monster-specific updating
}
}
Discussion
In an inheritance-based layout, as shown in Figure 1-1, you define a single base class for your game object (often called GameObject
), which knows about general tasks like being updated, and then create subclasses for each specific type of game object. This hierarchy of subclasses can be multiple levels deep (e.g., you might subclass the GameObject
class to make the Monster
subclass, and then subclass that to create the Goblin
and Dragon
classes, each of which has its own different kinds of monster-like behavior).
The advantage of a hierarchy-based layout is that each object is able to stand alone: if you have a Dragon
object, you know that all of its behavior is contained inside that single object, and it doesn’t rely on other objects to work. The downside is that you can often end up with a very deep hierarchy of different game object types, which can be tricky to keep in your head as you program.
Creating a Component-Based Game Layout
Problem
You want to use a component-based architecture for your game, which allows for greater flexibility.
Solution
First, define a Component
class. This class represents components that are attached to game objects—it is a very simple class that, at least initially, only has a single method and a single property:
@class
GameObject
;@interface
Component
:NSObject
-
(
void
)
update:
(
float
)
deltaTime
;
@property
(
weak
)
GameObject
*
gameObject
;
@end
Next, define a GameObject
class. This class represents game objects:
class
Component
:
NSObject
{
// The game object this component is attached to
var
gameObject
:
GameObject
?
func
update
(
deltaTime
:
Float
)
{
// Update this component
}
}
The implementation for this class looks like this:
class
GameObject
:
NSObject
{
// The collection of Component objects attached to us
var
components
:
[
Component
]
=
[]
// Add a component to this gameobject
func
addComponent
(
component
:
Component
)
{
components
.
append
(
component
)
component
.
gameObject
=
self
}
// Remove a component from this game object, if we have it
func
removeComponent
(
component
:
Component
)
{
if
let
index
=
find
(
components
,
component
)
{
component
.
gameObject
=
nil
components
.
removeAtIndex
(
index
)
}
}
// Update this object by updating all components
func
update
(
deltaTime
:
Float
)
{
for
component
in
self
.
components
{
component
.
update
(
deltaTime
)
}
}
// Returns the first component of type T attached to this
// game object
func
findComponent
<
T
:
Component
>
()
->
T
?
{
for
component
in
self
.
components
{
if
let
theComponent
=
component
as
?
T
{
return
theComponent
}
}
return
nil
;
}
// Returns an array of all components of type T
// (this returned array might be empty)
func
findComponents
<
T
:
Component
>
()
->
[
T
?
]
{
// NOTE: this returns an array of T? (that is,
// optionals), even though it doesn't strictly need
// to. This is because Xcode 6.1.1's Swift compiler
// was crashing when this function returned an array of T
// (that is, non-optionals). Your mileage may vary.
var
foundComponents
:
[
T
]
=
[]
for
component
in
self
.
components
{
if
let
theComponent
=
component
as
?
T
{
foundComponents
.
append
(
theComponent
)
}
}
return
foundComponents
}
}
Using these objects looks like this:
// Define a type of component
class
DamageTaking
:
Component
{
var
hitpoints
:
Int
=
10
func
takeDamage
(
amount
:
Int
)
{
hitpoints
-=
amount
}
}
// Make an object - no need to subclass GameObject,
// because its behavior is determined by which
// components it has
let
monster
=
GameObject
()
// Add a new DamageTaking component
monster
.
addComponent
(
DamageTaking
())
// Get a reference to the first DamageTaking component
let
damage
:
DamageTaking
?
=
monster
.
findComponent
()
damage
?
.
takeDamage
(
5
)
// When the game needs to update, send all game
// objects the "update" message.
// This makes all components run their update logic.
monster
.
update
(
0.33
)
Discussion
In a component-based architecture, as shown in Figure 1-2, each game object is made up of multiple components. Compare this to an inheritance-based architecture, where each game object is a subclass of some more general class (see Creating an Inheritance-Based Game Layout).
A component-based layout means you can be more flexible with your design and not worry about inheritance issues. For example, if you’ve got a bunch of monsters, and you want one specific monster to have some new behavior (such as, say, exploding every five seconds), you just write a new component and add it to that monster. If you later decide that you want other monsters to also have that behavior, you can add that behavior to them, too.
In a component-based architecture, each game object has a list of components. When something happens to an object—for example, the game updates, or the object is added to or removed from the game—the object goes through all of its components and notifies them. This gives them the opportunity to respond in their own way.
The main problem with component-based architectures is that it’s more laborious to create multiple copies of an object, because you have to create and add the same set of components every time you want a new copy.
Note
The findComponent
and findComponents
methods are worth a little explanation. These functions are designed to let you get a reference to a component, or an array of components, attached to the game object. The functions use generics to make them return an array of the type of component you expect. This means that you don’t need to do any type casting in your code—you’re guaranteed to receive objects that are the right type.
Calculating Delta Times
Solution
First, decide which object should be used to keep track of time. This may be a view controller, an SKScene
, a GLKViewController
, or something entirely custom.
Create an instance variable inside that object:
class
TimeKeeper
:
NSObject
{
var
lastFrameTime
:
Double
=
0.0
}
Then, each time your game is updated, get the current time in milliseconds, and subtract lastFrameTime
from that. This gives you the amount of time that has elapsed since the last update.
When you want to make something happen at a certain rate—for example, moving at 3 meters per second—multiply the rate by the delta time:
func
update
(
currentTime
:
Double
)
{
// Calculate the time since this method was last called
let
deltaTime
=
currentTime
-
lastFrameTime
// Move at 3 units per second
let
movementSpeed
=
3.0
// Multiply by deltaTime to work out how far
// an object needs to move this frame
someMovingObject
.
move
(
distance
:
movementSpeed
*
deltaTime
)
// Set last frame time to current time, so that
// we can calculate the delta time when we're next
// called
lastFrameTime
=
currentTime
}
Discussion
“Delta time” means “change in time.” Delta times are useful for keeping track of how much time has elapsed from one point in time to another—in games, this means the time from one frame to the next. Because the game content changes frame by frame, the amount of time between frames becomes important.
Additionally, the amount of time between frames might change a little. You should always be aiming for a constant frame rate of 60 frames per second (i.e., a delta time of 16 milliseconds: 1÷60 = 0.0166); however, this may not always be achievable, depending on how much work needs to be done in each frame. This means that delta time might vary slightly, so calculating the delta time between each frame becomes necessary if you want rates of change to appear constant.
Some engines give you the delta time directly. For example, CADisplayLink
gives you a duration
property (see Updating Based on When the Screen Updates), and GLKViewController
gives you timeSinceLastUpdate
(see Rotating a Cube).
Some engines give you just the current time, from which you can calculate the delta time. For example, the SKScene
class passes the currentTime
parameter to the update:
method (discussed further in Adding Thrusters to Objects).
In other cases (e.g., if you’re doing the main loop yourself), you won’t have easy access to either. In these cases, you need to get the current time yourself:
let
currentTime
=
NSDate
.
timeIntervalSinceReferenceDate
()
as
Double
Detecting When the User Enters and Exits Your Game
Problem
You want to detect when the user leaves your game, so that you can pause the game. You also want to know when the user comes back.
Solution
To get notified when the user enters and exits your game, you register to receive notifications from an NSNotificationCenter
. The specific notifications that you want to receive are UIApplicationDidBecomeActiveNotification
, UIApplicationWillEnterForegroundNotification
, UIApplicationWillResignActiveNotification
, and UIApplicationDidEnterBackgroundNotification
:
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
let
center
=
NSNotificationCenter
.
defaultCenter
()
center
.
addObserver
(
self
,
selector
:
"applicationDidBecomeActive:"
,
name
:
UIApplicationDidBecomeActiveNotification
,
object
:
nil
)
center
.
addObserver
(
self
,
selector
:
"applicationWillEnterForeground:"
,
name
:
UIApplicationWillEnterForegroundNotification
,
object
:
nil
)
center
.
addObserver
(
self
,
selector
:
"applicationWillResignActive:"
,
name
:
UIApplicationWillResignActiveNotification
,
object
:
nil
)
center
.
addObserver
(
self
,
selector
:
"applicationDidEnterBackground:"
,
name
:
UIApplicationDidEnterBackgroundNotification
,
object
:
nil
)
}
func
applicationDidBecomeActive
(
notification
:
NSNotification
)
{
NSLog
(
"Application became active"
)
}
func
applicationDidEnterBackground
(
notification
:
NSNotification
)
{
NSLog
(
"Application entered background - unload textures!"
)
}
func
applicationWillEnterForeground
(
notification
:
NSNotification
)
{
NSLog
(
"Application will enter foreground - reload "
+
"any textures that were unloaded"
)
}
func
applicationWillResignActive
(
notification
:
NSNotification
)
{
NSLog
(
"Application will resign active - pause the game now!"
)
}
deinit
{
// Remove this object from the notification center
NSNotificationCenter
.
defaultCenter
()
.
removeObserver
(
self
)
}
Discussion
On iOS, only one app can be the “active” application (i.e., the app that is taking up the screen and that the user is interacting with). This means that apps need to know when they become the active one, and when they stop being active.
When your game is no longer the active application, the player can’t interact with it. This means that the game should pause (see Pausing a Game). When the game resumes being the active application, the player should see a pause screen.
Note
Pausing, of course, only makes sense in real-time games, such as shooters, driving games, arcade games, and so on. In a turn-based game, like a strategy or puzzle game, you don’t really need to worry about the game being paused.
In addition to being the active application, an application can be in the foreground or the background. When an application is in the foreground, it’s being shown on the screen. When it’s in the background, it isn’t visible at all. Apps that are in the background become suspended after a short period of time to save battery power. Apps that enter the background should reduce their memory consumption as much as possible; if your app consumes a large amount of memory while it is in the background, it is more likely to be terminated by iOS.
Updating Based on a Timer
Solution
Use an NSTimer
to receive a message after a certain amount of time, or to receive an update on a fixed schedule.
First, add an instance variable to your view controller:
var
timer
:
NSTimer
?
Next, add a method that takes an NSTimer
parameter:
func
updateWithTimer
(
timer
:
NSTimer
)
{
// Timer went off; update the game
NSLog
(
"Timer went off!"
)
}
Finally, when you want to start the timer:
// Start a timer
self
.
timer
=
NSTimer
.
scheduledTimerWithTimeInterval
(
0.5
,
target
:
self
,
selector
:
"updateWithTimer"
,
userInfo
:
nil
,
repeats
:
true
)
To stop the timer:
// Stop a timer
self
.
timer
?
.
invalidate
()
self
.
timer
=
nil
Discussion
An NSTimer
waits for a specified number of seconds, and then calls a method on an object that you specify. You can change the number of seconds by changing the timeInterval
parameter.
You can also make a timer either fire only once or repeat forever, by changing the repeats
parameter to false
or true
, respectively.
Updating Based on When the Screen Updates
Solution
Use a CADisplayLink
, which sends a message every time the screen is redrawn.
First, import
the QuartzCore
framework:
import
QuartzCore
Next, add an instance variable to your view controller:
var
displayLink
:
CADisplayLink
?
Next, add a method that takes a single parameter (a CADisplayLink
):
func
screenUpdated
(
displayLink
:
CADisplayLink
)
{
// Update the game.
}
Finally, add this code when you want to begin receiving updates:
// Create and schedule the display link
displayLink
=
CADisplayLink
(
target
:
self
,
selector
:
"screenUpdated:"
)
displayLink
?
.
addToRunLoop
(
NSRunLoop
.
mainRunLoop
(),
forMode
:
NSRunLoopCommonModes
)
When you want to pause receiving updates, set the paused
property of the CADisplayLink
to YES
:
// Pause the display link
displayLink
?
.
paused
=
true
When you want to stop receiving updates, call invalidate
on the CADisplayLink
:
// Remove the display link; once done, you need to
// remove it from memory
displayLink
?
.
invalidate
()
displayLink
=
nil
Discussion
When we talk about “real-time” games, what comes to mind is objects like the player, vehicles, and other things moving around the screen, looking like they’re in continuous motion. This isn’t actually what happens, however—what’s really going on is that the screen is redrawing itself every 1/60 of a second, and every time it does this, the locations of some or all of the objects on the screen change slightly. If this is done fast enough, the human eye is fooled into thinking that everything’s moving continuously.
Note
In fact, you don’t technically need to update as quickly as every 1/60 of a second—anything moving faster than 25 frames per second (in other words, one update every 1/25 of a second) will look like motion. However, faster updates yield smoother-looking movement, and you should always aim for 60 frames per second.
You’ll get the best results if you update your game at the same rate as the screen. You can achieve this with a CADisplayLink
, which uses the Core Animation system to figure out when the screen has updated. Every time this happens, the CADisplayLink
sends its target a message, which you specify.
It’s worth mentioning that you can have as many CADisplayLink
objects as you like, though they’ll all update at the same time.
Pausing a Game
Solution
Keep track of the game’s “paused” state in a Bool
variable. Then, divide your game objects into two categories—ones that run while paused, and ones that don’t run while paused:
for
gameObject
in
self
.
gameObjects
{
// Update it if we're not paused, or if this game object
// ignores the paused state
if
self
.
paused
==
false
||
gameObject
.
canPause
==
false
{
gameObject
.
update
(
deltaTime
)
}
}
Discussion
The simplest possible way to pause the game is to keep track of a pause state; every time the game updates, you check to see if the pause state is set to true
, and if it is, you don’t update any game objects.
However, you often don’t want every single thing in the game to freeze. For example:
- The user interface may need to continue to animate.
- The network may need to keep communicating with other computers, rather than stopping entirely.
In these cases, having special objects that never get paused makes more sense.
Calculating Time Elapsed Since the Game Start
Solution
When the game starts, create an NSDate
object and store it:
// Store the time when the game started as a property
var
gameStartDate
:
NSDate
?
// When the game actually begins, store the current date
self
.
gameStartDate
=
NSDate
()
When you want to find out how much time has elapsed since the game started, create a second NSDate
and use the timeIntervalSinceDate
method to calculate the time:
let
now
=
NSDate
()
let
timeSinceGameStart
=
now
.
timeIntervalSinceDate
(
self
.
gameStartDate
!
)
NSLog
(
"The game started \(timeSinceGameStart) seconds ago"
)
Discussion
NSDate
objects represent moments in time. They’re the go-to object for representing any instant of time that you want to be able to refer to again later, such as when your game starts. NSDate
objects can refer to practically any date in the past or future and are very precise.
When you create an NSDate
with the [NSDate date]
method, you get back an NSDate
object that refers to the current time (i.e., the instant when the NSDate
object was created).
To determine the interval between two dates, you use timeIntervalSinceDate:
. This method returns an NSTimeInterval
, which is actually another term for a floating-point number. These values are represented in seconds, so it’s up to your code to do things like determine the number of hours and minutes:
let
hours
=
timeSinceGameStart
/
3600.0
// 3600 seconds in an hour
let
minutes
=
timeSinceGameStart
%
3600.0
/
60.0
// 60 seconds in a minute
let
seconds
=
timeSinceGameStart
%
60.0
// remaining seconds
NSLog
(
"Time elapsed: \(hours):\(minutes):\(seconds)"
)
Working with Closures
Solution
Closures are ideal for this:
class
GameObject
{
// define a type of closure that takes a single GameObject
// as a parameter and returns nothing
var
onCollision
:
(
GameObject
->
Void
)
?
}
// Create two objects for this example
let
car
=
GameObject
()
let
brickWall
=
GameObject
()
// Provide code to run when the car hits any another object
car
.
onCollision
=
{
(
objectWeHit
)
in
NSLog
(
"Car collided with \(objectWeHit)"
)
}
// later, when a character collides:
car
.
onCollision
?
(
brickWall
)
// note the ? - this means that
// the code will only run if onCollision
// is not nil
Discussion
Closures are a language feature in Swift that allow you to store chunks of code in variables, which can then be worked with like any other variable.
Here’s an example of a simple closure:
var
multiplyNumber
:
Int
->
Int
multiplyNumber
=
{
(
number
)
->
Int
in
return
number
*
2
}
multiplyNumber
(
2
)
This is how you define a closure. In this case, the closure returns an
Int
, is namedmultiplyNumber
, and accepts a singleInt
parameter.Just like any other variable, once a closure is defined, it needs to be given a value. In this case, we’re providing a closure that takes an
Int
and returns anInt
, just like the variable’s definition.Calling a closure works just like calling any other function.
How closures work
So far, this just seems like a very roundabout way to call a function. However, the real power of closures comes from two facts:
- Closures capture the state of any other variables their code references.
- Closures are objects, just like everything else. They stay around until you need them. If you store a closure, you can call it however often you like.
This is extremely powerful, because it means that your game doesn’t need to carefully store values for later use; if a closure needs a value, it automatically keeps it.
You define a closure by describing the parameters it receives and the type of information it returns. To help protect against mistakes, you can also create a type alias for closures, which defines a specific type of closure. This allows you to declare variables with more easily understandable semantics:
typealias
ErrorHandler
=
NSError
->
Void
var
myErrorHandler
:
ErrorHandler
myErrorHandler
=
{
(
theError
)
in
// do work with theError
NSLog
(
"i SPILL my DRINK! \(theError)"
)
}
Closures and other objects
When a closure is created, the compiler looks at all of the variables that the closure is referencing. If a variable is a simple value, like an int
or a float
, that value is simply copied. However, if the variable is a Swift object, it can’t be copied because it could potentially be very large. Instead, the object is retained by the closure. When a closure is freed, any objects retained by the closure are released.
This means that if you have a closure that references another object, that closure will keep the other object around. This is usually what you want, because it would be annoying to have to remember to keep the variables referenced by closures in memory. However, sometimes that’s not what you want.
One example is when you want a closure to run in two seconds’ time that causes an enemy object to run an attack animation. However, between the time you schedule the closure and the time the closure runs, the enemy is removed from the game. If the closure has a strong reference to the enemy, the enemy isn’t actually removed from memory until the closure is scheduled to run, which could have unintended side effects.
To get around this problem, you use weak references. A weak reference is a reference that does not keep an object in memory; additionally, if the object that is being referred to is removed (because all owning references to it have gone away), the weak reference will automatically be set to nil
. For more information on weak references, see The Swift Programming Language’s chapter on Automatic Reference Counting.
Writing a Method That Calls a Closure
Problem
You want to write a method that, after performing its work, calls a closure to indicate that the work is complete.
For example, you want to tell a character to start moving to a destination, and then run a closure when the character finishes moving.
Solution
To create a method that takes a closure as a parameter, you just do this:
func
moveToPosition
(
position
:
CGPoint
,
completion
:
(
Void
->
Void
)
?
)
{
// Do the actual work of moving to the location, which
// might take place over several frames
// Call the completion handler, if it exists
completion
?
()
}
let
destination
=
CGPoint
(
x
:
5
,
y
:
3
)
// Call the function and provide the closure as a parameter
moveToPosition
(
destination
)
{
NSLog
(
"Arrived!"
)
}
Discussion
Methods that take a closure as a parameter are useful for when you’re writing code that starts off a long-running process, and you want to run some code at the conclusion of that process but want to keep that conclusion code close to the original call itself.
Before closures were added to the Swift language, the usual technique was to write two methods: one where you started the long-running process, and one that would be called when the process completed. This separates the various parts of the code, which decreases the readability of your code; additionally, passing around variables between these two methods is more complicated (because you need to manually store them in a temporary variable at the start, and retrieve them at the end; with closures, you just use the variables without any additional work).
Note
If the last parameter that you pass to a function or method is a closure, you can place the closure outside the function call’s parentheses. It can look a little cleaner.
Working with Operation Queues
Problem
You want to put chunks of work in a queue, so that they’re run when the operating system has a moment to do them.
Solution
Use an NSOperationQueue
to schedule closures to be run in the background without interfering with more time-critical tasks like rendering or accepting user input:
// Create a work queue to put tasks on
let
concurrentQueue
=
NSOperationQueue
()
// This queue can run 10 operations at the same time, at most
concurrentQueue
.
maxConcurrentOperationCount
=
10
// Add some tasks
concurrentQueue
.
addOperationWithBlock
{
UploadHighScores
()
}
concurrentQueue
.
addOperationWithBlock
{
SaveGame
()
}
concurrentQueue
.
addOperationWithBlock
{
DownloadMaps
()
}
Discussion
An operation queue is a tool for running chunks of work. Every application has an operation queue called the main queue. The main queue is the queue that normal application tasks (e.g., handling touches, redrawing the screen, etc.) are run on.
Note
Many tasks can only be run on the main queue, including updating anything run by UIKit. It’s also a good idea to only have a single operation queue that’s in charge of sending OpenGL instructions.
The main queue is a specific NSOperationQueue
, which you can access using the mainQueue
method:
let
mainQueue
=
NSOperationQueue
.
mainQueue
()
mainQueue
.
addOperationWithBlock
{
()
->
Void
in
ProcessPlayerInput
()
}
It’s often the case that you want to do something in the background (i.e., on another operation queue), and then alert the user when it’s finished. However, as we’ve already mentioned, you can only do UIKit or OpenGL tasks (e.g., displaying an alert box) on the main queue.
To address this, you can put tasks on the main queue from inside a background queue:
let
backgroundQueue
=
NSOperationQueue
()
backgroundQueue
.
addOperationWithBlock
{
()
->
Void
in
// Do work in the background
NSOperationQueue
.
mainQueue
().
addOperationWithBlock
{
// Once that's done, do work on the main queue
}
}
An operation queue runs as many operations as it can simultaneously. The number of concurrent operations that can be run depends on several conditions, including the number of processor cores available and the different priorities that other operations may have.
By default, an operation queue determines the number of operations that it can run at the same time on its own. However, you can specify a maximum number of concurrent operations by using the maxConcurrentOperationCount
property.
Performing a Task in the Future
Solution
Use dispatch_after
to schedule a closure of code to run in the future:
// Place a bomb, but make it explode in 10 seconds
PlaceBomb
()
let
delayTime
:
Float
=
10.0
let
dispatchTime
=
dispatch_time
(
DISPATCH_TIME_NOW
,
Int64
(
delayTime
*
Float
(
NSEC_PER_SEC
)))
let
queue
=
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_BACKGROUND
,
0
)
dispatch_after
(
dispatchTime
,
queue
)
{
()
->
Void
in
// Time's up. Kaboom.
ExplodeBomb
()
}
Discussion
NSOperationQueue
is actually a higher-level wrapper around the lower-level features provided by the C-based Grand Central Dispatch API. Grand Central Dispatch, or GCD, works mostly with objects called “dispatch queues,” which are basically NSOperationQueue
s. You do work with GCD by putting closures onto a queue, which runs the closures. Just as with NSOperationQueue
, there can be many queues operating at the same time, and they can be serial or concurrent queues.
GCD provides a function called dispatch_after
that runs a closure on an operation queue at a given time. To use the function, you first need to figure out the time when the closure should be run. GCD doesn’t actually work in seconds, or even in nanoseconds, but rather with time units called dispatch_time_t
, which Apple’s documentation describes as “a somewhat abstract representation of time.”
To work with dispatch_time_t
, you use the function dispatch_time
, which takes two parameters: a base time and an amount of time to be added on top, measured in nanoseconds.
Once you specify a time for the closure to run, you need to get a reference to a GCD dispatch_queue
. You can get background queues using dispatch_get_global_queue
, but you can also get the main queue using dispatch_get_main_queue
.
Finally, you instruct GCD to run the closure.
Making Operations Depend on Each Other
Problem
You want to run some operations, but they need to run only after certain other operations are done.
Solution
To make an operation wait for another operation to complete, store each individual operation in a variable, and then use the addDependency:
method to indicate which operations need to complete before a given operation begins:
let
firstOperation
=
NSBlockOperation
{
()
->
Void
in
NSLog
(
"First operation"
)
}
let
secondOperation
=
NSBlockOperation
{
()
->
Void
in
NSLog
(
"Second operation"
)
}
let
thirdOperation
=
NSBlockOperation
{
()
->
Void
in
NSLog
(
"Third operation"
)
}
// secondOperation will not run until firstOperation and
// thirdOperation have finished
secondOperation
.
addDependency
(
firstOperation
)
secondOperation
.
addDependency
(
thirdOperation
)
NSOperationQueue
.
mainQueue
().
addOperations
([
firstOperation
,
secondOperation
,
thirdOperation
],
waitUntilFinished
:
true
)
Discussion
You can add an operation to another operation as a dependency. This is useful for cases where you want one closure to run only after one or more operations have completed.
To add a dependency to an operation, you use the addDependency:
method. Doing this doesn’t run the operation, but just links the two together.
Once the operation dependencies have been set up, you can add the operations to the queue in any order that you like; operations will not run until all of their dependencies have finished running.
Filtering an Array with Closures
Solution
Use the filtered
method to create an array that only contains objects that meet certain conditions:
let
array
=
[
"One"
,
"Two"
,
"Three"
,
"Four"
,
"Five"
]
NSLog
(
"Original array: \(array)"
)
let
filteredArray
=
filter
(
array
)
{
(
element
)
->
Bool
in
if
element
.
rangeOfString
(
"e"
)
!=
nil
{
return
true
}
else
{
return
false
}
}
NSLog
(
"Filtered array: \(filteredArray)"
)
Discussion
The closure that you provide to the filtered
method is called multiple times. Each time it’s called, it takes an item in the array as its single parameter, and returns true
if that item should appear in the filtered array.
Loading New Assets During Gameplay
Solution
For each resource that needs loading, run an operation that does the loading into memory, and do it in the background. Then run a subsequent operation when all of the loads have completed.
You can do this by scheduling load operations on a background queue, and also running an operation on the main queue that depends on all of the load operations. This means that all of your images will load in the background, and you’ll run code on the main queue when it’s complete:
let
imagesToLoad
=
[
"Image 1.jpg"
,
"Image 2.jpg"
,
"Image 3.jpg"
]
let
imageLoadingQueue
=
NSOperationQueue
()
// We want the main queue to run at close to regular speed, so mark this
// background queue as running in the background
// (Note: this is actually the default value, but it's good to know about
// the qualityOfService property.)
imageLoadingQueue
.
qualityOfService
=
NSQualityOfService
.
Background
// Allow loading multiple images at once
imageLoadingQueue
.
maxConcurrentOperationCount
=
10
// Create an operation that will run when all images are loaded - you may want
// to tweak this
let
loadingComplete
=
NSBlockOperation
{
()
->
Void
in
NSLog
(
"Loading complete!"
)
}
// Create an array for storing our loading operations
var
loadingOperations
:
[
NSOperation
]
=
[]
// Add a load operation for each image
for
imageName
in
imagesToLoad
{
let
loadOperation
=
NSBlockOperation
{
()
->
Void
in
NSLog
(
"Loading \(imageName)"
)
}
loadingOperations
.
append
(
loadOperation
)
// Don't run the loading complete operation until
// this load (and all other loads) are done
loadingComplete
.
addDependency
(
loadOperation
)
}
imageLoadingQueue
.
addOperations
(
loadingOperations
,
waitUntilFinished
:
false
)
imageLoadingQueue
.
addOperation
(
loadingComplete
)
Discussion
When you create an NSOperationQueue
, you can control its quality of service. By default, operation queues you create have the background quality of service, which indicates to the operating system that it’s OK for higher-priority operations to take precedence. This is generally what you want for your asset loading routines, because it’s important that you keep your application responsive to user input.
Depending on how much memory the rest of your game takes, you can also use this technique to load assets while the user is busy doing something else. For example, once the user reaches the main menu, you could start loading the resources needed for actual gameplay while you wait for the user to tap the New Game button.
Adding Unit Tests to Your Game
Problem
You want to test different parts of your game’s code in isolation, so that you can ensure that each part is working.
Solution
You can write code that tests different parts of your app in isolation using unit tests. By default, all newly created projects come with an empty set of unit tests, in which you can add isolated testing functions.
Note
If you’re working with an existing project, you can create a new set of unit tests by choosing File→New→Target and creating a Cocoa Touch Unit Testing Bundle.
You’ll find your unit test files in a group whose name ends with Tests
. For example, if your Xcode project is called MyAwesomeGame
, your testing files will be in a group named MyAwesomeGameTests
, and it will by default come with a file called MyAwesomeGameTests.swift.
When you want to add a test, open your test file (the .swift file) and add a method whose name begins with test
:
func
testDoingSomethingCool
()
{
let
object
=
SomeAwesomeObject
()
let
succeeded
=
object
.
doSomethingCool
()
if
succeeded
==
false
{
XCTFail
(
"Failed to do something cool"
);
}
}
When you want to run the tests, choose Product→Test or press Command-U. All of the methods in your testing classes that begin with test
will be run, one after the other.
You can also add additional collections of tests, by creating a new test suite. You do this by choosing File→New→File and creating a new Swift test case class. When you create this new class, don’t forget to make it belong to your testing target instead of your game target, or you’ll get compile errors.
Discussion
Unit testing is the practice of writing small tests that test specific features of your code. In normal use, your code is used in a variety of ways, and if there’s a bug, it can be difficult to track down exactly why your code isn’t behaving the way you want it to. By using unit tests, you can run multiple tests of your code and check each time to see if the results are what you expect. If a test fails, the parts of your game that use your code in that particular way will also fail.
Each test is actually a method in a test case. Test cases are subclasses of XCTestCase
whose names begin with test
. The XCTestCase
objects in a testing bundle make up a test suite, which is what’s run when you tell Xcode to test your application.
When tests run, Xcode performs the following tasks for each test method, in each test case, in each test suite:
-
Call the test case’s
setUp
method. - Call the test method itself, and note if the test succeeds or fails.
-
Call the test case’s
tearDown
method. - Show a report showing which tests failed.
As you can see, the test case’s setUp
and tearDown
methods are called for each test method. The idea behind this is that you use setUp
to create whatever conditions you want to run your test under (e.g., if you’re testing the behavior of an AI, you could use setUp
to load the level in which the AI needs to operate). Conversely, the tearDown
method is used to dismantle whatever resources are set up in setUp
. This means that each time a test method is run, it’s operating under the same conditions.
The contents of each test method are entirely up to you. Typically, you create objects that you want to test, run methods, and then check to see if the outcomes were what you expected. The actual way that you check the outcomes is through a collection of dedicated assertion methods, which flag the test as failing if the condition you pass in evaluates to false. The assertion methods also take a string parameter, which is shown to the user if the test fails.
For example:
// Fails if X is not nil
XCTAssertNil
(
X
,
"X should be nil"
)
// Fails if X IS nil
XCTAssertNotNil
(
X
,
"X should not be nil"
)
// Fails if X is not true
XCTAssertTrue
(
1
==
1
,
"1 really should be equal to 1"
)
// Fails if X is not false
XCTAssertFalse
(
2
!=
3
,
"In this universe, 2 equals 3 apparently"
)
// Fails if X and Y are not equal (tested by calling X.equals(Y)])
XCTAssertEqualObjects
((
2
),
(
1
+
1
),
"Objects should be equal"
)
// Fails if X and Y ARE equal (tested by calling X.equals(Y))
XCTAssertNotEqualObjects
(
"One"
,
"1"
,
"Objects should not be equal"
)
// Fails, regardless of circumstances
XCTFail
(
"Everything is broken"
)
There are several other assertion methods available for you to use that won’t fit in this book; for a comprehensive list, see the file XCTestAssertions.h (press Command-Shift-O, type XCTestAssertions
, and then press Enter).
2D Grids
Problem
You want to store objects in a two-dimensional grid, as shown in Figure 1-3.
Solution
Use a Grid
class, which lets you store and look up objects.
Create an NSObject
subclass called Grid
, and put the following code in Grid.swift:
// A GridPoint is a structure that represents a location in the grid.
// This is Hashable, because it will be stored in a dictionary.
struct
GridPoint
:
Hashable
{
var
x
:
Int
;
var
y
:
Int
;
// Returns a unique number that represents this location.
var
hashValue
:
Int
{
get
{
return
x
^
(
y
<<
32
)
}
}
}
// If an object is Hashable, it's also Equatable. To conform
// to the requirements of the Equatable protocol, you need
// to implement the == operation (which returns true if two objects
// are the same, and false if they aren't)
func
==
(
first
:
GridPoint
,
second
:
GridPoint
)
->
Bool
{
return
first
.
x
==
second
.
x
&&
first
.
y
==
second
.
y
}
// Next: the grid itself
class
Grid
:
NSObject
{
// The information is stored as a dictionary mapping
// GridPoints to arrays of NSObjects
var
contents
:
[
GridPoint
:
[
NSObject
]]
=
[
:
]
// Returns the list of objects that occupy the given position
func
objectsAtPosition
(
position
:
GridPoint
)
->
[
AnyObject
]
{
// If we have a collection of objects at this position, return it
if
let
objects
=
self
.
contents
[
position
]
{
return
objects
}
else
{
// Otherwise, create an empty collection
self
.
contents
[
position
]
=
[]
// And return it
return
[]
}
}
// Returns a GridPoint describing the position of an object, if it exists
func
positionForObject
(
objectToFind
:
NSObject
)
->
GridPoint
?
{
for
(
position
,
objects
)
in
contents
{
if
find
(
objects
,
objectToFind
)
!=
nil
{
return
position
}
}
return
nil
}
// Adds or moves the object to a location on the board
func
addObject
(
object
:
NSObject
,
atPosition
position
:
GridPoint
)
{
if
self
.
contents
[
position
]
==
nil
{
self
.
contents
[
position
]
=
[]
}
self
.
contents
[
position
]
?
.
append
(
object
)
}
// Removes a given object from the board
func
removeObjectFromGrid
(
objectToRemove
:
NSObject
)
{
var
newContents
=
self
.
contents
for
(
position
,
objects
)
in
contents
{
newContents
[
position
]
=
newContents
[
position
]
?
.
filter
{
(
item
)
->
Bool
in
return
item
!=
objectToRemove
}
}
self
.
contents
=
newContents
}
// Removes all objects at a given point from the board
func
removeAllObjectsAtPosition
(
position
:
GridPoint
)
{
self
.
contents
[
position
]
=
[]
}
// Removes all objects from the board.
func
removeAllObjects
()
{
self
.
contents
=
[
:
]
}
}
Discussion
When working with 2D grids, you usually have two main tasks that you want to perform with it:
- You have a game object on the board, and want to work out where on the board it is.
- You have a location on the board, and you want to work out what object (or objects) are at that point on the board.
This Grid
class doesn’t require that you limit the board to a predefined size.
This class implements grids by using a dictionary to map locations to arrays of objects. When you add a piece to the board, the class works out which array should contain the object (and creates one if necessary) and inserts it. Later, when you want to get the objects at a given location, it simply looks up the location in the dictionary.
Note
For small boards (for example, those with a size of about 14-by-14), you can get away with a simple implementation. However, this implementation will slow down when you start having larger boards (especially with many objects on the board). In those cases, you’d be better off creating multiple dictionaries for different areas of the board (for example, one for the upper-left corner, one for the top-right, and so on). This improves the lookup speed of getting objects at a location, though it complicates your implementation.
Get iOS Swift Game Development Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.