Achievement Handler Service - UQdeco2800/2022-studio-1 GitHub Wiki
Jump to a section or return to Achievement Summary here!
The Achievement Handler service is responsible for tracking the progress of a player as they make achievements. It persists the progress made so far using file storage and ligdx JSON library to marshal and umarshal the saves. Generally there's six types of achievements Kills,Buildings,Resources,Game,Misc and Upgrades.
It communicates with other parts of the game largely through events. When it receives an event it flushes its contents to disk so they can be loaded when the game is restarted.
Astah - Fig. 1
The service is designed to be loosely-coupled and to be located via the ServiceLocator
. The reason for this design choice is to make it flexible and make it easy to instrument parts of the game that need achievement collection.
It manages two types of achievements. Stat achievements and just regular achievements. Stat achievements have milestones by default these milestones are 1,10,25,50
. A stat achievement is complete when all milestones have been achieved. Regular achievements on the other hand, will be complete when what they depend on is achieved and true
is set on isCompleted
. The design called for a pure data class that houses the achievement data and a super data structure that holds the las time the data was saved to disk as well as the list holding all the achievements. This class is called AchievementData
it holds Achievement
objects in an ArrayList
.
This segment of code is responsible to saving the list of achievements to disk.
It saves to the user's home directory in a folder AtlantisSinks
.
On Mac /Users/[user]/AtlantisSinks
.
On Windows C:\Users\[user]\AtlantisSinks
Where [user]
is the user's name on their machine.
The file is called playerAchievements.json
/**
* Saves the current state of the achievement list with the current time
*/
public void saveAchievements() {
this.lastSaved = System.currentTimeMillis();
AchievementData achievementData = new AchievementData(this.lastSaved, new ArrayList<>(this.achievements));
achievementsFileHandle.writeString(json.prettyPrint(achievementData),false);
}
To load from file the same libGDX library for JSON and file access are used to make this possible.
/**
* Loads the achievement list from the achievement file
* @param fH FileHandle
* @return ArrayList
*/
public List<Achievement> loadAchievements(FileHandle fH) {
AchievementData data = json.fromJson(AchievementData.class, fH);
this.lastSaved = data.getLastSaved();
return data.getAchievements();
}
To update the achievements. Events from elsewhere in the game are listened for. These events cover the six aforemetioned categories. Once an event is received the events are updated and will be flushed to disk by the event handlers.
/**
* Listens for events from outside
*/
private void listenOnEvents() {
this.events.addListener(EVENT_CRYSTAL_UPGRADED, this::updateStatAchievementByType);
this.events.addListener(EVENT_BUILDING_PLACED, this::updateStatAchievementByType);
this.events.addListener(EVENT_ENEMY_KILLED, this::updateStatAchievementByType);
// resource stat listeners
this.events.addListener(EVENT_RESOURCE_ADDED, this::updateResourceStatOnResourceAdded);
}
The service emits two types of events EVENT_STAT_ACHIEVEMENT_MADE
and EVENT_ACHIEVEMENT_MADE
.
Thexe events are emitted when an achievement has been completed.
To hook into them you do it as follows:
ServiceLocator.getAchievementHandler().getEvents()
.addListener(AchievementHandler.EVENT_STAT_ACHIEVEMENT_MADE, this::onAchievementMade);
ServiceLocator.getAchievementHandler().getEvents().addListener(AchievementHandler.EVENT_ACHIEVEMENT_MADE, this::onAchievementMade);
For usage examples see AchievementPopupComponent.java
private void onAchievementMade(Achievement achievement) {
// ...
}
For more technical details about implementation please see the JavaDoc.
Some of these challenges were faced:
- Incorrect JSON format saved resulting in data that can't be loaded
Resolved through careful inspection of libgdx documentation and examples
- Files written to project directory polluting codebase and making accidental commits more likely
Resolved by writting achievements JSON file to user's home directory so Git doesn't try to track it.
- Some achievements have bigger increments and don't follow milestone increments such as 1,10,20,50
An example of this was resources gold, stone and wood. Resolved by introducing a map keyed by achievement ID with custome milestones.
Most of these challenges were solved by reading libGDX documentation.