Hello there!
It looks like I fail to stick to my plans regarding this development blog, and sadly overall to becoming a bit more organized . Instead of listing the well known and common indie dev. distractions which bog me down too I jump onto this months "achievement": the achievement system .
When I designed the Steam update for KREEP and selected the features that will make the cut (self imposed time constraints, that I already ran out of ) Steam achievements were no. 1 on my priority list. So I knew, achievements have to happen, but when I sat down to design the software changes required, I came to the conclusion, that simply implementing the whole thing in the game project would be messy and foolish, and the bulk of the achievement system could be implemented as a "low level" service in my framework code!
I wrote down the following points, as the design guidelines and requirements for this lower level service:
- An achievement is not much more than a boolean flag (state) whether it's unlocked or not, and some accompanying rules about unlocking circumstances and presentation (description).
- The game logic itself already implements fine details regarding when and how something relevant to achievements occur and it only needs to signal these events to the system (this is the only thing that has to be added to an otherwise complete game code for fully working achievements).
- The game code shouldn't need to know or care about whether the achievement state is read from a file or downloaded from a network back-end, or gathered using the SteamAPI. This logic is especially irrelevant for the part of the code where certain events unlock achievements, but at the meantime the service should support multiple persistency modes, since not everyone is a Steam user and achievements would be a nice addition to non-Steam buyers too!
This is what I came up with and implemented:
A little more description:
- The API user interacts with the AchivementSystem class almost exclusively.
- For unlocking achievements "events" (string or integer IDs) have to be fired/signaled to the system, e.g.: "XYZKilled", "Level1Complete", "BossDead" etc...
- For finer control of some achievements, time-frames (again string or integer IDs) can be started and stopped on demand, e.g.: "First10SecondsOfRound", "JustRespawned" etc...
- The AchievementDescription instances are shipped as a game asset, the AchievementState instances are gathered using the persistency manager, and they are matched with their correct description using a string ID (external reference). The API user can not directly influence or change the state of these objects. It must be done (for the sake of clean code and my sanity ) through the AchivementSystem using "events".
- Achievements can have a counter (optional), for "collection" based achievements, e.g.: "Kill 1000 zombies", "Respawn a 100 times in one match"...
These counters can be accumulative ("Achieve 100 kills") or they can be connected to time-frames and reset immediately upon a stopped time-frame ("Achieve 100 kills within the first 10 minutes of a match"). - State persistency management is automatic and almost entirely hidden from the game logic, only configuration and implementation selection required: instantiating the wanted AchievementPersistencyManager implementation e.g: File, Network, SteamAPI. Persistency handling is "session" based, so unlocked/progressed/reset achievements can be collected and flushed together (but it is optional), which is usually useful for both file and network based serialization.
A small video footage of a sample built using the system:
Here is an achievement description from the sample in XML format:[code=xml:0] Backspace Back out seconds Hold down backspace for 10 seconds! 10 {1,2}/{0} ({2}) true HoldBackspaceForOneSec BackspaceDown
It describes the "Backspace" achievement, keeping state using a counter (10 seconds), progressing for every "HoldBackspace" event fired (this is fired in the sample every second if the backspace key is down), and resetting if the "BackspaceDown" time-frame is stopped (this is started when backspace key is pressed, and stopped when it is released).
There are other useful features in the system, which I didn't mention yet, and a lot of planned ideas to extend it (like multiple named counters for achievements) but I often end up with a huge writing so I'm calling it a day at this point .
Hopefully the next writing will be about concrete new features in KREEP and maybe about a release date on Steam .
Stay tuned!