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 the Score to the HUD
Games are all about achievements and score, so there’s no sense in keeping the player’s score behind the scenes. You need to put it upfront, right on the HUD layer.
It will be a bit boring, though, if the score label were to instantly update from, say, 500 to 750. It’s a lot more fun to have the score label rapidly cycle through 500, 501, 502, etc all the way up to 700 – it just a little polish to provide an awesome game experience.
In Anagrams/Classes/views, create a new Objective-C file for the class CounterLabelView
and make it a subclass of UILabel
. Switch to CounterLabelView.h and add the following code to the interface:
@property (assign, nonatomic) int value;
+(instancetype)labelWithFont:(UIFont*)font frame:(CGRect)r andValue:(int)v;
-(void)countTo:(int)to withDuration:(float)t;
Here’s what you need to know about the above:
-
value
is a property that will hold the score currently shown on the label. -
labelWithFont:frame:andValue:
is a class method that will return a newCounterLabelView
initialized with the given frame, font and value. -
countTo:withDuration:
will animate the label’s text, counting up or down to/from the current value to the one provided. The animation will have the given duration in seconds.
Now open up CounterLabelView.m. You can safely delete the pre-defined methods – you won’t need them. Then add two private variables to the class, like so:
@implementation CounterLabelView
{
int endValue;
double delta;
}
Basically, each delta
time (in seconds), you’ll increment (or decrement) the label one point, until you reach endValue
.
Start with the easier stuff and add the factory method, which is pretty straightforward:
//create an instance of the counter label
+(instancetype)labelWithFont:(UIFont*)font frame:(CGRect)r andValue:(int)v
{
CounterLabelView* label = [[CounterLabelView alloc] initWithFrame:r];
if (label!=nil) {
//initialization
label.backgroundColor = [UIColor clearColor];
label.font = font;
label.value = v;
}
return label;
}
In the above code, you create a new instance of CounterLabelView
with the given frame, then you set its background to transparent and its font
and value
properties to the given font and value.
Another easy task is to overwrite the value
setter so that it will update both the property and the text being displayed by the label. Add the following method:
//update the label's text
-(void)setValue:(int)value
{
_value = value;
self.text = [NSString stringWithFormat:@" %i", self.value];
}
Now add the following helper method:
//increment/decrement method
-(void)updateValueBy:(NSNumber*)valueDelta
{
//1 update the property
self.value += [valueDelta intValue];
//2 check for reaching the end value
if ([valueDelta intValue] > 0) {
if (self.value > endValue) {
self.value = endValue;
return;
}
} else {
if (self.value < endValue) {
self.value = endValue;
return;
}
}
//3 if not - do it again
[self performSelector:@selector(updateValueBy:) withObject:valueDelta afterDelay:delta];
}
There are three things happening in this method:
- You update the
self.value
property by thevalueDelta
that was passed to the method, which will be either 1 or -1. - You check whether you are counting up or down, and then perform the appropriate check to see if you've reached the final value. If counting up, you check to see if the current value is greater than
endValue
; if counting down, you check to see if it is less thanendValue
. If you reach the final value, you just bail out of the method. - If you reach this stage, it means the
endValue
was not yet reached. Here you schedule another execution of this same method afterdelta
seconds.
updateValueBy:
will keep calling itself and updating the label's text until it reaches the value stored in endValue
.
Now it's time to implement countTo:withDuration:
, which is the method that initially calls updateValueBy:
to get the counter rolling. Add the following method:
//count to a given value
-(void)countTo:(int)to withDuration:(float)t
{
//1 detect the time for the animation
delta = t/(abs(to-self.value)+1);
if (delta < 0.05) delta = 0.05;
//2 set the end value
endValue = to;
//3 cancel previous scheduled actions
[NSObject cancelPreviousPerformRequestsWithTarget:self];
//4 detect which way counting goes
if (to-self.value>0) {
//count up
[self updateValueBy: @1];
} else {
//count down
[self updateValueBy: @-1];
}
}
This method is the most complicated one you've added thus far, so let's go over it carefully.
- Here you calculate the
delta
time between calls toupdateValueBy:
. To do so, you do the following:- You find how many values the label needs to count through by calling
abs(to-self.value)
. For example, if the current score is 50 and you want it to go to 65, you'll get the value 15. If the current value is 65 and you want to go to 50, you'll get the same value of 15. - You add 1 to the above value just in case it turned out to be zero. You do this because in the next step you'll be dividing by this value, and dividing by zero is a no-no.
- You divide the total time for the animation,
t
, by the number of values you need to count through. This gives you a value fordelta
. - Finally, if
delta
is less than 0.05 seconds, you increase it to 0.05. This just keeps the animation from moving too quickly, which wouldn't look as nice.
- You find how many values the label needs to count through by calling
- You set the
endValue
you are counting towards. - You cancel any scheduled operations for the class, because you will schedule some others in a moment and don't want them overlapping.
- Finally, you have an
if
statement that callsupdateValueBy:
, passing either 1 or -1 and thus counting either up or down, depending on the current label value and the end value.
Nice – and you're just about done!
Now you need to add the new counter label to the HUD and animate it from the game controller. Switch to HUDView.h and update it like so:
//with the other imports
#import "CounterLabelView.h"
//with the other property
@property (strong, nonatomic) CounterLabelView* gamePoints;
This property will store the HUD's CounterLabelView
.
Inside HUDView.m, add the following to viewWithRect:
, just before the return statement at the end of the method:
//"points" label
UILabel* pts = [[UILabel alloc] initWithFrame:CGRectMake(kScreenWidth-340,30,140,70)];
pts.backgroundColor = [UIColor clearColor];
pts.font = kFontHUD;
pts.text = @" Points:";
[hud addSubview:pts];
//the dynamic points label
hud.gamePoints = [CounterLabelView labelWithFont:kFontHUD frame:CGRectMake(kScreenWidth-200,30,200,70) andValue:0];
hud.gamePoints.textColor = [UIColor colorWithRed:0.38 green:0.098 blue:0.035 alpha:1] /*#611909*/;
[hud addSubview: hud.gamePoints];
This code looks a bit longer than you might expect, but it's nothing fancy. You create one label that reads "Points:", using the same font you used for the timer (but the smaller size this time). Then you create one CounterLabelView
label with an initial value of 0.
Build and run the project to enjoy your new and shiny HUD:
If you try playing, you'll find that the score is not updated, ever. Well, that's a bit boring and anticlimactic, after all your work! Luckily, it's easy to fix.
Remember the two places where you change the score? In GameController.m, find self.data.points += self.level.pointsPerTile;
and add the following just below it:
[self.hud.gamePoints countTo:self.data.points withDuration:1.5];
This will make the score label count up to the just-updated score value, and it'll do that in about 1.5 seconds.
Then look for the line self.data.points -= self.level.pointsPerTile/2;
and add the following just below it:
[self.hud.gamePoints countTo:self.data.points withDuration:.75];
This updates the label, but does it in about .75 seconds. Since the penalty values are half the success values, making the duration half as much will make the counter appear to change values at the same pace, whether it's going up or down.
And that's all! Build and run again:
Oh, sweet joy! The score label counts up and down as you drop tiles on the targets. Your game is really coming together!