Achievements UI: Design and Implementation[Cross-posted from the devblog here--follow link for better formatting and light-on-dark style.]Players not only appreciate a game
having achievements to begin with, they
really appreciate it when said achievements are available without Steam. A sizeable chunk of players don't use Steam for whatever reason, so having achievements be a Steam-only feature would leave all of them out of the fun and benefits described before! Achievements are essentially a form of content in their own right, so it'd be unfortunate to restrict that content to a subset of the player base. I've heard a lot of praise for this decision, both with regard to Cogmind and other games that have taken the same approach in the past.
Of course supporting achievements independent of Steam
does involve a lot more work! At least on the implementation side there's not much extra to do, since actions that earn achievements need to be detected all the same in either case, but without relying on Steam a game must provide its own interface to both notify players of new achievements and offer a way to interact with them. With Cogmind in particular this need comes to the forefront even for Steam players, since achievement interaction is restricted due to the lack of Steam overlay support.
I took this opportunity to build a better system than Steam's own :)
Supplementary UIThe most basic UI requirement is notifying players when they've earned an achievement. I added this much before even starting on individual achievements, since being able to clearly see when one is earned is fairly useful while testing and working on them :P
New achievements are immediately shown in the bottom-right corner of the screen as a pop-up that remains for an adjustable 5 (?) seconds, where multiple simultaneous achievements will push up older ones that haven't yet disappeared. Cogmind was designed to be an immersive experience and these pop-ups can kinda ruin the atmosphere at some points, so I also added an option to remove them completely (though it's probably best that they be left on by default).
Achievement notification pop-ups in action, showing both their name and icon. Achievement pop-ups are queued through a separate system so their display can be delayed if necessary. Although they're shown as soon as possible, some may be earned while in a different UI (hacking, for example), and it's better to have the notification appear in a consistent, predictable location (bottom-right corner of the map) once that location is available for use. Notice that the code for earning an achievement, shared
last time, ends with a call to GM::addAchievement(). That just adds it to the queue and the UI can draw from there when ready.
New achievements also get their own meta notification in the message log, for later reference after the pop-up has disappeared. Meta notifications are game-related messages that always appear in white and enclosed by brackets.
Achievement notification as it appears in the message log. The majority of achievements are earned and displayed mid-run, but some cannot be determined until the end of a run, in which case they'll appear to the right of the game over stats in a separate window.
New achievement list displayed on game over if applicable. Since I didn't really want to mess up the balance and flow of the game over screen, I originally imagined I'd display any end game achievements at the beginning of the
next run, but later decided it would be a bad idea, and unnecessarily anti-climactic, to force a such a potentially large disjoint between when an achievement is earned and when it is shown. It's great to have a sense of accomplishment be associated more closely with how and when it was achieved. In fact I even read that on the PlayStation developers
must show achievements immediately at the point they are earned, presumably for that same reason. So even though it doesn't look great, for now this subset of achievements (if any were earned) appears in its own window.
An alternative solution I'm still considering is to have achievements appear first in a centered window over where the stats will be (or perhaps on a separate window
after the stats), and the player has to press a button or key to advance to the next window. This would kinda mess with the flow, but at least it would look better. A lot of games take the multi-page game over approach in order to fit more information in without crowding the layout.
Main Achievements UIThe last part of the interface I worked on, and by far the most involved, was the achievements browsing UI. According to my records I spent 18 hours coding it, but as an essential part of any game that wants to provide its own system for interacting with achievements independent of Steam, it was worth it to make this UI as functional and informative as possible.
As with any major UI component I started with a design mockup in
REXPaint, setting a clear goal to aim for that also accurately reflects intended functionality so that I knew everything I needed would fit just right and there'd be no time wasted on iteration following the implementation phase.
Working mockup of the achievements UI, as it appears in REXPaint. Note that the achievement-specific content is in some cases repetitive or inaccurate--it's purely for testing purposes and needed to fill the space :) The elements across the bottom match the other two collection-oriented features (item gallery and lore), with page buttons, animated percent bar, and export buttons. Exports are for players who like to view or process their performance via other means. For image examples see
this progress update.
New subwindows were implemented one by one, starting with the secondary ones that control content in the main window so that when it came time to do the latter it could be fully implemented and tested all at once.
First was the category-specific window, useful for enabling players to cut down the otherwise huge number of achievements that could be annoying to scroll through looking for something specific.
Interacting with achievement category filters. Below those are the state filters, further helping players hone in on an achievement they're looking for, or simply browse their actual achievements vs the pool of remaining achievements they can aim for. 256 achievements is a lot, but with sufficient filtering each subgroup can be reduced to a pretty manageable size, especially considering that the UI can display up to 16 achievements on a single page.
Achievement state filters and sort types. Sorting features have a number of purposes: search efficiency is increased when listed alphabetically or grouped by category; "oldest first" gives a chronological listing since the player starting playing Cogmind; and "newest first" is best for reviewing those earned across the current run, or seeing the exact description for the latest achievement, since that's not reflected in the map pop-up notification.
Note the highlighted letters in all these windows, there to indicate the relevant keyboard command for that button. Giving keyboard players easy and intuitive access to everything actually affected what words could be chosen for each button, simply to ensure it was always the first letter highlighted. Fortunately there was no overlap among the achievement category names themselves, though the "Category" sorting method had to be renamed to "Grouped" due to the former's overlap with the "Challenges" category itself.
And here's the whole thing put together! (As usual scrolling was annoying to implement, but having done it a ridiculous number of times in different formats by now it went surprisingly smoothly...)
Interacting with the full Cogmind achievements UI. Notice that the UI behind it remains unchanged when the achievements UI opens, resulting in oddities like that dangling "Alt" in the bottom right, and leaves other commands visible at the top left. To fix the top left, for now the Beta 6 version of Cogmind simply raised the height of the Filter window so that its top is level with the Achievements. I'll lower it again like this later (as per the mockup, too) once I implement darkening of the background while this and similar windows are open. The current height adjustment is just a temporary measure so that the UI appears less confusing until then.
All done! Oh wait yeah, this is it for the DRM-free version of Cogmind but there's still more to do for Steam...
Steam IntegrationIntegrating with Steam is pretty easy, even for often technically challenged me :P
It would be more involved if I also had to make use of Steam's stats interface as a backbone for achievements, but 1) most Cogmind achievements are for intra-run accomplishments anyway and 2) Cogmind has its own internal stats system for recording long-term progress in the few areas it's needed so there's no need to rely on Steam for that, either. (Any game that wants to offer its own Steam-free achievement system will naturally need its own solution for stats, and the good thing is that with Steam cloud saves even players on Steam can retain persistent data all the same, as long as the meta data is considered part of the cloud saved content!)
In any case, all the relevant API commands can be found on
this page, and Steam has pretty good step-by-step documentation for how to set up an achievement system, including a complete example with code you can just borrow directly or modify as necessary.
My own version is pretty similar to theirs.
steam.h The source is somewhat bloated with additions of my own, which I'll get to in a moment.
steam.cpp On startup the game basically just has to ask Steam for the latest "stats" (which includes achievements) and wait for a response via callback. Then it's good to go, adding new achievements at any point or using any of the API's other dozens of methods.
As per onUserStatsRecieved(), as soon as Cogmind has the online stats data the other thing it does is immediately sync the data between the game and Steam. Usually there will be no difference, but if for example a player has been using the DRM-free version then migrates their data over to a Steam install, it'd be nice to automatically upload all their previous achievements. Likewise, a fresh reinstall of Cogmind on Steam will also need to know all previous achievements so that it can display them in the game's own UI. The drawback of the latter Steam->Cogmind scenario is that Cogmind technically stores
more information about individual achievements than Steam's database (for example the highest difficulty on which it was earned), meaning that extra data would be lost.
So that's what all the logged "uploading" and "downloading" is about in the source there :)
With a basic API interface available, the first order of business was to test it! Using my trusty new EARN_ACHIEVEMENT debug console command, I gave myself a couple of achievements--earnAchievement() simply calls SteamAchievements::SetAchievement() and voilĂ , the first ever Cogmind achievements to appear on Steam popped right up :)
First Cogmind achievements to be tested on Steam. "Every single achievement" my... I also needed the CLEAR_ACHIEVEMENT command to make sure different scenarios worked as expected, and it was pretty neat to see how quickly Steam's UI reacted to having or not having the achievements (it's pretty much instantaneous), even when I was just repeatedly toggling them on and off :P
Testing achievements on Steam requires them to be added to the Steam database, sure, but unfortunately test achievements
also show on the store page. I guess you could make an obvious "test_achievement" and use that temporarily to make it more obvious, but I didn't want to pollute my data with testing stuff, so for a couple weeks or more leading up to the Beta 6 release, Cogmind's store page looked somewhat odd xD
Test achievements appearing on Cogmind's Steam store page... 2 of them. Steamworks clearly has an internal toggle for whether a game supports achievements, but it
doesn't honor that toggle in terms of showing on the freaking
store page whether a game includes any. How misleading...
There were no hitches at all--time to upload the entire batch! Steam does
not, however, provide a way to batch upload achievements, so games with lots of them are going to have a harder time here. I dunno, maybe they do it as a way to discourage devs from adding
too many achievements? (there have certainly been a number of requests for batch uploading over the years)
It took me about two hours to add the data (tag, name, description, hidden state, locked icon, unlocked icon) for all 256, a pretty decent pace. Sometimes it's fun to just put on some good music and find ways to do some mundane task more and more efficiently until it's done. You can technically write a script to do it for you through Steam's web interface, but I'm terrible at web stuff so it was unquestionably faster to just upload them manually xD
Also the different focus of this task, and looking at achievements from a different angle as I worked with them yet again, allowed me to notice a few last minute issues that needed to be addressed (e.g. correcting descriptions).
All achievements appearing on Cogmind's Store Page. Now that's more like it! More?Aside from the two-page game over screen I mentioned earlier, another new feature that might be worked in is global achievement rates. You can see there's a place for these numbers in the original mockup. Games can retrieve these values as part of the API with
RequestGlobalAchievementPercentages(), so it would only be available to players on Steam.
Also it goes without saying that there'll definitely be more achievements as more content is added :). That's too bad because we'll ruin the coincidental "256" figure! 512 is out of the question... for now.