Trial includes a generic API to handle achievements in games. This API is core to Trial, as it aims to be independent of whatever platforms you may be ultimately distributing the game on. Using this API your achievements will work properly for games shipped without any service, or on Steam, or whatever you may have.
For this to work each platform extension must implement a small API to integrate and synchronize the data. When the game starts up, you must call
(load-achievement-data T) to select the appropriate API and load the actual data. After this call,
*achievement-api* will be set. You should then call
+achievement-api+ for every event, typically from your
scene, or by registering the api as a handler.
(load-achievement-data T) will always succeed, falling back to a local achievements api, which simply stores achievements in a local file within the config directory.
Once this is set up, you can get on to actually defining your achievements.
(define-achievement all-races-complete race-complete (loop for race in (list-races) always (complete-p race)))
The above achievement will trigger when a
race-complete event is handled and every race is complete. Or in other words: the body of the achievement should return a boolean, which if true will unlock the achievement.
You can also not supply any event or body, in which case you must manually unlock the achievement, either by calling
award or setting
active-p, on an
achievement. Note that while this also allows you to re-lock achievements, it is generally discouraged to do so outside of testing environments.
You can also list all achievements by
list-achievements and query their
icon. The title and description slots should store symbols naming localization keys, such that reading them with the accessor automatically returns the appropriately translated string. You can customise these keys by supplying extra keywords in the name of the achievement:
(define-achievement (all-races-complete :title foo) ...)
If not supplied, the title defaults to the achievement name, and the description defaults to the name with
/description appended. The icon is NIL unless manually supplied. If supplied, it should be a
texture resource that can be used to display the icon in the UI.
Note that the names of the achievements matter here, and must correspond to whatever names you have on your distribution platforms. In order to still allow somewhat convenient naming though, the package of the name is ignored, the name is compared case-insensitively, and any underscores or spaces in the name on the distribution platform is treated as a wildcard. For instance, if the name on the platform is
my_achievement, this will match
MY-ACHIEVEMENT just fine.
If you are integrating with another platform like Steam it's likely that the platform will take care of showing an achievement popup for you in the expected native style. You can query and possibly set whether the selected achievement api shows notifications with
If you are integrating with another service and would like to tie to the API, you should proceed as follows: implement a subclass of
achievement-api and push an instance onto
*achievement-apis*. Then implement a method on
load-achievement-data, which should error if the platform is not available, and otherwise restore the
active-p state on the achievements. It is recommended to set the slot-value directly during this, so that no events are generated.
Next you'll want to handle
achievement-relocked to synchronize the state with your platform when changes occur. And finally, you should implement
notifications-display-p to signal whether the platform will display achievement notification popups for the user on its own or not.