In last week's review of Zombieville USA 2, I pointed out the upticking score label as a nice bit of polish. Today we are going to build one of our own.
My current project uses cocos2d so we are going to extend one of the existing label classes to build our score label. If you aren't familiar with cocos2d, then I suggest you head over to their website and run through some of the beginner tutorials or at least the documentation. Before we start coding let's define what we want.
- Label that displays a score
- Can roll from value to another
- Can roll either up or down
- Can roll more than once
- Can target value can be updated mid-roll
- The displayed string is formatable
- Customizable alignment and size
We want to be able to setup and start rolling the label like this:
Now let's start by defining our class in the header:
We don't have anything too crazy going on here. We track the current score as a double rather than as an int so we can track fractional updates per interval. The score, format string, and update rate (pointsPerSecond) are all exposed as public properties so users of the class can set their own values. It is important to note that we are retaining formatString, so we will have to release it in our dealloc and if we assign a new value to the formatString property we should release the old value.
We are building off of CCLabelTTF which is one of the labels that cocos2d provides. CCLabelTTF is not nearly as performant as CCLabelAtlas. The cost of setString: in CCLabelTTF is much higher than in CCLabelAtlas. This label was built to be used on a post gameplay scoreboard so performance is not a huge issue. If you need a score label in an area of your game where you are fighting for every FPS, then you may want to build a similar label based on CCLabelAtlas.
Let's take a look at rollToScore: the method you would call to start the score rolling/ticking.
The first thing we do is update the target score to the new target. Next we check if we are currently rolling. If we are then we don't need to do anything because the update_: method will take care of the magic for us. If we aren't updating then we set the update flag and schedule an update. This will cause the update_: method to fire repeatedly after the number of seconds specified by interval_.
The actual updating of the label occurs in... you guessed it update_:.
The first thing we do here is determine which direction we should be rolling in. Remember that one of our requirements was that we could roll the score up or down. There are a number of ways you can accomplish this, but we stuck with the straight forward comparison.
Next we update the score. Note the use of the double for pointChange to match the type of curScore_. Depending on the value you are using for pointsPerSecond you might only update a fractional point per tick and you don't want your score stuck in the mud. We multiple by dt rather than by interval_ because dt will give us the actual time passed rather than the scheduled time.
Then we make sure that we haven't overshot our target score. Alternatively you could use min and max functions here.
Now we are ready to update our displayed string. Straight forward stringWithFormat call here. One of our assumptions is that the format string is looking for an int. We cast curScore_ to an int to match that assumption.
The last thing we need to do is check if we hit our target score and if so stop updating and unschedule future updates.
I hope that all makes sense and this is something that will be useful in your own projects. Click here for all the code at once. Hopefully the rest is fairly self explanatory, but if it isn't please ask questions in the comments. You can download the full sample from the gist.
*Disclaimer: The code presented in this post is far from perfect. Please improve on it.*