Programming Unlockable Achievements

So you wrote this game, but would now like to step up? Unlockable achievements is an aspect that enhances your game and it’s fun and interesting to program.

Minecraft Achievement
A simple Minecraft Achievement

The Concept

Let’s elaborate on the actual purpose and the concept of unlockable achievements. Basically you could consider an Achievement to be a milestone. It is a goal your end user achieves, literally. It is up to you to determine these milestones.

Let’s take Minecraft‘s ‘Taking Inventory’ achievement as an example. The achievement is unlocked at the moment the inventory is opened for the first time. This example is a single Achievement, meaning it has no predecessors or successors. Possibly you’d want to create an achievement that could only be unlocked when this one was unlocked first. You may even want to have a set of achievements unlocked before achieving a parent achievement.

The previous example was obviously very simplistic. There was one task, one goal that lead to the unlocking of the achievement. Different achievements might consist of multiple milestones to reach before unlock. Or maybe you’d want specific actions to take place in order to achieve the achievement.

Achievements Everywhere

Gaming companies, publishers and console builders all make use of achievements. Think about the trophies you can earn on PlayStation and XBox consoles. Think about achievements that you can collect on the Steam platform. And don’t forget the¬†achievements on mobile devices in Google Play Games (Android) and Game Center (iOS). There are so many possibilities, so many things you can do with your in-game achievements. You can go beyond an implementation in your game itself.

Implementation

Let’s put things in practice by covering a possible implementation approach in Java.

You want to get things started by defining an interface:

public interface Achievable {
    void achieve();
}

Now we’re ready to define an Achievement class. You can make it Observable as we did in the following example. That way registered observers can get notified at the moment the Achievement is unlocked.

public class Achievement extends Observable implements Achievable {

    private String name, description;
    private List<AchievementProperty> propertyList;
    private boolean unlocked;

    /**
     * Class constructor specifying name, description and properties to be activated.
     *
     * @param name        the untranslated name of the achievement
     * @param description the untranslated description of the achievement
     * @param properties  the properties related to the achievement
     */
    public Achievement(String name, String description, List<AchievementProperty> properties) {
        this.name = name;
        this.description = description;
        this.propertyList = properties;
        this.unlocked = false;
    }

    /**
     * Unlocks this achievement and notifies its' observers.
     */
    @Override
    public void achieve() {
        if (!unlocked) {
            boolean allActive = true;
            for (AchievementProperty property : propertyList) {
                if (!property.isActive()) {
                    allActive = false;
                    break;
                }
            }
            if (allActive) {
                this.unlocked = true;
                setChanged();
                notifyObservers();
            }
    }
//Getters, setters and toString omitted...
}

As you can see in the above code, the achievement contains a list of AchievementProperty. These are properties that need to be active when the achievement is unlocked.

Here’s how the AchievementProperty class may look like:

public class AchievementProperty {

    private String name;
    private int value;
    private String activation;
    private int activationValue;
    private int initialValue;
    private String tag;

    /**
     * Class constructor specifying name, activation, activation value and initial value.
     *
     * @param name            the untranslated name of the property
     * @param activation      the activation method of the property
     * @param activationValue the activation value of the property
     * @param initialValue    the initial value of the property
     */
    public AchievementProperty(String name, String activation, int activationValue, int initialValue) {
        this.name = name;
        this.activation = activation;
        this.activationValue = activationValue;
        this.initialValue = initialValue;
    }

    /**
     * Class constructor specifying name, activation, activation value, initial value and tag.
     *
     * @param name            the untranslated name of the property
     * @param activation      the activation method of the property
     * @param activationValue the activation value of the property
     * @param initialValue    the initial value of the property
     * @param tag             the tag of the property
     */
    public AchievementProperty(String name, String activation, int activationValue, int initialValue, String tag) {
        this.name = name;
        this.activation = activation;
        this.activationValue = activationValue;
        this.initialValue = initialValue;
        this.tag = tag;
    }

    /**
     * Returns true when this property is active, false if otherwise.
     *
     * @return true when this property is active, false if otherwise
     */
    public boolean isActive() {
        boolean flag = false;

        switch (activation) {
            case Achiever.ACTIVE_IF_GREATER_THAN:
                flag = value > activationValue;
                break;
            case Achiever.ACTIVE_IF_LESS_THAN:
                flag = value < activationValue;
                break;
            case Achiever.ACTIVE_IF_EQUALS_TO:
                flag = value == activationValue;
        }

        return flag;
    }

    /**
     * Resets this property's current value.
     */
    public void reset() {
        this.value = initialValue;
    }

Now it’s up to you to get all that linked up by creating the ‘Achiever’ class. I’ll leave that as an exercise to the reader. Here’s a bit more context to get you going:

  1. Make use of a HashMap to keep AchievementProperty instances;
  2. Make use of a HashMap to keep Achievement instances;
  3. Provide a ‘defineProperty’ method that creates and returns a new AchievementProperty based on specified parameters. If the property is not yet listed in the HashMap, put it in there;
  4. Provide a ‘defineAchievement’ method that creates and returns a new Achievement based on specified parameters (do not forget its’ related Achievementproperty/AchievementProperties!). If the achievement is not yet listed in the HashMap, put in in there;
  5. Add a method that will achieve all eligible achievements (don’t forget checks on the related AchievementProperty/AchievementProperties;
  6. Add methods to add/set a value to properties, or even to reset property values.

At this point you can create your new achievements and their respective related property/properties.

On the moment a property needs to get an update, you call the ‘addValue’ method from step 6 and you call the ‘checkAchievements’ method from step 5. This way your achievements can get unlocked at the proper time.