Advertisement
  1. Game Development
  2. Programming
Gamedevelopment

How to Code Unlockable Achievements for Your Game (A Simple Approach)

by
Difficulty:IntermediateLength:MediumLanguages:

Achievements are extremely popular among gamers. They can be used in a variety of ways, from teaching to measuring progress, but how can we code them? In this tutorial, I will present a simple approach for implementing achievements.

Note: Although this tutorial is written using AS3 and Flash, you should be able to use the same techniques and concepts in almost any game development environment.

You can download or fork the final code from the GitHub repo: https://github.com/Dovyski/Achieve


Achivements Code: Trick or Treat?

At first glance, programming an achievements system seems trivial - and that's partially true. They are generally implemented with counters, each one representing an important game metric, such as the number of enemies killed, or the player's lives.

An achievement is unlocked if those counters match specific tests:

There is nothing wrong with that approach, but imagine a test with 10 or more counters. Depending on the number of achievements (and counters), you may end up with spaghetti code replicated all over the place:


A Better Idea

My approach is also based on counters, but they are controlled by simple rules:


Achievements based on properties. Gray rectangles are active properties/achievements.

An achievement is unlocked when all of its related properties are active. An achievement may have one or more related properties, each one being managed by a single class, so there is no need to write if() statements all over the code.

Here is the idea:

  • Identify any interesting game metric (kills, deaths, mistakes, matches, etc).
  • Every metric becomes a property, guided by an update constraint. The constraint controls whether the property should be changed when a new value arrives.
  • The constraints are: "update only if new value is greater then current value"; "update only if new value is less then current value"; and "update no matter what the current value is".
  • Every property has an activation rule - for instance "kills is active if its value is greater than 10".
  • Check activated properties periodically. If all related properties of an achievement are active, then the achievement is unlocked.

In order to implement an achievement, one must define which properties should be active to unlock that achievement. After that the properties must be updated during the gameplay, and you're done!

The next sections present an implementation for that idea.


Describing Properties and Achievements

The first implementation step is the representation of properties and achievements. The class Property can be the following:

A property has a name (mName), a value (mValue, which is the counter), an activation value (mActivationValue) and an activation rule (mActivation).

The activation rule is something like "active if greater than" and it controls whether a property is active (more on that later). A property is said to be active when its value is compared to the activation value and the result satisfies the activation rule.

An achievement can be described as follows:

An achievement has a name (mName) and a flag to indicate whether it is already unlocked (mUnlocked). The array mProps contains an entry for every property needed to unlock the achievement. When all those properties are active, then the achievement should be unlocked.


Managing Properties And Achievements

All properties and achievements will be managed by a centralized class named Achieve. This class should behave as a black box that receives property updates and tells whether an achievement was unlocked. Its basic structure is:

Since all properties will be updated using their name as the lookup index, it's convenient to store them in a dictionary (the mProps attribute in the class). The achievements will be handled similarly, so they are stored in the same way (mAchievements attribute).

In order to handle the addition of properties and achievements, we create the methods defineProperty() and defineAchievement():

Both methods simply add an entry to the property or the achievement dictionary.


Updating Properties

Now that the Achieve class can handle properties and achievements, it's time to make it able to update property values. A property will be updated during the game and it will act as a counter. For instance the property killedEnemies should be incremented every time an enemy is destroyed.

Just two methods are needed for that: one to read and another to set a property value. Both methods belong to the Achieve class and can be implemented as follows:

It's also useful to have a method to add a value to a group of properties, something like a batch increment/decrement:


Checking For Achievements

Checking for unlocked achievements is simple and easy: iterate over the achievements dictionary, checking whether all the related properties of an achievement are active.

In order to perform that iteration, first we need a method to check whether a property is active:

Now let's implement the checkAchievements() method in the Achieve class:

The checkAchievements() method iterates over all achievements. At the end of every iteration it tests whether the number of active properties for that particular achievement equals the amount of related properties. If that is true, then 100% of the related properties for that achievement are active, so the player has unlocked a new achievement.

For convenience, the method returns a Vector (which acts like a typed Array or List) containing all achievements that were unlocked during the check. Also, the method marks all achievements found as "unlocked", so they will not be analysed again in the future.


Adding Constraints To Properties

So far properties have no constraints, which means any value passed through setValue() will update the property. Imagine a property named killedWithASingleBomb, which stores the number of enemies the player killed using a single bomb.

If its activation rule is "if greater than 5" and the player kills six enemies, it should unlock the achievement. However, assume the checkAchievements() method was not invoked right after the bomb exploded. If the player detonates another bomb and it kills three enemies, the property will be updated to 3.

That change will cause the player to miss the achievement. In order to fix that, we can use the property activation rule as a constraint. It means a property with "if greater then 5" will be updated only if the new value is greater than the current one:


Resetting and Tagging Properties

Often achievements are not related to the whole game, but specific to periods such as levels. Something like "beat a level killing 40 or more enemies" must be counted during the level, then reset so the player can try again in the next level.

A possible solution for that problem is the addition of tags to properties. The use of tags allows the manipulation of a groups of  properties. Using the previous example, the killedEnemies property can be tagged as levelStuff, for instance.

As a consequence, it's possible to check for achievements and reset properties based on tags:

The method checkAchievements() becomes much more versatile with tags. It can be invoked any time now, as long as it operates the correct group of properties.


Usage Demonstration

Below is a code snippet demonstrating how to use this achievement implementation:


Conclusion

This tutorial demonstrated a simple approach for implementing achievements in code. Focusing on managed counters and tags, the idea tries to eliminate several tests spread all over the code.

You can download or fork the code from its GitHub repo: https://github.com/Dovyski/Achieve

I hope this approach helps you implement achievements in a simpler and better way. Thank you for reading! Don't forget to keep up to date by following us on TwitterFacebook, or Google+.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.