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
Problem
You want to determine the best way to lay out the architecture of your game.
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
:
@interface
GameObject
:NSObject
{
}
-
(
void
)
update
:
(
float
)
deltaTime
;
@end
@implementation
GameObject
-
(
void
)
update:
(
float
)
deltaTime
{
// Do some updating
}
@end
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:
@interface
Monster
:GameObject
{
}
@property
(
assign
)
float
hitPoints
;
// num of times it can be hit without dying
@property
(
weak
)
GameObject
*
targetObject
;
// try to kill this object
@end
@implementation
Monster
-
(
void
)
update:
(
float
)
deltaTime
{
[
super
update
:
deltaTime
];
// Do some monster-specific updating
}
@end
Discussion
In an inheritance-based layout, as seen 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:
#import "Component.h"
@interface
GameObject
:NSObject
@property
(
strong
)
NSSet
*
components
;
-
(
void
)
addComponent:
(
Component
*
)
component
;
-
(
void
)
removeComponent:
(
Component
*
)
component
;
-
(
void
)
update:
(
float
)
deltaTime
;
-
(
id
)
componentWithType:
(
Class
)
componentType
;
-
(
NSArray
*
)
componentsWithType:
(
Class
)
componentType
;
@end
The implementation for this class looks like this:
#import "GameObject.h"
@implementation
GameObject
{
NSMutableSet
*
_components
;
}
@synthesize
components
=
_components
;
-
(
id
)
init
{
self
=
[
super
init
];
if
(
self
)
{
_components
=
[
NSMutableSet
set
];
}
return
self
;
}
-
(
void
)
addComponent:
(
Component
*
)
component
{
[
_components
addObject
:
component
];
component
.
gameObject
=
self
;
}
-
(
void
)
removeComponent:
(
Component
*
)
component
{
[
_components
removeObject
:
component
];
component
.
gameObject
=
nil
;
}
-
(
void
)
update:
(
float
)
deltaTime
{
for
(
Component
*
component
in
_components
)
{
[
component
update
:
deltaTime
];
}
}
-
(
id
)
componentWithType:
(
Class
)
componentType
{
// Helper function that just returns the first component with a given type
return
[[
self
componentsWithType
:
componentType
]
firstObject
];
}
-
(
NSArray
*
)
componentsWithType:
(
Class
)
componentType
{
// Return nil if the class isn't actually a type of component
if
([
componentType
isSubclassOfClass
:
[
Component
class
]]
==
NO
)
return
nil
;
// Work out which components match the component type, and return them all
return
[[
_components
objectsPassingTest
:^
BOOL
(
id
obj
,
BOOL
*
stop
)
{
return
[
obj
isKindOfClass
:
componentType
];
}]
allObjects
];
}
@end
Using these objects looks like this:
// Make a new game object
GameObject
gameObject
=
[[
GameObject
alloc
]
init
];
// Add some components
Component
*
component
=
[[
Component
alloc
]
init
];
[
gameObject
addComponent
:
component
];
// When the game needs to update, send all game objects the "update" message
// This makes all components get updated as well
[
gameObject
update
];
Discussion
In a component-based architecture, as seen 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. In this recipe, we’re using an NSMutableSet
, which means that if you add the same component more than once, the list will only contain one copy of the component. 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 each one 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.
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:
@interface
MyTimeKeepingObject
{
double
lastFrameTime
;
}
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:
-
(
void
)
update:
(
double
)
currentTime
{
double
deltaTime
=
currentTime
-
lastFrameTime
;
// Move at 3 units per second
float
movementSpeed
=
3
;
[
someObject
moveAtSpeed
:
movementSpeed
*
deltaTime
];
lastFrameTime
=
currentTime
;
}
Discussion
“Delta time” means “change in time.” Delta times are useful to keep 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:
double
currentTime
=
[
NSDate
timeIntervalSinceReferenceDate
];
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
:
-
(
void
)
viewDidAppear:
(
BOOL
)
animated
{
// Called when the application becomes the active one
// (i.e., when nothing's covering it up)
[[
NSNotificationCenter
defaultCenter
]
addObserver:
self
selector
:
@selector
(
applicationDidBecomeActive
:
)
name:
UIApplicationDidBecomeActiveNotification
object
:
nil
];
// Called when the application will enter the foreground (i.e.,
// when the user has left another app and entered this one)
[[
NSNotificationCenter
defaultCenter
]
addObserver:
self
selector
:
@selector
(
applicationWillEnterForeground
:
)
name:
UIApplicationWillEnterForegroundNotification
object
:
nil
];
// Called when the application will resign active (i.e., when something's
// covering it up, like the notification tray or a phone call)
[[
NSNotificationCenter
defaultCenter
]
addObserver:
self
selector
:
@selector
(
applicationWillResignActive
:
)
name:
UIApplicationWillResignActiveNotification
object
:
nil
];
// Called when the application enters the background
// (i.e., when the user has left it)
[[
NSNotificationCenter
defaultCenter
]
addObserver:
self
selector
:
@selector
(
applicationDidEnterBackground
:
)
name:
UIApplicationDidEnterBackgroundNotification
object
:
nil
];
}
-
(
void
)
viewDidDisappear:
(
BOOL
)
animated
{
[[
NSNotificationCenter
defaultCenter
]
removeObserver
:
self
];
}
-
(
void
)
applicationDidBecomeActive:
(
NSNotification
*
)
notification
{
NSLog
(
@"Application did become active!"
);
}
-
(
void
)
applicationWillResignActive:
(
NSNotification
*
)
notification
{
NSLog
(
@"Application will resign active!"
);
}
-
(
void
)
applicationDidEnterBackground:
(
NSNotification
*
)
notification
{
NSLog
(
@"Application did enter background!"
);
}
-
(
void
)
applicationWillEnterForeground:
(
NSNotification
*
)
notification
{
NSLog
(
@"Application will enter foreground!"
);
}
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 or not.
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:
NSTimer
*
timer
;
Next, add a method that takes an NSTimer
parameter:
-
(
void
)
updateWithTimer:
(
NSTimer
*
)
timer
{
// The timer's gone off; update the game
NSLog
(
@"Updated from timer!"
);
}
Finally, when you want to start the timer:
timer
=
[
NSTimer
scheduledTimerWithTimeInterval
:
0.5
target:
self
selector
:
@selector
(
updateWithTimer
:
)
userInfo
:
nil
repeats
:
YES
];
To stop the timer:
[
timer
invalidate
];
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:
// Wait 2 seconds
[
NSTimer
scheduledTimerWithTimeInterval
:
2
target:
self
selector
:
@selector
(
updateWithTimer
:
)
userInfo
:
nil
repeats
:
YES
];
// Wait 0.1 seconds (100 milliseconds)
[
NSTimer
scheduledTimerWithTimeInterval
:
0.1
target:
self
selector
:
@selector
(
updateWithTimer
:
)
userInfo
:
nil
repeats
:
YES
];
You can also make a timer either fire only once or repeat forever, by changing the repeats
parameter to NO
or YES
, respectively.
Updating Based on When the Screen Updates
Solution
Use a CADisplayLink
, which sends a message every time the screen is redrawn.
First, add an instance variable to your view controller:
@interface
ViewController
()
{
CADisplayLink
*
displayLink
;
}
Next, add a method that takes a single parameter (a CADisplayLink
):
-
(
void
)
update:
(
CADisplayLink
*
)
displayLink
{
// The screen's just updated; update the game
NSLog
(
@"Updated from display link!"
);
}
Finally, add this code when you want to begin receiving updates:
// Create the CADisplayLink; "self" will receive the updateWithDisplayLink:
// message every time the screen updates
displayLink
=
[
CADisplayLink
displayLinkWithTarget:
self
selector
:
@selector
(
update
:
)];
// Add the display link and start receiving updates
[
displayLink
addToRunLoop
:
[
NSRunLoop
mainRunLoop
]
forMode
:
NSRunLoopCommonModes
];
When you want to pause receiving updates, set the paused
property of the CADisplayLink
to YES
:
displayLink
.
paused
=
YES
;
When you want to stop receiving updates, call invalidate
on the CADisplayLink
:
[
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/60th 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/60th of a second—anything moving faster than 25 frames per second (in other words, one update every 1/25th 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:
// This is just pseudocode--your game will likely look slightly different
for
(
GameObject
*
gameObject
in
gameObjects
)
{
if
(
paused
==
NO
||
gameObject
.
shouldPause
==
NO
)
{
[
gameObject
update
];
}
}
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 YES
, 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:
// In your class's @interface:
@property
(
strong
)
NSDate
*
gameStartDate
;
// When the game starts:
self
.
gameStartDate
=
[
NSDate
date
];
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:
NSDate
*
now
=
[
NSDate
date
];
NSTimeInterval
timeSinceGameStart
=
[
self
.
gameStartDate
timeIntervalSinceDate
:
self
.
gameStartDate
];
NSLog
(
@"The game started %.2f seconds ago"
,
timeSinceGameStart
);
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:
NSTimeInterval
timeElapsed
=
...
// an NSTimeInterval from somewhere
float
minutes
=
timeElapsed
/
60.0
;
// 60 seconds per minute
float
hours
=
timeElapsed
/
3600.0
;
// 3600 seconds per hour
float
seconds
=
fmodf
(
timeElapsed
,
60.0
);
// get the remainder
NSLog
(
@"Time elapsed:%.0f:%.0f:%.2f"
,
hours
,
minutes
,
seconds
);
Working with Blocks
Solution
Blocks are ideal for this:
void
(
^
onCollision
)(
void
);
onCollision
=
^
(
void
)
{
NSLog
(
@"Character collided with something!"
);
};
// Later, when a collision happens:
onCollision
();
Discussion
Blocks are a language feature in Objective-C 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 block:
void
(
^
MyBlock
)(
void
);
MyBlock
=
^
(
void
)
{
NSLog
(
@"Hello from the block!"
);
};
MyBlock
();
This is how you define a block. In this case, the block returns
void
, is namedMyBlock
, and accepts no parameters.Just like any other variable, once a block is defined, it needs to be given a value. In this case, we’re providing a block that takes no parameters and returns nothing, just like the block variable’s definition.
Calling a block works just like calling any other function.
How blocks work
So far, this just seems like a very roundabout way to call a function. However, the real power of blocks comes from two facts:
- Blocks capture the state of any other variables their code references.
- Blocks are objects, and they stay around until you need them. If you store a block, you can call it however often you like.
Let’s talk about the first point. Say you had a block like this:
int
i
=
1
;
void
(
^
MyBlock
)(
void
)
=
^
(
void
)
{
NSLog
(
@"i = %i"
,
i
);
};
MyBlock
();
As you’d expect, running this code would print i = 1
to the console. But watch what happens when you change the value of i
after creating the block, like this:
int
i
=
1
;
void
(
^
MyBlock
)(
void
)
=
^
(
void
)
{
NSLog
(
@"i = %i"
,
i
);
};
i
=
5
;
MyBlock
();
This code will print the following to the console:
i
=
1
That’s right—running this code produces the same result as the first version. Even though the i
variable has been modified, the console will still print i = 1
. This is because i
had the value of 1 at the moment the block was created—the fact that i
was later changed to 5 doesn’t affect the block’s copy of that variable.
This is extremely powerful, because it means that your game doesn’t need to carefully store values for later use; if a block needs a value, it automatically keeps it.
The syntax for creating blocks is a little messy, with carets (^) and parentheses all over the place. An easier way to do it is to define a block type, which is just a simple type definition of a block. For example, here’s a block type for the preceding examples:
typedef
void
(
^
ExampleBlock
)(
void
);
This allows you to declare variables with nicer syntax, and with significantly fewer parentheses and carets:
ExampleBlock
myBlock
=
^
(
void
)
{
NSLog
(
@"i SPILL my DRINK!"
);
};
So far, we’ve talked entirely about blocks that don’t have parameters or return types. These are easy to define, though. For example, here’s a block that returns a BOOL
and takes two parameters, one NSString
and one int
:
typedef
BOOL
(
^
ParameterBlock
)(
NSString
*
string
,
int
number
);
The syntax to create this block is very similar to the earlier examples:
ParameterBlock
paramBlock
=
^
(
NSString
*
string
,
int
number
)
{
NSLog
(
@"I received a string %@, and a number %i!"
,
string
,
number
);
return
YES
;
};
paramBlock
(
@"Hello"
,
1337
);
If your block doesn’t take any parameters, you can actually skip the parameter list entirely:
typedef
void
(
^
ExampleBlock
)(
void
);
[...]
ExampleBlock
aBlock
=
^
{
NSLog
(
@"Whoa!"
);
};
Blocks and other objects
When a block is created, the compiler looks at all of the variables that the block 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 an Objective-C object, it can’t be copied, because it could potentially be very large. Instead, the object is retained by the block. When a block is freed, any objects retained by the block are released.
This means that if you have a block that references another object, that block will keep the other object around.
For example, say you have code that looks like this:
NSString
*
aString
=
[
NSString
stringWithFormat
:
@"One = %i"
,
1
];
void
(
^
MyBlock
)(
void
)
=
^
(
void
)
{
NSLog
(
@"The string is %@"
,
aString
);
};
aString
=
nil
;
// aString is still in memory, because MyBlock is keeping it around!
The block MyBlock
, because it references the aString
object, will maintain an owning reference to aString
. This means that even if all other references to aString
go away, the string is kept in memory, and it will only be released when MyBlock
goes away. (This example only makes sense if you’re using automatic reference counting, or ARC—which you should be.)
This is usually what you want, since it would be annoying to have to remember to keep the variables referenced by blocks in memory. However, sometimes that’s not what you want.
One example is when you want a block to run in two seconds’ time that causes an enemy object to run an attack animation. However, between the time you schedule the block and the time the block runs, the enemy is removed from the game. If the block has a strong reference to the enemy, the enemy isn’t actually removed from memory until the block 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
.
You create a weak reference by prepending the keyword __weak
to a variable declaration, like so:
__weak
NSString
*
weakString
=
aString
;
This makes weakString
into a weak reference to aString
. Now, when aString
is removed from memory, the weakString
reference will automatically point to nil
instead.
Using weak references, the previous example code looks like this:
NSString
*
aString
=
[
NSString
stringWithFormat
:
@"One = %i"
,
1
];
__weak
NSString
*
weakString
=
aString
;
void
(
^
MyBlock
)(
void
)
=
^
(
void
)
{
NSLog
(
@"The string is %@"
,
weakString
);
};
aString
=
nil
;
// aString is no longer in memory, and calling MyBlock will print
// "The string is (null)"
Writing a Method That Calls a Block
Problem
You want to write a method that, after performing its work, calls a block 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 block when the character finishes moving.
Solution
To create a method that takes a block as a parameter, you just do this:
-
(
void
)
moveToPosition:
(
CGPoint
)
position
completionBlock:
(
void
(
^
)(
void
))
completion
{
// Do the actual work, which might take place over several frames
[...]
// Call the completion block
if
(
completion
!=
nil
)
{
completion
();
}
}
And to pass the block to the method, you do this:
CGPoint
destination
=
...
[
someObject
moveToPosition
:
destination
completionBlock
:^
{
NSLog
(
@"Finished moving!"
);
}];
Discussion
Methods that take a block 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 blocks were added to the Objective-C 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 blocks, you just use the variables without any additional work).
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 blocks to be run in the background without interfering with more time-critical tasks like rendering or accepting user input:
NSOperationQueue
*
concurrentQueue
=
[[
NSOperationQueue
alloc
]
init
];
concurrentQueue
.
maxConcurrentOperationCount
=
10
;
[
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:
NSOperationQueue
*
mainQueue
=
[
NSOperationQueue
mainQueue
];
[
mainQueue
addOperationWithBlock
:^
{
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:
NSOperationQueue
*
backgroundQueue
=
[[
NSOperationQueue
alloc
]
init
];
[
backgroundQueue
addOperationWithBlock
:^
{
[[
NSOperationQueue
mainQueue
]
addOperationWithBlock
:^
{
NSLog
(
@"This is run 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 a number of 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:
NSOperationQueue
*
aQueue
=
[[
NSOperationQueue
alloc
]
init
];
aQueue
.
maxConcurrentOperationCount
=
2
;
Performing a Task in the Future
Solution
Use dispatch_after
to schedule a block of code to run in the future:
// Place a bomb, but make it explode in 10 seconds
PlaceBomb
();
double
timeToWait
=
10.0
;
dispatch_time_t
delayTime
=
dispatch_time
(
DISPATCH_TIME_NOW
,
(
int64_t
)(
timeToWait
*
NSEC_PER_SEC
));
dispatch_queue_t
queue
=
dispatch_get_main_queue
();
dispatch_after
(
delayTime
,
queue
,
^
(
void
){
// 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 blocks onto a queue, which runs the blocks. 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 block on an operation queue at a given time. To use the function, you first need to figure out the time when the block 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.
Therefore, to get a dispatch_time_t
that represents 1 second in the future, you would use this:
double
timeToWait
=
1.0
;
dispatch_time_t
delayTime
=
dispatch_time
(
DISPATCH_TIME_NOW
,
(
int64_t
)(
timeToWait
*
NSEC_PER_SEC
));
Once you have a time for the block to run at, you need to get a reference to a GCD dispatch_queue
. You can create your own, but you generally only want the main queue, which you can get using dispatch_get_main_queue
:
dispatch_queue_t
queue
=
dispatch_get_main_queue
();
Finally, you instruct GCD to run the block:
dispatch_after
(
delayTime
,
queue
,
^
(
void
){
NSLog
(
@"Delayed block"
);
});
Storing Blocks in Objects
Solution
Blocks can be stored as properties in objects. For example:
typedef
void
(
^
CallbackBlock
)(
void
);
@interface
Monster
:NSObject
@property
(
copy
)
CallbackBlock
onDeathBlock
;
@property
(
copy
)
CallbackBlock
onHitBlock
;
@end
[...]
// In the Monster class:
-
(
void
)
die
{
self
.
onDeathBlock
();
}
-
(
void
)
hit
{
self
.
onHitBlock
();
}
[...]
Monster
*
monster
=
...
monster
.
onDeathBlock
=
^
{
NSLog
(
@"Monster died!"
);
};
monster
.
onHitBlock
=
^
{
NSLog
(
@"Monster hit something"
)
!
};
Note that the property settings for the aBlock
copy in this example indicate that the block should be copied. This is important—if you don’t do this, then your application will crash when you try to call it.
The reason for this is that when a block is created, it’s created on the stack. When the function that it’s created in returns, the block is destroyed.
Copying a block moves the block from the stack to the heap, which means that the block stays around after the function that created it returns. It also means that the block needs to be explicitly freed (though ARC handles this for you, either when you set the property that contains the block to nil
or when the object is freed).
Discussion
A block stored as a property can be accessed just like any other variable, and called like all blocks are:
-
(
void
)
doSomething
{
self
.
aBlock
();
}
Warning
If you call a block that’s set to nil
, your app will crash. For example:
ExampleBlock
aBlock
=
nil
;
aBlock
();
// CRASH!
You need to do explicit checking if you want to be safe:
if
(
aBlock
!=
nil
)
{
aBlock
();
}
When you store a block as a property of an object, it’s important to remember that blocks can retain objects. If an object is retaining a block, and that block is retaining the object as well, you have a retain cycle.
Say you have a block that looks like this:
void
(
^
MyBlock
)(
void
)
=
^
(
void
)
{
NSLog
(
@"I am %@"
,
self
);
// block now retains self
};
self
.
aBlock
=
MyBlock
;
// self now retains block; retain cycle!
Note how MyBlock
references the self
object. The compiler notices this, and will make the block retain the self
object in memory (because in order for the block to function, it needs to be able to use that variable, which means that the object that that variable points to needs to be kept around). See Figure 1-3.
However, if you store that block in a property in self
, the block and the object will be retaining each other. This prevents the object from being removed from memory when all other strong references to it have gone away, as shown in Figure 1-4.
To prevent this from happening, you should always use weak references to self
by declaring the variable with the __weak
keyword:
__weak
id
weakSelf
=
self
;
void
(
^
MyBlock
)(
void
)
=
^
(
void
)
{
NSLog
(
@"I am %@"
,
weakSelf
);
// block does NOT retain self
};
self
.
aBlock
=
MyBlock
;
// self now retains block; no retain cycle
Using a Timer
Solution
First, create an instance variable to store the timer:
@interface
ViewController
()
{
dispatch_source_t
timer
;
}
@end
Then, create the timer like so:
// Get the queue to run the blocks on
dispatch_queue_t
queue
=
dispatch_get_main_queue
();
// Create a dispatch source, and make it into a timer that goes off every second
timer
=
dispatch_source_create
(
DISPATCH_SOURCE_TYPE_TIMER
,
0
,
0
,
queue
);
dispatch_source_set_timer
(
timer
,
DISPATCH_TIME_NOW
,
1
*
NSEC_PER_SEC
,
0
);
// When the timer goes off, heal the player
dispatch_source_set_event_handler
(
timer
,
^
{
GivePlayerHitPoints
();
});
// Dispatch sources start out paused, so start the timer by resuming it
dispatch_resume
(
timer
);
To cancel the timer, just set the timer
variable to nil
:
timer
=
nil
;
Discussion
In addition to dispatch queues, GCD has a concept called a dispatch source, which is something that triggers work to be done. Dispatch sources can be created for things like file I/O where you can run a block when data is available to be read, and timers, where you run a block at a fixed interval.
To create a dispatch timer, you first create a dispatch_source_t
, and then configure it to be a timer. As part of setting it up, you also give it a dispatch_queue_t
for it to run its code on:
dispatch_queue_t
queue
=
dispatch_get_main_queue
();
timer
=
dispatch_source_create
(
DISPATCH_SOURCE_TYPE_TIMER
,
0
,
0
,
queue
);
The next step is to tell the timer how often it should be fired. This is defined in nanoseconds:
dispatch_source_set_timer
(
timer
,
DISPATCH_TIME_NOW
,
1
*
NSEC_PER_SEC
,
0
);
Finally, you provide the timer with the block that should be run every time the timer goes off. Dispatch sources start out paused, so you also need to unpause the timer by calling dispatch_resume
:
dispatch_source_set_event_handler
(
timer
,
^
{
GivePlayerHitPoints
();
});
dispatch_resume
(
timer
);
When you want to stop the timer, you just set it to nil
. ARC knows about GCD objects, so setting it to nil
will cause it to be cleaned up properly.
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:
NSBlockOperation
*
firstOperation
=
[
NSBlockOperation
blockOperationWithBlock
:^
{
NSLog
(
@"First operation"
);
}];
NSBlockOperation
*
secondOperation
=
[
NSBlockOperation
blockOperationWithBlock
:^
{
NSLog
(
@"Second operation (depends on third operation and first operation)"
);
}];
NSBlockOperation
*
thirdOperation
=
[
NSBlockOperation
blockOperationWithBlock
:^
{
NSLog
(
@"Third operation"
);
}];
[
secondOperation
addDependency
:
thirdOperation
];
[
secondOperation
addDependency
:
firstOperation
];
[[
NSOperationQueue
mainQueue
]
addOperations
:
@
[
firstOperation
,
secondOperation
,
thirdOperation
]
waitUntilFinished
:
NO
];
Discussion
You can add an operation to another operation as a dependency. This is useful for cases where you want one block 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 Blocks
Solution
Use the filteredArrayUsingPredicate
method to create an array that only contains objects that meet certain conditions:
NSArray
*
array
=
@
[
@"One"
,
@"Two"
,
@"Three"
,
@"Four"
,
@"Five"
];
NSLog
(
@"Original array: %@"
,
array
);
array
=
[
array
filteredArrayUsingPredicate
:
[
NSPredicate
predicateWithBlock
:^
BOOL
(
id
evaluatedObject
,
NSDictionary
*
bindings
)
{
NSString
*
string
=
evaluatedObject
;
// Search for an "e" in the string
if
([
string
rangeOfString
:
@"e"
].
location
!=
NSNotFound
)
return
YES
;
else
return
NO
;
}]];
NSLog
(
@"Filtered array: %@"
,
array
);
Discussion
Arrays can be filtered using the filteredArrayUsingPredicate:
method. This method uses an NSPredicate
to test each object in the array, and returns a new array; if an object passes the test, it’s included in the new array.
NSPredicate
objects can be set up to run their tests in a variety of ways. One of them is by providing a block, which receives the object to be tested and returns a BOOL
value: YES
means it’s included, and NO
means it’s not.
Note that using filteredArrayUsingPredicate:
doesn’t modify the existing array, but rather creates a new one.
Loading New Assets During Gameplay
Solution
For each resource that needs loading, run a block that does the loading into memory, and run it in the background. Then run a subsequent block when all of the blocks have completed.
You can do this through dispatch groups, which are a way to submit multiple units of work to Grand Central Dispatch:
NSArray
*
imagesToLoad
=
[
NSArray
array
];
dispatch_group_t
group
=
dispatch_group_create
();
dispatch_queue_t
backgroundQueue
=
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_BACKGROUND
,
0
);
for
(
NSString
*
imageFileName
in
imagesToLoad
)
{
dispatch_group_async
(
group
,
backgroundQueue
,
^
{
// Load the file
});
}
dispatch_queue_t
mainQueue
=
dispatch_get_main_queue
();
dispatch_group_notify
(
group
,
mainQueue
,
^
{
// All images are done loading at this point
});
Discussion
Using a dispatch group will make GCD spin up as many threads as it needs to load all of the resources.
You can control how much CPU time will be allocated to the loading task by changing the priority of the background queue. Here are the available priorities:
-
DISPATCH_QUEUE_PRIORITY_HIGH
- Blocks on this queue will be scheduled to run before those on all other lower-priority queues.
-
DISPATCH_QUEUE_PRIORITY_DEFAULT
- Blocks on this queue will be scheduled to run after those on high-priority queues, but before blocks on low-priority queues.
-
DISPATCH_QUEUE_PRIORITY_LOW
- Blocks on this queue will be scheduled to run after those on all other higher-priority queues.
-
DISPATCH_QUEUE_PRIORITY_BACKGROUND
-
The same as
PRIORITY_LOW
, but the system will dedicate even fewer resources to it.
When you create a dispatch group with dispatch_group_create
, you add one or more blocks to it. You can also add a notification block, which is run immediately after all blocks in the dispatch group have finished running. It’s generally good practice to put the notification block on the main queue, so that it’s easier to coordinate with other parts of your game’s engine.
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.m.
When you want to add a test, open your test file (the .m file) and add a method whose name begins with test
:
-
(
void
)
testDoingSomethingCool
{
SomeAwesomeObject
*
object
=
[[
SomeAwesomeObject
alloc
]
init
];
BOOL
succeeded
=
[
object
doSomethingCool
];
if
(
succeeded
==
NO
)
{
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 Objective-C 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, then 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 that the AI needs to operate in). 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 and type XCTestAssertions
then press Enter).
2D Grids
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.h:
#import <Foundation/Foundation.h>
// Grid points are just integers defining the location of an object
typedef
struct
{
int
x
;
int
y
;
}
GridPoint
;
enum
{
GridPointNotValid
=
INT_MAX
};
@interface
Grid
:NSObject
// Returns the list of objects that occupy the given position
-
(
NSArray
*
)
objectsAtPosition:
(
GridPoint
)
position
;
// Returns a GridPoint describing the position of an object
-
(
GridPoint
)
positionForObject:
(
id
)
objectToFind
;
// Adds or move the object to a location on the board
-
(
void
)
addObject:
(
id
)
object
atPosition:
(
GridPoint
)
position
;
// Removes a given object from the board
-
(
void
)
removeObjectFromGrid:
(
id
)
object
;
// Removes all objects at a given point from the board
-
(
void
)
removeAllObjectsAtPosition:
(
GridPoint
)
position
;
// Removes all objects from the board.
-
(
void
)
removeAllObjects
;
@end
Then, put the following code in Grid.m:
#import "Grid.h"
@implementation
Grid
{
NSMutableDictionary
*
_grid
;
}
-
(
id
)
init
{
if
((
self
=
[
super
init
]))
{
// Create the dictionary that maps all objects to locations
_grid
=
[[
NSMutableDictionary
alloc
]
init
];
}
return
self
;
}
// Returns an NSString given a point. For example, the location (1,2)
// is turned into "1-2".
-
(
NSString
*
)
keyForGridPoint
:
(
GridPoint
)
position
{
return
[
NSString
stringWithFormat
:
@"%i-%i"
,
position
.
x
,
position
.
y
];
}
// Returns a GridPoint given a key. The string "1-2" is turned into (1,2).
-
(
GridPoint
)
gridPointForKey
:
(
NSString
*
)
key
{
// Split the string into two
NSArray
*
components
=
[
key
componentsSeparatedByString
:
@"-"
];
// Check to see if there are exactly two components; if not, it's not
// a valid grid point and so return (GridPointNotValid,GridPointNotValid).
if
(
components
.
count
!=
2
)
return
(
GridPoint
){
GridPointNotValid
,
GridPointNotValid
};
// Construct and return the grid point
return
(
GridPoint
){[
components
[
0
]
intValue
],
[
components
[
1
]
intValue
]};
}
// Returns the array containing all objects at a given position
-
(
NSArray
*
)
objectsAtPosition
:
(
GridPoint
)
position
{
NSString
*
key
=
[
self
keyForGridPoint
:
position
];
return
[
_grid
objectForKey
:
key
];
}
// Returns the array containing a specific object
-
(
NSMutableArray
*
)
arrayContainingObject
:
(
id
)
object
{
for
(
NSMutableArray
*
array
in
_grid
)
{
if
([
array
containsObject
:
object
])
return
array
;
}
return
nil
;
}
// Returns the GridPoint for an object on the board
-
(
GridPoint
)
positionForObject
:
(
id
)
objectToFind
{
// Find the array containing this object
NSArray
*
arrayWithObject
=
[
self
arrayContainingObject
:
objectToFind
];
// Find the key associated with this array
NSString
*
key
=
[[
_grid
allKeysForObject
:
arrayWithObject
]
lastObject
];
// Convert the key into a grid point and return it
return
[
self
gridPointForKey
:
key
];
}
// Adds an object to the board. It's moved from its previous location
// if necessary.
-
(
void
)
addObject
:
(
id
)
object
atPosition
:
(
GridPoint
)
position
{
// First, remove the object from its current location
[
self
removeObjectFromGrid
:
object
];
// Next, work out which array it should go in. If no suitable array exists,
// create a new one.
NSString
*
key
=
[
self
keyForGridPoint
:
position
];
NSMutableArray
*
foundArray
=
[
_grid
objectForKey
:
key
];
if
(
foundArray
==
nil
)
{
foundArray
=
[
NSMutableArray
array
];
[
_grid
setObject
:
foundArray
forKey
:
key
];
}
// Finally, add the object if it doesn't already exist.
if
([
foundArray
containsObject
:
object
]
==
NO
)
{
[
foundArray
addObject
:
object
];
}
}
// Removes an object from the board.
-
(
void
)
removeObjectFromGrid
:
(
id
)
object
{
NSMutableArray
*
arrayContainingObject
=
[
self
arrayContainingObject
:
object
];
[
arrayContainingObject
removeObject
:
object
];
}
// Removes all objects from a position on the board.
-
(
void
)
removeAllObjectsAtPosition
:
(
GridPoint
)
position
{
[
_grid
removeObjectForKey
:
[
self
keyForGridPoint
:
position
]];
}
// Removes all objects.
-
(
void
)
removeAllObjects
{
// Blow away the existing dictionary and replace it with an empty one.
_grid
=
[
NSMutableDictionary
dictionary
];
}
@end
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 pre-defined size. Any location works, as long the number doesn’t exceeed the integer limit (that is, −2,147,483,648 to 2,147,483,647).
This class implements grids by using an NSDictionary
to map locations to mutable 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×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 Game Development Cookbook 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.