How To Make a Letter / Word Game with UIKit: Part 2/3
In this second part of the tutorial series, you’ll aim for developing a fully playable version of the game. When you’re finished, the user will be able to drag the tiles and drop them on the correct targets, where they will “stick” to the spot. By Marin Todorov.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make a Letter / Word Game with UIKit: Part 2/3
45 mins
Adding a Level Timer
Implementing the game timer is incredibly simple. Remember that you already load the total game time in seconds from the level config file. You haven’t used this data yet, but now you will.
Add the timer to the game controller. Go to GameController.m and add the following two private variables in the same section where you declared _targets
and _tiles
:
//stopwatch variables
int _secondsLeft;
NSTimer* _timer;
_secondsLeft
will store the number of seconds left to complete the level. It will be decreased every second by _timer
.
Now you need three new helper methods:
- One to start the timer when the tiles are displayed on the board.
- One to stop the timer in case the player solves the puzzle or the time is up.
- One to fire each second and update the HUD.
Add them one at a time to the game controller. First add the following method to initialize the new variables and start the timer:
-(void)startStopwatch
{
//initialize the timer HUD
_secondsLeft = self.level.timeToSolve;
[self.hud.stopwatch setSeconds:_secondsLeft];
//schedule a new timer
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(tick:)
userInfo:nil
repeats:YES];
}
You initialize _secondsLeft
with the initial remaining time stored in the level, and update the stopwatch label to display this time. You also schedule a new NSTimer
that will invoke tick:
each second (you haven’t added this method yet).
Add stopStopwatch
to stop the timer:
//stop the watch
-(void)stopStopwatch
{
[_timer invalidate];
_timer = nil;
}
Calling invalidate
on the timer stops it, and then you set it to nil
because it is no longer needed.
Now define tick:
, which will be called once a second while _timer
is running.
//stopwatch on tick
-(void)tick:(NSTimer*)timer
{
_secondsLeft --;
[self.hud.stopwatch setSeconds:_secondsLeft];
if (_secondsLeft==0) {
[self stopStopwatch];
}
}
It does nothing more than decrease the amount of seconds left by one, then update the stopwatch label with the new value. If the number of seconds remaining hits 0, it calls stopStopwatch
. That’s all!
The only step left is to actually start the timer when the game starts! At the end of dealRandomAnagram
, add:
//start the timer
[self startStopwatch];
All right, another feature is in! Build and run again to see the timer ticking down when the game starts:
Wait, don’t forget that you need to stop the timer if the player completes the phrase. At the end of checkForSuccess
, add the following call:
//stop the stopwatch
[self stopStopwatch];
Hmm, what else can you add to your fancy new HUD?
Keeping Score, Fair and Square
When making a game you want to have a separate class just to keep the game data for the current player session. This class will store such things as the player’s current score, remaining number of lives and progress through the game. In a more complicated game, you could have this data also sorted per user, which might require a simple database of some sort.
For this tutorial, your game will have a simple class to keep the score between games, but not between app restarts.
Create a new class in Anagrams/Classes/models named GameData
that subclasses NSObject
. Inside GameData.h, add the following property to the interface:
//store the user's game achievement
@property (assign, nonatomic) int points;
The points
property will store the player’s current score.
Inside GameData.m, add the following method:
//custom setter - keep the score positive
-(void)setPoints:(int)points
{
_points = MAX(points, 0);
}
A setPoints:
method would have been created automatically when the compiler synthesized the points
property, but you want a custom version that ensures the value is never negative. You don’t want the player to have fewer than zero points, right? So you pass points
and 0 to the MAX
macro, which returns the greater value of its two arguments. Thus, if you pass setPoints:
a negative number, it will assign 0 to _points
.
That’s all you need for your simple GameData
class. But if your game evolves, this class will also become more complicated.
OK, it’s time to decide where in the game the player will receive or lose points.
There are two point-events so far in the gameplay:
- When a tile is dropped on a correct target, award the player with points.
- When a tile is dropped on a wrong target, subtract some points from the current score.
This way, to get the most points possible out of the current anagram, the player will have to solve it without making any mistakes.
Challenge: You can also award points upon puzzle completion, and subtract points when the player fails to complete a puzzle in time. How would you implement that yourself?
Challenge: You can also award points upon puzzle completion, and subtract points when the player fails to complete a puzzle in time. How would you implement that yourself?
Hook the score-keeping into the game controller logic by adding the following import and property inside GameController.h:
// with the other imports
#import "GameData.h"
// with the other properties
@property (strong, nonatomic) GameData* data;
This gives you access to a GameData
object to store the score.
Now switch to GameController.m. You need to initialize the self.data
property. To do that, add the following custom init
:
//initialize the game controller
-(instancetype)init
{
self = [super init];
if (self != nil) {
//initialize
self.data = [[GameData alloc] init];
}
return self;
}
For now, this just initializes the GameController
and assigns a new GameData
object to its data
property. You will add more here in Part 3 of this tutorial.
In the same file, add the following code inside tileView:didDragToPoint:
, just below the comment that reads “//more stuff to do on success here”:
//give points
self.data.points += self.level.pointsPerTile;
Luckily the level config file already includes the points per title for every level of difficulty, so all you do here is increment the current score by that number.
To handle errors, add the following code in the same method, just below the comment that reads “//more stuff to do on failure here”:
//take out points
self.data.points -= self.level.pointsPerTile/2;
Here, you do the opposite and subtract points. But there’s no point (ha, ha) in being too tough on the player – if they drop the tile on the wrong target, only half of what they receive for a matching one is subtracted from the current score.
All right! Keeping score – check.
Note: The GameData
class is quite naïve as it is. Your own UIKit game will likely have some other game data you need to track, requiring you to adjust the class as you wish. A few possible features deserve an honorable mention:
- You might want to add Game Center integration to your game, which will forward the score from
GameData
to Apple’s Game Center. More on Game Center. - You might want to persist the score by saving it to the
NSUserDefaults
or a keychain. More on secure storage of data with a keychain. - If you would like to store the game data to a keychain or persist it to a file, you might find using the JSONModel library super useful. It allows you to convert your model data to a JSON string, which is easily storable in a keychain or a text file.
Note: The GameData
class is quite naïve as it is. Your own UIKit game will likely have some other game data you need to track, requiring you to adjust the class as you wish. A few possible features deserve an honorable mention:
- You might want to add Game Center integration to your game, which will forward the score from
GameData
to Apple’s Game Center. More on Game Center. - You might want to persist the score by saving it to the
NSUserDefaults
or a keychain. More on secure storage of data with a keychain. - If you would like to store the game data to a keychain or persist it to a file, you might find using the JSONModel library super useful. It allows you to convert your model data to a JSON string, which is easily storable in a keychain or a text file.