Best way to implement achievement system?

I'm currently trying to implement achievement system for my game. However, I can't find a better way to realize some complex ones like: The player has collected x coins, complete level with time least than x seconds, Kill over x enemies etc. Can you share your experiences please?

Secondly, Why don't we just implement achievement system as local only instead of using server like Scoreloop or OpenFeint? Or put it in another way, What are the advantages of using server for managing player's achievement?

• Don't ask many questions at once, focus on one. – Matsemann Jul 15 '12 at 16:01

2 Answers

An achievement system can be pretty complex. Many of the achievements are special cases that need to be watched for. That means special code, just for that achievement. Some will be easy to code for, like finishing a level under a certain amount of time or total number of coins collected. As simple as having a timer or a global counter. The more complex achievements, obviously, require more complex code. Killing x type of enemies over x number of seconds would could require a rolling counter or detailed logging with regular analysis.

Basically, if you want achievements, you need to code them in. Just like any other feature. As the developer you have access to all the information about the game, you just need to log what happens and see if it matches your achievement requirements.

I plan on implementing my achievement system into the event system. Since I'm already sending significant events to be logged, I might as well let the achievement system listen in. This is nice because then all the code for achievements is in one place. So for example, you could fire an event each time a level ends. The event would include all the information about the performance of the player on that level including their score, time to complete, number of coins collected and so on. Those events are read by the event system and logged. You can then keep running totals of coins collected and compare finish times to previous bests.

As for the server for managing achievements. I believe one of the big draws with achievements is sharing them with others, or comparing your achievements to your friends. Having the achievements hosted publicly implements the social features that are appealing to many types of gamers.

EDIT

Absolutely you can avoid hard-coded rules! The system I describe is mostly for collecting information and getting it all into one place. That would be best hard-coded and you will have to hard-code collection code for any additional rules. You try to collect as much significant data as you can, so you'll have more to work with later.

How you analyze the data is up to you. Incoming events can be filtered by rules defined in JSON or Lua. As for implementing Lua, I've not done that, so I don't know if it's easy to do. I imagine it is. Lua is a very powerful scripting language with plenty of resources to help if you get stuck. Seems like a good choice to me.

Usually achievement systems boil down to a simple pattern of:

values -> conditions -> achievements

1. Define a list of values that might influence achievements (e.g. Highscore, Highest-Completed-Level, Number-Of-Hours-Player, Cash-Earned)
2. Every time one of these values changes have the "owner" of the value (e.g. the "Player-Class" for "Cash-Earned") send a valueChanged( value_id, new_value ) event which is routed to the AchievementManager.
3. The AchievementManager checks his list of conditions (for incomplete achievements depending on that value) and updates the achievements (sending an event if needed).

Don't forget the load/save/reset edge-cases, but that's it.

My current implementation has around 70 lines of code, not including the "Achievement.config to achievement.cfg" converter.

The configuration currently looks like this:

ACHIEVEMENT_PLAYED_VERY_LONG_AND_EARNED_LOTS_OF_CASH: [
VALUE_PLAY_TIME_IN_MINUTES > 3600,
VALUE_CASH > 10k
]


The created .cfg, which is loaded by the game, then looks something like this

[Number of Achievements]
for each achievement
[CRC32 of the achievement string, e.g. ACHIEVEMENT_PLAYED_VERY_LONG_AND_EARNED_LOTS_OF_CASH]
[Number of Conditions]
for each condition
[CRC32 of the value name string, e.g. VALUE_CASH]
[condition type, e.g. 0 => less than, 1 => greater than]
[value, e.g. 10000]


The converter is actually a little smarter, extending "10K" to "10000", warning about duplicates, detecting of conditions are conflicting, etc.

Sidenotes: You could use an enum for the achievement and value id, but I prefer CRC32s, since you don't have to update a list for new values and still get the benefit of numeric keys and even can map them back to the original name in debug builds. (But ids are a completely different topic ;) )

The system once had a quite complexe boolean logic for conditions, but in the end all we ever used was "and", so we removed it.