Welcome to September’s development blog! Today we’ll begin with a look at our new user interface style (for brevity, we’ll shorten it to ‘UI’ – it includes all the buttons and 2D looking stuff), and how we implemented a consistent, efficient UI throughout Antidote, with a deep look at how our team members and teams work together. Then we’ll look at a major change “under the hood” that broke a lot of things for a short time, but ended up with much greater stability. Finally, we’ll take a look at the design and development of our high scores, a new feature available for all users very soon.
Developing a consistent UI
It seems a fairly common rule in studios : programmers can’t touch the UI looks, and artists can’t touch the UI behavior. Yet the two are inextricably linked most of the time, with the best looking interfaces combining a hybrid of code (or at least a thorough understanding of Unity’s layout rules) and the best behavior comes with a consistent, carefully laid out UI. Add into this “self blindness” (that thing where you can’t see your own mistakes) and it’s clear that both teams need to work together closely.
Despite this challenge, we think we’ve managed to put together a consistent, good looking UI in Antidote – like in the encyclopedia screenshot above. We develop our UI using the following process:
- A programmer develops a UI to a functional specification.
- An artist creates any new assets required (ideally, most assets are re-used)
- A programmer and artist sit down together to complete the UI, together
This maximises the power of solo work (allowing both artist and programmer to get in flow) and the benefits of working together. It also allows the emergence of hybrid solutions, like that used for backgrounds in Antidote.
The Antidote background panel needed to be consistent across the game, and needed to be used in multiple prefabs. Unity, the game engine we use for Antidote, restricts nesting of prefabs, so it requires both an artistic and a technical hand. Let’s use this as a quick case-study.
The work began with a general (dis)agreement that the background was both ugly and poorly implemented. Both art and code teams were grumpy. Our design goal was to make it consistent, beautiful, and easy to maintain.
- One of our programmers develops a new prefab-in-prefab script, allowing one background panel to be used across all UIs. This eliminates duplicity, so the artists only need to make the background layout once, and if they change it, only need to change it in one place. (comment if you’re interested in the technical implementation). All backgrounds in all UIs are replaced with a new blank canvas (actually, a “panel”).
- One of our artists develops a set of assets for the background, including a fill texture and several “stretchable” bits to make it look pretty. They lay them out in the new background panel. They put everything in about the right place.
- The two developers sit at one computer, and tweak the UI. The programmer makes sure the anchors and scaling modes are set correctly, while the artist makes sure the programmer doesn’t align the UI like a python script. The programmer also notices the weight of some of the assets and creates a few optimization tasks.
Of course, it’s never quite perfect first time. During polish and iteration the programmer switched to an optimised gradient shader to shave 6mb off the build size, and the art team tweaked the positioning and appearance of the elements. Major work was pair-developed again, or tested and given a seal of approval by the other team.
Just a little refactoring
In recent updates, there’s been one big change “under the hood” so to speak, which has absolutely no effect for you, as you play Antidote. But for us…
…it solved a problem we’ve fixed a million times. It started from one specific bug (where the game “wouldn’t end”) that kept recurring. As the comic above suggest, we just kept fixing the problems that resulted from fixing the problems…
What’s changed in this update is that we dug right to the root of the problem, and found several bits of Ancient Code(TM) from the earliest versions of Antidote. These were a scattering of booleans and enumerations with trifling names like “isPaused”, “onPauseScreen” and “gameRunning” – ok, ok, not so trifling! We’ve known about this smell for a while, but we knew… touching it would have far reaching consequences. In addition to these state variables, there were 3 or 4 places where “levelStart()” “levelEnd()” and other similar things were handled, rather than one central place.
This refactoring took the heart of the system and ripped it out, far more ruthlessly than we would normally do. So, how did we do it?
- Testing the existing system and documenting exactly how it works, and how it should work after the refactoring. (so that at the end, we can verify it! We weren’t totally ruthless!)
- Exploration of the existing system. Understanding exactly how it works, and why. This was probably the longest phase. It involved stepping through every single function explicitly, tracing the exact execution of various states. Simple renaming was done here when existing names were wrong (with special care around serialized values, though thankfully not too many of them here).
- Destruction!!!! Ok, it’s not as exciting as it sounds. But destruction is vital to the refactoring process. We systematically destroyed (usually by merging) functions and variables, until there were clear single points in the system for checking state and when things “happened”. The new system has a CurrentScene enumeration and a CoreGameLoop enumeration (including pause!), and all versions of “game won” have been merged into a single function. (more on this below). The same is true for all significant events – pause, resume, end, lose, start, load, etc.
- Changes. The most obvious change was from a “list of functions called on game end” to a single event invocation of a GameEnded event. This matches with the style we use throughout the game, and more importantly it allows the core game loop to be blissfully unaware of everything else. Now, if the pause menu doesn’t show up… it’s because the pause menu didn’t correctly register to the events. Definitely not because the core game didn’t fire the “OnPaused” event (well, it could be it didn’t fire the OnPaused event: but then “pause” wouldn’t be a thing… very philosophical.)
- The final phase of the refactor was extensive testing. Actually, this phase is still ongoing – a bunch of latent dragons rose their heads. Most of these dragons were highly specific issues that we’d been unable to reproduce reliably. Being able to reproduce them, they’re now all fixed. Oh, and to make sure they never came back, we scattered our code with assertions.
Beginning to end feature : global high scores
First off, let’s make one thing clear : we’ve not really reached the end of this feature. But we’ve reached an end – there will be improvements and additions as we play with the system, but for now it’s done. Now we’ll talk you through the end to end process of high scores in Antidote…
The first step in the high score process was to lay the groundwork while achieving a basic “fun” measure. The most important thing in Antidote, of course, is phagocytozing infections! So, with this in mind, we set up a flexible but simple “scorecard” data structure, and made friendly units notify the data structure when they completed phagocytozing something.
Stem cell health
Of course, digesting enemies isn’t the only goal and, if it was, it might lead to quite boring leaderboards… after all, it’s not like you can kill more than all the incoming enemies (…or can you ;D). To solve this “boringness” we added several other scientifically inspired scoring values. Firstly, and most importantly, we added bonuses for certain objectives (like taking no stem cell damage) and a point reduction based damage to the stem cell. We then added a small number of points for generating sugar efficiently. Finally, we added small bonus point boosts for various “scientific kills” – such as chaining together Tea Zell, Basophil and Snot Factory. The result of these changes was that we can’t actually determine the maximum score on a level (it’s designed to be a “wicked problem”).
In addition to this, we introduced support for both “base difficulty” -a feature coming soon – and procedural loadout based difficulty. Which means, yes, you’ll be able to take on even harder challenges than “hard”. Think you can handle a world ruined by misuse of antibiotics…? The loadout will test your hypothesis…
The final element of the scores is almost invisible compared to the amount of work it takes to get it done right. That’s the ability for you to seamlessly compare your scores with the rest of the world, and, in the future, with the people you actually want to compare with (e.g. friends!). In case you’re interested, we’re using Playfab to handle our high scores, which should simplify the work for us when it comes to adding more of these features. For now, we let you stand off against the world number 1, and also the “average high score”, a pretty neat measure of how well you’re doing that we saw worked well in I Love Hue.
The final stage (which is still being polished up) is making the high scores both functional and pretty. Kind of like Ikea, but more Finnish… This will include visualizations of how you’re doing, and easy ways to compare and compete with others.
Thanks for reading! Let us know if there’s a topic you’d like us to dive into in the next development blog. We’ll try to accommodate 🙂
Of course… if you haven’t already, remember to download and play Antidote on Google play. It’s available in early access, with a discount on the infinite energy until we launch… Speaking of which… you might want to note down 13th November in your calendar…!
Share this Post