Friday, 13 January 2012

The code of Awesomenauts' upgrade system

Last week I explained how our designers can create tons of upgrades for Awesomenauts using our Settings system. However, I didn't say a thing about how we actually made that work. We used some fun template and functor tricks there, so I figured it would be nice to show how it was built to work elegantly, without making our code a big mess of checks for upgrades.

Warning: this blogpost contains hardcore programming awesomeness! If you are an artist or game designer and fear templatised meta-programming functors might disintegrate your brains and/or explode your head, then I advice skipping this specific blogpost and coming back next week!

First lets have a look at how things would work without upgrades. Note that to keep things readable, I only show the essential bits of code, and I have greatly simplified all the code examples here.

The function loadSettings() translates from a variable name to a string, so that we can actually load the variable from a text-file. Doing this for every setting in the game is a bit cumbersome, but necessary because variable names in C++ are not real things and become simple memory addresses when the code is compiled. Some spot is needed where we have the variable name as a string in code, and I chose to put all of those in a single place in the function loadSettings(). I guess it might be possible to write a macro that does the same thing, but I don't know about that myself.

The key thing to notice here is how easy it is to use the settings in gameplay code, as can be seen in the function handleWalking(). We are going to add upgrades to this and out most important goal for that, is to keep the gameplay code this simple.

Since the upgrade system requires each setting to be able to have several upgrades and combine them, we switch the settings from a simple float or bool to a real class.

I want basically the same behaviour from floats, bools, strings and any other types that need to be upgradeable, so I used templates for this. Experienced programmers probably know what templates are, but for those who don't: in the code below the type T is left open and it can be interpreted as whatever we say it is. So we can make an UpgradeableSetting where T is a float, or where T is a bool, or something else. C++ can compile this code with any type we want it to.

The load() function fills baseValue and upgrades by looking for all the occurrences of the setting in the settings file.

The get() function returns the value that this setting has for the Character that is passed to it. The Character is needed here, because we need to check which upgrades he has and return a different value depending on that. Its code is rather simple, but seeing it might help understanding how this works:

Note that this code only handles upgrades that replace the standard value, so upgrades that are marked with @ in the settings file. For simplicity, I have left out upgrades with + (which add to the original value) that I discussed last week.

Now that we have this, we can make some slight modifications to our gameplay code to use the upgraded value:

This all works fine, but I am not all too happy with that function get(). Normally it would be perfect, but there are so many places in the gameplay code where settings are used, that I would rather not add get() to all of those.

Now C++ happens to have a nice solution for this. I rarely see it used outside STL, but it has an absolutely awesome name: "functor". I love that name! Got to be a Decepticon! Anyway, a functor is a construction where you can call an object as if it is a function. With a functor, our little class looks like this:

Now we can do the exact same gameplay code as before, but we can simply leave out the get call. For the rest, this code does the exact same stuff. Because we use the settings so often, this is a nice improvement.

Since we have so many settings in our game, typing the entire type every time is too much work, since that would be something like UpgradeableSetting every time. We simply solved this using typedefs. At Ronimo we normally try to avoid typedefs, because they hide the real type of a value and I would rather know exactly what I am dealing with. However, because there are thousands of settings, I consider this an exception and use typedefs anyway:

Now that we have this new class, let's have a look at the same code we had before, but now with the upgrade system added to it. Note how it has hardly changed, but can do so much more now!

So, that's it. It's just a little bit if code, but this makes the settings system way more powerful and flexible. It is definitely a great improvement to the RoniTech (the engine we created here at Ronimo Games and that we used to build Awesomenauts). In fact, its uses are so diverse that we also use it for many other things than the upgrades that the player can buy in the shop. So let's conclude this blogpost with this nice example of that: