Monday, 19 March 2012

Adding Game Center achievements to Balls Abound

Based on my love of having a checklist to complete in a game, I decided that Balls Abound needed achievements before I could call it finished. The hope, of course, is that achievements will keep people who need a goal playing the game. I'm already supporting Game Center for leaderboards, this should be simple...

Defining the Achievements

First thing to do was figure out what achievements to make available. In a pinch I created 3 or 4 achievements for getting various levels of score in the game. Next, I put a lot of effort into stereo positioned sound for the game, so to encourage people to experience it I added an achievement for plugging in headphones. I also added a couple of hidden achievements for the "easter eggs" in the game. After that things got tougher, and I ended up just mucking around with the game and stumbling on a few ideas I liked. The full list of achievements I decided upon is as follows:


Karmapurchased the full gameYes100

Touchdownused finger on splash screenYes50

Poke the crabpoked the crab 100 timesYes50

Century Clubearned 100 points in a gameNo25

High Roller Clubearned 10,000 points in a gameNo50

100 Grand Clubearned 100,000 points in a gameNo75

Mile High Clubearned 1,000,000 points in a gameNo100

Palindromelit the signs in order

Stuck on youcleared three fields of coconuts,
hitting only the letter 'A'

Double Hitterturned two signs on in one bounce,
two signs off in one bounce, and
turned one on and one off in one bounce,
all in one game

Audiophileplugged in headphonesNo100

Nauseacleared two fields of coconuts,
while playing upside down

Multiball Masterearned 1,000,000 in games
with each of the types of ball

Luckily I had created the pelican icons a while back and finally have a use for them.

In total Balls Abound has 10 visible achievements and 3 hidden ones, for players to work towards. iTunes Connect is used to define the achievements in Game Center. Use iTunes Connect to add localizations, point values, icons, and other achievement properties. The identifiers you assign to the achievements defined in iTunes Connect are referenced from your code when obtaining GKAchievement objects, which are used to communicate progress to Game Center.

Tracking the Achievements

I am rushing to get this game finished, and I am the only person touching the codebase. With this in mind, my main goal is not the most beautiful design right now, I just need things to work.

I am already using Core Data to track some of the user settings in the game (muted, etc..) so I decided to model a new class in XCode to track achievements progress. Easy enough, now I have a new managed object that holds floats that represent the progress towards each of the achievements; they mirror the data stored in GameKit's GKAchievement objects. Another require for storing achievement data is, multiple Game Center users might play on the same device. So you need to track achievement progress on a per user basis. Adding a userId attribute to the UserAchievements managed object allows tracking achievements on a per user basis, creating one UserAchievements object for each userId. I create or attach the UserAchievements for the current user to my game settings whenever Game Center authentication is completed. I also use this as an opportunity to load the users current achievement status from Game Center, and reconcile any discrepancies between local and remote progress (using the rule, higher progress always wins).

Now that there is a data model capable of storing user achievement progress, three more things are required.
  1. an AchievementTracker that will monitor achievements and update the UserAchievements data
  2. an AchievementBoard UI to listen for UserAchievements changes and put notifications on screen
  3. the ScoreKeeper must listen for UserAchievements changes and report updates to Game Center
The AchievementTracker class is bound to all the points needed to listen on when detecting achievement progress. The AchievementTracker has the ability to track game state, coconut hits, sign hits, and accelerometer readings. This allows the AchievementTracker to decide when progress towards an achievement is made and update the UserSettings accordingly.

Listening for the Achievements

The ScoreKeeper and AchievementBoard both listen for achievement updates through KVO on the UserAchievements object. In order to achieve a reasonably performant option, I treat achievement progress internally as an int, rather than a double, which helps limit the number of updates that I have to process. Updates to achievement progress are relatively expensive, they trigger a KVO transaction which is followed by a managed context save and possibly network traffic through GameKit reporting through GKAchievement objects.

Displaying the Achievements

I decided to create my own UI for displaying achievements, the AchievementBoard. This was largely due to the fact that Apple only provide functionality for displaying achievements in iOS 5.0+ and I wanted to support iOS 4.2+.

I wanted a UI that would animate it's entrance and exit, was capable of handling multiple simultaneous achievement updates, and could display at top and bottom of the screen. The AchievementBoard holds a queue of achievement updates. If progress towards an achievement is made, it will either append the update into the AchievementBoard's queue, or replace the progress in an existing update for the same achievement. The AchievementBoard coordinates the onscreen display of achievement updates that are in it's queue. This system allows multiple achievements to be acted on simultaneously, with no notifications being ignored.

Coming up with graphics that tied into the game took a number of iterations of producing graphics and redeploying the game to test their visuals.  I eventually came up with the following, which I think works well.

The Final Touches

With achievements being monitored, updates being communicated to both Game Center and the display, there is just one more task before achievements can be considered fully integrated. There needs to be a button on the high score view so people can view their achievements in Game Center. Ideally I would create an in game view for achievements and leaderboards, but that is out of scope for now. So the quick solution is a simple button that opens a default GKAchievementViewController for displaying achievements. This leads to launching Inkscape and drawing a new icon for viewing achievements and updating the icon that already existed for viewing leaderboards. This in turn means running TexturePacker again, and finall PVRTextTool to generate my pvr with mipmaps. Anyway, achievements are finally present in Balls Abound.

Everything for achievements is plugged in and working. Except I'm suddenly down from a nice smooth 60fps to ~40fps in many parts of the game. I'll save the details for another post, but a day or two of work later and I am back to 60fps consistently (my solution involved clipping with glScissor and a hack to CCLabelBMFont). If only everything in life were so straightforward...

Friday, 9 March 2012

Didn't make the cut

This little guy just didn't fit into Balls Abound like I thought, but at least he made the blog.  And now I'm seriously done making gifs for a bit.

Halftoned animated gif

This is the last gif for a while, I'm simply wasting time now.

In order to produce a larger image with smaller file size (though still a sizeable 5.5MB) I created a layer when creating this gif in Gimp and rendered a grid with lines 1 pixel wide spaced 2 pixels apart (so every other row and column are painted). Then "selection from alpha" on this layer and delete from each of the animations layers. You are left with a bunch of layers that are mostly alpha right now, just little dots of color floating there now. I then remove the alpha channel from each layer with a background color of black. Finally switch to indexed color mode with no dithering, and then optimize animation for gif.  This particular image has 466 layers and runs at 60fps.

Balls Abound Splash screen as animated gif

I think this turned out nicely.  I outline how I am making gifs here.

Creating 2D animations using Inkscape

Balls Abound contains a number of animated elements. I used Inkscape to hand render each of the elements below.

Flame Crab Idle Crab Walk
Pelican Flight

Each of the above animations contains at least 2 keyframes that were hand drawn using Inkscape. Inkscape provides a function for creating an interpolated series of shapes that morph from a start shape to an end shape.
Start and end shapes

Using this function you can select shapes from your key frames as start and end shapes, and interpolate between them to create a series of intermediary shapes.
Straight interpolation, left to right, no exponent

This set of shapes will define the frames of your 2D animation. There are some drawbacks to this unfortunately. You will be given back a group of objects that needs to be separated out into layers. If you want to interpolate between two objects with gradient fills you have some work ahead as well.
Gradient fills don't interpolate properly

Solid fills do fortunately interpolate, if you require that.
Solid fills do interpolate correctly

I like to separate each of my animated frames onto it's own layer, and use a dummy layer to hold a rectangle that will be my export viewport. This way I can select the viewport rectangle, hide the dummy layer, and then export the animated frame images by simply turning on the desired layer and exporting. There are likely better tools out there for this, Inkscape is very labour intensive. I'm sure this could be scripted in Inkscape as well, but haven't bother to look into it. You will need to repeat the interpolate process for each shape in your key frames, and gradient fills don't interpolate with the shape, so you will be hand tweaking fills on each frame for each shape. But the end result can be very good if you are ready to invest the time.

The pelican animation made use of the exponent feature in Inkscape's interpolate function. When interpolating between two shapes you can set an exponent which will create an easing function. The size of the exponent determines the amount of easing.
Exponent = 1, leftmost shape selected first

The order in which you select the two end points of your interpolation determines which end the easing occurs.
Exponent = 1, rightmost shape selected first

The easing on the extremes of the pelicans flap are baked into the animated frames, this allows the animation to be run at a constant frame rate in the game, but still appear as natural flight.

The torch animation uses a bunch of code to alter the timing of four distinct stages of the flame animation that run in an endless loop. The smoke emitted from the torches is synchronized with the flickering of the flames. Other properties of the flame animation, including the lighting, are affected by the Perlin noise function. Perlin noise with the right inputs can produce output that flickers very much like fire.

I used Blender to create the balls and the coconuts.

Beach Ball Happy Ball Wood Ball Eight Ball Eye Ball Coconut

Each of the above renderings used the same technique. Using Blender, create a spherical 3D model and point the camera some distance from it's circumference, and make the ball fill the cameras view. Then setup a timeline where the camera rotates 360 degrees around the ball for however many frames you want the animation to take (I used 64 frames; though most of the balls repeat half way round and have been shrunk to 32 images). In the game the balls have a a bunch of simple math applied to calculate the circumference of the ball and determine the distant that the ball needs to move/roll before the next frame should be displayed.  Keeping track of setPosition calls it is possible to adjust the sprites display frame and rotation to provide the illusion of a rolling 3D ball.

In the game the coconuts rotate in accordance with a Perlin noise function.  The rotation of each coconut is based on the output of the Perlin noise function that has been provided x, y, and z as coconut position x, coconut position y, and time, respectively.  When the game is in play, the x and y parameters above have an additional play ball offset added to each.  This additional offset causes the spin of the coconut grid to be affected by the position of the in play game ball, the effect is subtle.

Ralph Baer, what a cool guy

Thursday, 8 March 2012

Creating an animated gif using Gimp

Here's a few frames taken from Balls Abound.

I created this with Gimp

There are 120 source images taken from a set I made earlier when producing video from the game.  Using "Open as layers" I opened the source images into a Gimp project and now have a set of layers with the ball running from left to right.  I then duplicated the Gimp project, deleted the first and last layer, and reversed the layer order using the layer stack menu.  I then save the duplicate project as an xcf file, and return to the first Gimp project which has the original 120 images in order.  Now using "Open as layers" again I open up the xcf file (with 118 frames in reverse order) and finally I have 238 frames of the ball running left -> right -> left.  Now it's a simple matter of scaling to your desired size, dithering down to indexed color, running the animate for gif optimizer, and save your gif with 16ms between frames (for ~60fps).

One day I will make the effort to brush up on my Gimp scripting (another language I have no capacity to retain once learned) and make this easier on myself.