Saturday, 15 July 2017

What we learned improvising a live game soundtrack

Recently I've done a couple of music performances that were quite different from any live game music performance I've ever seen. We let someone play the games Journey and Ori And The Blind Forest live on stage and we improvised a completely new soundtrack on the spot, reacting to whatever the player was doing. This was a unique experiment for us and we learned a lot about what works and what doesn't, so today I'd like to share our approach and experiences.



Usually when game music is performed live the original soundtrack is replicated by musicians on stage. We however completely ignored whatever the original soundtrack had sounded like and improvised based on what we saw. The resulting music is different with each performance and sounds nothing like the original soundtrack. The fun of improvisation is that it's entirely in the moment. You don't know what's going to happen and it will never happen in the exact same way again. A truly live experience! Sometimes we might make mistakes, but sometimes we might also improvise the most intensely awesome music. Doing this in response to a game that's being played live is really exciting! Here's a video compilation of our performance at Animecon in the Hague last month:


Some people might know my game Cello Fortress. While there are similarities between Cello Fortress and our live game soundtracks, they really are very different. In Cello Fortress I control the game by playing cello. The cello is a game controller and the game is designed around this unique input device. On the other hand, the live game soundtracks in this blogpost are for existing games: the music doesn't control the game at all. We just respond musically to whatever happens on the screen. This is a bit less extreme than Cello Fortress, but as far as I know it also hadn't been done before and it's a different, interesting approach.

Before I continue, a short word about who 'we' are. The original idea for this performance came from Bram Michielsen and Zuraida Buter, who asked us to improvise live music to Journey at the Screenshake festival in Antwerp in 2015. After that we did a couple more performances and added Ori And The Blind Forest to the mix. I did these performances together with Rene Derks, who played djembe, keyboards and pads. Rene is a lecturer in game design and production at the NHTV in Breda and previously worked on Loco Roco 2 and Gatling Gears. I myself played cello and in my daily life I'm lead programmer at Ronimo Games, mostly known for our games Awesomenauts, Swords & Soldiers and the original version of the student game De Blob. We're not professional musicians but music is a rather serious hobby for both of us.

Improvising interesting music is difficult, and doing so together with other musicians even more so: whatever you're playing needs to not just sound good on its own, but also sound good with what the other musicians are playing. This means you can't just suddenly change the rhythm or mode unless you have a lot of experience improvising together. The specific combination of djembe and cello helps a lot here. It's only two instruments and they both carry a very clear responsibility: the djembe is in charge of rhythm and the cello is in charge of melody.

That gives me total freedom to change the mode or chord without needing to worry that this will sound bad with the other instrument. This kind of freedom is extra important here because we need to respond not just to each other and the audience, but also to the game. It also leaves room for great accidentals: a fast djembe rhythm can work great with slow cello notes and vice versa, so as long as we change intensity at the same moment it's okay if we don't always agree on whether we're increasing or decreasing the intensity.

When preparing for our first gig we started by choosing specific parts of each game, looking for areas with enough of an arc to make them interesting musically. The part of Journey we chose starts relaxed in the desert, then goes down into a darker valley with machinery, then has a very fast descent and finally goes through a dark, scary area that ends in a climax as the player flees from flying monsters. This gives us plenty of room for variation and for having an emotional arc in the music.



The next step was to figure out the general mood we wanted in each area and to come up with some ideas for the kinds of things we could do there musically. We looked up some longplay videos of Journey and improvised over those. We didn't actually write any music but we did end up with a good idea of what kind of things would work were. We also decided where Rene would play djembe and where he would use atmospheric synthesizer pads. In some cases we defined rather specific musical ideas, but in most it was just general directions. For example, in the haunted caves in Journey we didn't have more than this idea: "the caves get low dark dissonant cello and the djembe keeps silent in the beginning and joins after a while."



We wanted to make it very clear to the audience that we were truly responding to the game and not just playing pre-composed music, so we looked for some player actions we could respond to. Journey turns out to be great for this: jumps can take as long as 10 seconds so they are long enough that we can respond to them musically. During a big jump I might for example play an upward flurry on cello, hold a long sliding note during the fall and then get back into the rhythm as the player lands on the ground. Musically it feels slightly childish to play an upwards line as the player jumps upward, but audiences responded very well to this. We could clearly hear in their responses that these were the moments where they truly 'got' it.

To tie it all together musically and to have some kind of theme that the audience can recognise and remember we choose a couple of events for which I wrote small melodies. This is very much like in Zelda, where a short melody plays whenever you open a chest. In Journey we chose the finishing of each level as the key moments and in Ori And The Blind Forest the opening of doors. I quite liked the melody I came up with for Journey so I've also reused it as an attack melody in Cello Fortress.


This kind of performance isn't just about the music, but also about the person who sits on stage playing the game. Figuring out what's best here was a challenge. On the one hand the most pure expression of the concept would be to have a random player from the audience play the game without any preparation. However, this is risky since the player might not know the game and get stuck or need to retry an area a dozen times. That's going to be really boring to watch. On the other hand a prepped player feels like faking it. So we ended up making sure we had a player who knew the basics of the game, but who hadn't played with us before so that we both didn't know what was going to happen exactly.

This worked well in general but during one performance it backfired. The player got stuck in a dark cave in Journey and walked around aimlessly for minutes. It's difficult to improvise anything good for that and I saw some people from the audience leave during that section. I think next time it might be better to look for a player who knows the game better and accept that the player might rush through some sections. That's probably more fun than watching someone be stuck, trying to figure out what the jump button is or looking in every nook and cranny to find every last item...



While Journey is a slow game in which we could respond to individual actions by the player, Ori And The Blind Forest is way too fast for this kind of approach. For example, combat often takes only 2 seconds. We tried switching to combat music every time an enemy was encountered, but the music alternating so much just didn't sound good. Music needs to flow. So instead we decided to respond to individual events very little and mostly just have different types of music per area. The player can quickly go from place to place but generally stays in an area for at least 30 seconds so it's okay if the music changes whenever you go in or out of an area.



In Ori We also took a more melodic and less improvisational approach: Rene played keyboards here and we defined most of the chords and arpeggios beforehand. This works, but it felt a bit too forced to our liking. Our goal with this project was to truly improvise and after doing a couple of performances especially Ori felt a bit stale.

Doing these performances was really exciting and we've learned a lot about what works and what doesn't. Our next experiment with this idea is going to have a different structure. At the Abunai convention we want to try a smaller, more cosy setting where we don't decide beforehand which games are going to be played. Instead we're just going to bring a list of suitable games and let the audience decide whatever they want to play and we'll improvise to that. If someone happens to be carrying their own Switch they could even hook that up and play their own games! This means we can't prepare beforehand, which will probably result in less polished music, but at the same time it will be much more spontaneous so I expect more fun, weird things will happen. I also hope that not being on a stage will make the interaction between the audience, the players and us more direct. I'm really looking forward to trying out this new experience!

Monday, 10 July 2017

The Ronimo coding style guide

Last week I showed the coding methodology that we use at Ronimo, which describes our workflow. This week we'll have a look at what our actual code looks like, which is defined in our coding style guide. The idea behind our style guide is that if all code is formatted in a similar manner then it's easier to read and edit each other's code. For example, reading a different bracing style or naming convention usually takes some getting used to, and we avoid that altogether by having a strict style guide that all programmers at Ronimo must follow.



I haven't seen a whole lot of style guides from other companies, but from what I've heard our style guide is quite a lot stricter than what's common elsewhere. I'm not sure whether that's true, but I can totally imagine it is since I'm known for being precise (sometimes maybe overly much). Our style guide isn't set in stone though: there's an exception to every rule. If our style guide really doesn't make sense in a particular situation, then it's okay if a coder ignores it somewhere. Just as long as there's a good reason for it.

Some of the choices in this document are quite arbitrary. Sometimes alternatives would have been equally good, but without clear choices you can't have similar formatting for all programmers. This is especially true for bracing style. I know that this is a heated subject and while I have a clear preference, good arguments can be made for alternative bracing styles. (Still, it would have been nice if advocates of the other major style wouldn't have called it the One True Bracing Style... ;) )

A key element in our style guide is that I want code to read like English wherever possible. Variable and function names should be descriptive and only the most commonly known abbreviations are allowed. Brevity isn't a concern of mine, readability is.

Not all points in our style guide are about formatting though. Others are about actual language constructions. C++ is a rich language with an enormous amount of possibilities, but quite a few are too confusing or have too much risk of bugs to actually use. For example, nesting ternary operators is totally possible in C++, but the result is rarely readable so we disallow it altogether.



Our style guide also contains some rules that are intended to make cross platform development easy. On consoles you usually can't choose your compiler, so you have to work with whatever Nintendo, Sony or Microsoft have chosen, including the limitations of their compilers. We've researched what features of C++ each supports and have forbidden some of the newer C++ constructions that we think might not work on one of the consoles. Since we're not currently actively developing on some of the consoles we went by documentation only though, but I'd rather be too strict here than too lenient.

Another thing you can see in our style guide is my dislike for complex language constructions. C++ allows for some highly impressive stuff, especially using templates and macros. While I appreciate that these tricks can sometimes be really useful, I generally dislike them whenever they become too difficult to read. In the rare cases where these tricks are truly needed they are allowed, but usually I prefer if complex language constructions are avoided.



One particularly hotly debated point in coding styles is whether to mark class member variables. If the Car class has a float speed, do we call that speed, mSpeed, _speed or something else still? I've chosen to simply call this speed. Here too the reason is that I want code to be as similar to English as possible. The more prefixes and underscores there are, the further it moves away from natural language and the more difficult it becomes to just read code and understand it like you would text.

However, there's a good reason many programmers mark their member variables: it's very important in code to know whether a variable is a class member, a function parameter or a local variable. This argument is true, but I think we have that covered elsewhere: our style guide contains limitations on how long a class or function can be. If a function is short and fits on one screen, then it's easy to immediately see where variables are coming from. I think if classes and functions are short enough, then markers for member variables aren't really needed.

Note by the way that the rule for how long functions and classes can be is the one broken most internally. Sometimes it's just really difficult to split a class or function in a neat way. In the end the goal of our style guide is to produce clear code, not to hinder that by forcing clumsy splits. Still, there's real skill in figuring out how to neatly split classes and functions into smaller, more maintainable units, so if you're not super experienced yet then more often than not a neat split is possible and you just don't see it. In my opinion the ideal size of a class is anywhere between 200 and 400 lines, but a rule that strict isn't feasible so what's listed in the style guide is more lenient.

Now that I've discussed the reasoning behind our coding style guide, let's finally have a look at what it's actually like!

The Ronimo Coding Style Guide


There is an exception to every rule. However, keep to these rules as much as possible to maintain a constant layout and style for all the code. A lot of this is taste and a constant code layout requires setting aside one's own taste and keeping to these rules. Once used to it, it's easier to read such code.

When working in another language than C++, try to keep as close as possible to the C++ coding standard, but of course within reason. There are some specific notes on C# at the bottom as well.

C++


  • All code and comments are in Great Britain English. Not American English. So these are right: colour, centre, initialiser. These are wrong: color, center, initializer.
  • Every comma is followed by a space, for example doStuff(5, 7, 8)
  • Spaces around operators, for example: 5 + 7 instead of 5+7
  • Tabs have the same size as four spaces. Tabs are stored as tabs, not as spaces.
  • Functions and static variables are in the same order in the .h and .cpp files.
  • Use #pragma once instead of include guards (we recently switched to this so you will still see a lot of old include guards in our code).
  • Try to make short functions, preferably no more than 50 lines.
  • Avoid making very large classes. Split a class whenever you anticipate the class will grow a lot and you can cleanly split it. In general, try to keep class sizes below 750 lines. However, don't split a class if the split would result in messy code. Some examples of messy divorces are: too tightly coupled classes, friend classes and complex class hierarchies.
  • Don't write lines that are longer than what fits on a normal 1920*1080 screen (keep in mind that the Solution Explorer is on that screen as well).
  • When splitting a long line over several lines, make sure indentation is correct with the relevant parenthesis. For example like this:
    myReallyLongFunctionName(Vector2(bananaXPos + xOffset,
                                     bananaYPos * multiplier),
                             explodingKiwi);
  • Use forward declaration where possible: put as few includes in header-files as possible. For example, use class Bert; instead and move the #include "Bert.h" to the .cpp-file.
  • Includes and forward declarations are ordered as follows:
    • Start with all forward declarations from our own code (in alphabetical order)
    • Then includes from our own code (in alphabetical order)
    • One empty line
    • Then per library:
      • Forward declarations from that library (in alphabetical order)
      • Includes from that library (in alphabetical order)
  • The include of the .h-file belonging to a .cpp file is always at the top.
  • Don't define static variables in a function. Use class member variables instead (possibly static ones).
  • Everything must be const correct.
  • Names of variables and functions should be descriptive. Long names are no problem, undescriptive names are. Only use abbreviations if they are very clear and commonly known.
  • The first letter of classes, structs and enum type names is a capital. Variables and functions start without a capital. Each further word in a name starts with a captical. Don't use underscores ( _ ) in variable names. So like this:
    class MyClass
    {
        void someFunction();
        int someVariable;
    };
  • Member variables aren't marked with something like an m in front of them or an _ behind. Functions should be short enough to keep track of what is declared in the function and what in the class. Don't mark member-variables with this-> either.
  • Implementations of functions are never in .h-files.
  • Templatised functions that can't be implemented in the .cpp file are implemented in an extra header file that is included from the main header file. Such a class can have 3 files: MyClass.h, MyClassImplementation.h and MyClass.cpp. So like this:
    class MyClass
    {
        template <typename T>
        void doStuff(T thingy);
    };
    
    
    #include "MyClassImplementation.h"
  • Start template typenames with T. If more info is relevant, you can add words after that, like TString.
  • Several classes can never be defined in the same header file, except if a class is part of another class (i.e. defined inside the other class).
  • Between functions there are two empty lines (in the .cpp file).
  • Use empty lines to structure and group code for readability.
  • Add loads of comments to code.
  • Write a short explanation of what a class does above each class. Especially make sure to explain relations there (e.g. “This class helps class X by doing Y for him”).
  • Curly braces { and } always get their own line, so they aren't put on the same line as the if or the for. They are also never left out. The only exception is if you have lots of similar single line if-statements right beneath each other. In that case it's okay to put them on a single line. Like in this example:
    if      ( banana &&  kiwi && length > 5) return cow;
    else if ( banana && !kiwi && length > 9) return pig;
    else if (!banana && !kiwi && length < 7) return duck;
    else                                     return dragon;
  • When writing a do-while function, put the while on the same line as the closing brace:
    do
    {
        blabla;
    } while (bleble);
  • Indent switch statements like this:
    switch (giraffeCount)
    {
        case 1: text = "one giraffe";  break;
        case 2: text = "two giraffes"; break;
        case 3:
            // If it's more than one line of code
            doStuffOnSeveralLines;
            text = "three giraffes";
            break;
        case 4:
        {
            // Can add curly braces for readability
            int x = getComplexThing();
            text = "quadruple giraffe";
            break;
        }
    }
  • Function parameters have the exact same names in the .h and the .cpp files.
  • If a function parameter and a class member variable have the same name, then either think of another name, or add an _ to the end of the function parameter. For example like this:
    void setHealth(float health_)
    {
        health = health_;
    }
  • Precompiler instructions (everything starting with #) should be kept to a minimum, except of course for #include and #pragma once.
  • Don't write macros.
  • Variables inside functions are declared at the point where they are needed, not all of them at the start of the function.
  • In constructors, prefer using initialiser lists over setting variables in the body of the constructor. Each initialisation in the initialiser list gets its own line. Make sure variables in the initialiser list are in the same order as in the class definition in the .h-file.
  • Don't use exceptions (unless you're using a library that requires it).
  • Don't use RTTI (so don't use dynamic_cast). RTTI costs a little bit of performance, but more important is that RTTI is almost always an indication of bad object oriented design.
  • Use reinterpret_cast and const_cast only when absolutely necessary.
  • Don't commit code that doesn't compile without errors or warnings (and don't disable warnings/errors either).
  • Don't commit code that breaks existing functionality.
  • No global variables. Use static member variables instead.
  • Use our own MathTools::abs instead of std::abs. This is because std::abs is inconsistently implemented between platforms, causing hard-to-find bugs.
  • Always use namespaces explicitely. Don't put things like using namespace std in code.
  • Never ever even think of using go-to statements. We have a thought detector and will electrocute you if you do.
  • Don't use the comma-operator, like in if (i += 7, i < 10)
  • Don't use unions.
  • Don't use function pointers, except where other libraries (like STL sort) require it.
  • Only use the ternary operator in extremely simple cases. Never nest the ternary operator. Example of a simple case where it can be used:
    print(i > 5 ? "big" : "small");
  • When exposing a counter for an artist or designer it starts at 0, just like it does for normal arrays in code. Some old tools might still start at 1 but anything newly developed for artists starts at 0.
  • When checking whether a pointer exists, make this explicit. So use if (myPointer != nullptr) instead of if (myPointer)
  • Use RAII (Resource Acquisition Is Initialization) where possible. So instead of creating a separate function initialise(), fully initialise the class in its constructor so that it's never in an incomplete state.
  • Write the constructor and destructor together: write the corresponding delete for every new immediately, so you don't forget to do it later on.
  • If you add temporary debugging code, then add a comment with QQQ to it. Never commit code with QQQ: remove the debugging stuff before committing.
  • If you want to leave a marker for something that needs to be done later on, use QQToDo. If it's something you need to do yourself then add your letter to it, for example QQToDoJ. Only commit QQToDo if it's really impractical to finish it right away.
  • Class definitions begin with all the functions and then all the variables. The order is public/protected/private. This might sometimes mean you have to use the keywords public/protected/private several times in a single header file (first for functions, then again for variables).
  • A class starts with its constructors, followed by its destructor. If these are private or protected, then they should still be at the top.
  • When something has two options, but isn't clearly true/false, then consider using an enum class instead of a boolean. For example, for direction don't use bool isRight. Instead, use enum Direction with the values Left and Right.
  • Instead of std::string and std::stringstream, use our own classes RString, RStringstream, WString and WStringstream (these tie into our own memory management).
  • The variable float time always stands for the time since the last frame, in seconds. If time means something else, make this explicit, for example by naming it timeExisting instead.
  • Make sure all code is framerate-independent, so use the variable float time often to achieve this.
  • Make the intended structure explicit and clear. For example, avoid doing things like this:
    if (yellow)
    {
        return banana;
    }
    return kiwi;
    What was really intended here, was that depending on yellow, either banana or kiwi is returned. It's more readable to make this explicit like this:
    if (yellow)
    {
        return banana;
    }
    else
    {
        return kiwi;
    }
  • Use nullptr instead of NULL.
  • We only use auto for things like complex iterator types. For everything else we explicitly type the type.
  • Use range-based for where applicable, for example:
    for (const Banana& ultimateFruit : myList)
    (Note that it's important to type that reference when not working with pointers, since otherwise the Banana would be copied in this case.)
  • Use override and final wherever applicable.
  • If a function is virtual, the virtual keyword should always be added to it, so not only in the parent class, but also in each version of the function in the child classes.
  • Use strongly typed enums, so with the class word. Their casing is the same as for classes. For example:
    enum class Fruit
    {
        Banana,
        Kiwi,
        ApplePie
    };
  • Don't use rvalue references (&&) unless you really really really need them.
  • Use unique_ptr wherever possible. If an object isn't owned, then it's stored as a normal pointer.
  • Avoid creating instances of complex types in the initialiser list. Simple copying and setting goes into the initialiser list, while more complex code like calling new goes into the body of the constructor. Here's an example of how that works:
    FruitManager::FruitManager(Kiwi* notMyFruit):
        notMyFruit(notMyFruit)
    {
        bestFruitOwnedHere.reset(new Banana());
    }
  • Only use shared_ptr in cases where shared ownership is truly needed. In general, try to avoid shared ownership and use unique_ptr.
  • Lambda's that are too big for a single line are indented like this:
    auto it = std::find_if(myVec.begin(),
                           myVec.end(),
                           [id = 42] (const Element& e)
                           { 
                               return e.id() == id;
                           });
  • Don't use MyConstructor = delete (this doesn't seem supported on some compilers we might need).
  • Don't use the C++11 feature for list initialization (this doesn't seem supported on some compilers we might need). So we don't use this:
    std::vector<int> thingy = {1, 2, 3};
  • Don't use any features that were added in C++14.

C#

  • Variables at the top of the file instead of at the bottom. We do a strict split between functions and variables (like we do in C++), so here it's first all variables, then all functions.
  • async functions must always end their name with Async, for example eatBananaAsync.

That's it, our coding style guide! ^_^ While I imagine you might disagree with a bunch of the specific rules, I think it's useful for any company that does programming to have some form of a style guide. Our style guide can be a good starting point for creating your own. I'm quite curious: what's the style guide like at your own company and how do you like it? Do you have one at all?

Sunday, 2 July 2017

The Ronimo coding methodology

I'm a strong believer in working in a structured way. As a game programmer you need to build complex systems and just going with the flow isn't good enough. That's why I've written two documents to describe how we code at Ronimo, which every programmer and intern gets to read on their first day. Our Methodology document explains the workflow while the Style Guide explains the layout of our code. Today I'd like to show our Methodology and describe the reasoning behind the rules in this document.



Note that the contents of this document aren't very original: most if it is a combination of common agile practices that I like.

Let's start by having a look at the actual methodology document:

The Ronimo coding methodology


General method of implementing a new feature:

  1. Analyse what should be made. Discuss approach with end users (usually artists and/or designers) and lead programmer.
  2. Split into small tasks of a day at most.
  3. Make a basic planning for the small tasks. Put the core of the functionality and the most difficult parts to implement at the front of the planning.
  4. Implement each small task.
  5. Evaluate result with end users and lead programmer.
  6. Explain to end user what the new functionality does, so that it's actually used.

Implementing a small task:

  1. Analyse what should be made.
  2. Verify that related existing code is bug-free (perform tests!).
  3. Research and experiment with any new technology required for this feature.
  4. Code-design for new functionality. Don't focus too much on theoretical future use, but do keep it in mind.
  5. Refactor existing code to make room for your new feature.
  6. Test whether older functionality is still intact.
  7. Implement new functionality.
  8. Test whether new functionality works.
  9. Finish code: add comments, clean up code and check the destructors.
  10. Test whether new functionality still works.
  11. Test whether older functionality still works.
  12. Evaluate result. If showable, also show this intermediate result to the end user.

Various other rules:

  • Keep a personal list of all the small things that you should do. If you come across some issue that you can't immediately take care of, or if someone asks for a feature that you promise to build later, then write that down in your list. Don't think you can always remember everything.
  • Never continue to work on something if you have encountered a crash or major bug. Always fix the crash first.
  • "Premature optimization is the root of all evil" (Donald Knuth). First implement a new feature in the simplest / clearest way possible, then analyse whether it works, then analyse the performance and only if necessary implement optimisations.
  • Don't worry too much about making your code in such a way that unknown future extensions might be easy to add. When new functionality is needed, the code can still be refactored. Of course, if it's little work to do, then do make things as generic as possible.

Most of these are quite clear, but it's interesting to discuss some of the reasons behind these rules. Often enough I've seen coding interns have loose ends everywhere in their code because they were working on five things at the same time and forgot to test, clean up and finish some of them. That's why our coding methodology requires that you finish what you were doing before you get to the next thing. This is also why I require that big tasks are split into smaller ones: a person can only remember so much and the more things you're working on simultaneously, the bigger the chance that you'll overlook something important.

At the same time I prefer the agile way: only make what you actually need and expand the codebase as you go. During early development you don't know all the features you'll need, nor do you know for all problems how you'll solve them. However, adding things one at a time will often make code bloated and without focus, and will muddy class responsibilities. That's why code needs to be refactored often. Refactoring requires discipline. Often you can hack in a new feature in just a few hours, or first spend half a day refactoring to make room for that feature in a good way. It's easy to skip or postpone refactoring, but doing so often produces unworkable code in the long run. That's why refactoring is an explicit step in our methodology.

Focusing only on one small new thing at a time shouldn't be taken too literally though. While I think it's important to not work on other things until what you're doing is finished and clean, that doesn't mean you shouldn't look ahead. When building something complex it's important to think about how you're going to make the whole system work. I once had an intern who had to rebuild a large part of a tool because he had taken our coding methodology too literally and hadn't thought ahead at all. The most complex features of the tool were not possible at all with what he had build. The key here is to find the right balance between thinking ahead and focusing on one thing at a time.



Another staple of mine is that programmers need to communicate directly with designers and artists. We believe extensive design documents are rarely a good idea, so the only way to know what's needed exactly is to talk to the designer or artist who needs a new feature. Often that person hasn't defined the exact details of the feature, so the coder needs to discuss it with them and think about the caveats, also from a design and art perspective. To smoothen this communication it helps a lot if the programmer has a little bit of experience in design and art, but even if that's not the case I think it's the programmer's job to talk the language of the designer or artist, not the other way around. It's really difficult for a designer to speak code, but a programmer should be able to talk about his work in comprehensible English (or Dutch in our case).

One thing that's surprisingly missing in our Coding Methodology is unit testing. We have a strong focus on testing our own code extensively, but the document doesn't say you need to write unit tests. This is because I think gameplay is often too chaotic and unpredictable to test well with unit tests. Certain things are testable with unit tests, but the bugs we encounter are often not things where I can imagine how a unit test would have found them. Often it's things that function fine but result in undesired gameplay.

I do realise that not making units tests makes us more vulnerable to bugs than a team that always writes unit tests, so we emphasise that if a crash or major bug is found, it needs to be fixed right away. We might have more bugs than software developers who write extensive unit tests, but at least we fix them quickly. I do think we ought to use unit tests more for things like server architecture. Unit testing isn't in our blood at all and it probably should be at least a little bit. I'm curious though: do you use unit tests for your gameplay or engine code?

Regardless of whether you agree with the particular rules in our methodology document, I think it's important that all programmers think about their workflow. Just doing whatever you feel like doing isn't good enough. Discipline and structure are important for anyone who works on larger, more complex systems. What's you're coding methodology like? If you happen to work at a company, is there an official document like the one I've shown today?

So that's it, the Ronimo Coding Methodology! Next week I'll show our Coding Style Guide, which is quite a bit more strict than most coders are used to.

Sunday, 29 January 2017

The many meanings of the confusing word 'lag'

'Lag' is one of the most confusing words used in gaming: it's used to describe wildly different problems. This makes communication difficult: when a player is complaining about 'lag', what's his problem exactly? Is he talking about his internet connection, framerate or input delay? So today I'd like to list the different meanings of the word 'lag', both to reduce confusion and to explain why all of them are important. I've grouped them into three main types.

It can be difficult to know exactly what's going on, but it would already help a lot to always at least specify which of the rough groups you mean, instead of just saying 'lag'.

Note that this post is not specifically about our own games. These problems can happen in most games, although the exact impact depends on how the game was been programmed.

Internet and connectivity issues


  • High ping: ping is the time it takes for an internet packet to travel from your computer to another. In most games the gameplay still feels smooth with high ping, but you'll often start seeing oddities like getting shot by someone who isn't in view anymore, or your shots missing despite perfect aim. The faster the game, the more problematic high ping is.
  • High jitter: jitter is an often overlooked aspect of ping. Jitter is when some packets take longer than others. Jitter often causes stuttery movement in games. You might have low average ping but still experience stutters because of high jitter. (Packet loss often looks the same as an extreme type of jitter so I'm not listing it separately here.)
  • Pingspikes: this is when occasionally the connection becomes extremely slow or even drops altogether for a few seconds. In most games ping spikes result in serious problems, like your controls not working anymore for a while, getting a lot of damage at once at the end of the spike, or even being disconnected.

All of these problems can have many causes, like a slow internet connection or your roommate downloading a torrent over the same connection. There's one common cause that's relatively easily solved though: WiFi. WiFi increases ping, causes jitter and packet loss and, worst of all, pingspikes. If you can, never play online games over WiFi. Connect to your router with an ethernet cable instead. Check this blogpost from the League Of Legends devs to see how bad WiFi really is.

Delay between input and seeing the result


  • Input delay: the time between you pressing a button and the game actually processing it. Games usually process input only once per frame, so the higher the framerate, the lower the input delay. Drivers and the operating system will also cause a little bit of input delay, or a lot if it's a complex controller like the Kinect camera.
  • Input jitter: this is when the input delay varies. This can happen if you press the button just before the frame update and then just after. If the game runs at 30 frames per second, this can create a 33 milliseconds difference in input delay. The higher the framerate, the lower this kind of jitter wil be.
  • Rendering delay: once the gameplay has processed your input, it needs to be rendered. The GPU is usually at least one frame behind, and in some cases the game itself can be as well. For example, if you set the Multithreading Mode in Awesomenauts to Higher Framerate, then the graphics are always one additional frame behind on the gameplay, causing additional input delay. Again, the higher the framerate, the smaller this effect.
  • Monitor delay: this is usually the most noticeable cause of delay between pressing a button and seeing the result. Many televisions have all kinds of clever processing to make the screen sharper and smoother, but this can add over 100ms of lag. That's a lot and can be very noticeable. When using a television you should always switch it to Game Mode to solve this. Computer monitors generally don't have this problem.

Framerate problems


Low framerate is also often called "lag". Players sometimes don't realise that there are different types of low framerate. Being precise when describing your problem helps a lot if you ever ask anyone for help with how to solve your framerate problems.

  • Constant low framerate: this usually means the videocard or processor is not fast enough to run the game.
  • Occasional long stutters: the game sometimes freezes for half a second or more. This might be caused by problems reading from the harddisk, so switching to an SSD might help. In general though, a game should be programmed in such a way as to do disk reads asynchronously and thus not freeze like this, but sometimes this is really difficult to achieve.
  • Regular short stutters: every second or so the game skips a few frames. Such stutters are really short but can be very noticeable, especially during smooth camera movements. This might be caused by running something else on your computer (like a virus scan or a lot of tabs in your internet browser). It can also be caused by the game not balancing the work well enough between frames, causing some frames to take longer.

Framerate problems and input delay are both often decreased by turning off VSync. This does come at the cost of getting screen tearing though. Which you prefer is a matter of personal taste.

Sunday, 15 January 2017

Adding more depth to 2D levels using scissor rectangles

While creating levels for Awesomenauts and Swords & Soldiers 2 our artists often struggled with how to create more depth. All those background elements from one area would slide into view in other parts of the level, limiting how deep the backgrounds could be. For the Starstorm level in Awesomenauts I've added a trick to our editors that solves this problem: scissor rectangles. It's a neat little trick that not only makes Starstorm have backgrounds with much more depth, but also makes rendering them a lot more efficient. So let's have a look: what's the problem exactly, and how do scissor rectangles solve it?

In a purely 2D game like Awesomenauts there's no real perspective: backgrounds are just a lot of 2D textures. As the camera moves all those layers move at different speeds to suggest depth. Traditionally in games that's called parallaxing. From a technical perspective that's basically all there is to 'perspective' and 'distance' in a game like Awesomenauts.

However, this simple idea of objects following the camera creates a big problem. An object that is really far away hardly moves on the screen when the camera moves. The mountain in front of you in the far distance? Walk 10 meters to the left and it's still in front of you. Walk 100 meters to the left and it's still in front of you. It's so far away that it's basically always in front of you. That's realistic and as intended, but what if two rooms in your level should have different objects in them? If the rooms are really deep (like for example a factory hall) then the objects in the back of one room will be visible in the other.



In a 3D game the solution to this is simple: just put a wall in between these two rooms. But this is not a 3D engine. This is a purely 2D engine and walls that go into the distance are not supported. Heck, we don't even have a z-buffer to figure out what parts would be in front of or behind that wall! Also, what if we don't want a wall? What if we want to suggest really deep spaces but don't want to put endless walls into the distance in between them?

The solution our artists used in older Awesomenauts maps is that such deep rooms can't be close to each other. For example, in Ribbit IV the top part of the level is a lush jungle that continues far into the background. Originally my colleague Ralph also added deep caves in the bottom part of the map, but parts from the jungle became visible in the caves, and vice versa. So Ralph made the caves less deep. The jungle parts are still rendered behind the caves, but because the caves have a solid wall close to the camera the jungle isn't visible there anymore.



Not only is this very limiting for our artists, it's also a huge waste of performance: it causes a lot of overdraw. 'Overdraw' is when several objects are rendered on top of each other. The more objects are rendered on top of each other, the higher the overdraw is and the worse the framerate gets. Sometimes that's needed to get a certain effect, like when having lots of overlapping smoke particles. But here it's a waste: the jungle isn't even visible, so why render it? High overdraw is the number one performance drain in Awesomenauts, so not doing this would be quite wonderful. (For more on overdraw have a look at this post: Optimising Special Effects In Awesomenauts.)



I had been thinking about this problem for a while, mostly coming up with solutions that were quite a lot of work to build, when one day I realised that videocards have a very simple feature that solves exactly this problem: scissor rectangles! The basic idea of a scissor rectangle is that when rendering an object, you tell the videocard to only render the parts of it that are within a certain area on the screen: the scissor rectangle. Everything else is cut off. It's like a mask in Photoshop, but in-game.



Scissor rectangles are a very simple and very limited feature. They only do rectangles that are aligned with the screen. So you can't have a rotated rectangle, or a shape that's more complex than a rectangle. If you want any of that you'll need to resort to more involved tech, like the stencil buffer, which isn't efficient to recreate differently for each object rendered. I was looking for something simpler, something that could be changed for each object rendered without causing a performance hit. Scissor rectangles provide exactly that.



The next question is how to integrate scissor rectangles with our tools: how can an artist easily define which objects are cut off by which scissor rectangle? I chose to link scissor rectangles to our animation system. An 'animation' in the RoniTech (our internal engine) is simply just a bunch of objects that might be animated, so a part of a level can also be an 'animation'. I made it so that each animation can have a custom scissor rectangle, placed by the artist. The resulting workflow is that a level artist creates a room or area in the animation editor, adds a scissor rectangle and then puts the entire 'animation' in the level. It's an easy and fast workflow.

Our levels are mostly decorated by my colleague Ralph, using art drawn by Gijs. On the Starstorm map Ralph used scissor rectangles extensively for the first time and I love the result: he put a lot of depth and detail in the backgrounds and the framerate is higher than on other maps because the scissor rectangles reduce overdraw. Here's a short demo video:


For any programmers that read this, here are some implementation details I've encountered. Feel free to skip this list if you're not a programmer. ;)
  • The videocard wants to know about scissor rectangles in screen coordinates, while the way we use them means our tools place them in world space. To use scissor rectangles this way you'll need to convert the coordinates yourself before sending them to the GPU.
  • If you accidentaly swap the top and bottom of the scissor rectangle then everything will still work fine on AMD and NVidia GPUs, but on some Intel GPUs the objects will disappear altogether.
  • It's not allowed to define scissor rectangles that are partially or entirely outside the screen, so be sure the cap them to the edges of the screen.
  • The relevant functions in OpenGL are glEnable(GL_SCISSOR_TEST) and glScissor()
  • DirectX 9: SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE) and SetScissorRect()
  • There are equivalent functions that we use on Xbox One and Playstation 4. I haven't checked on mobile but I expect scissor rectangles are a standard feature on practically all videocards.



So there you have it: scissor rectangles! Not only did they enable our level artists to make deeper, richer levels, but they also improved performance by reducing overdraw and were quick and easy to add to our engine.

Saturday, 8 October 2016

Analysing the new Awesomenauts matchmaker

We're currently rebuilding the entire matchmaking, menus and party system of Awesomenauts. This rework is called Galactron. This is a huge operation: especially the matchmaking part is highly complex. We've done a bunch of betas for it so far and last week we let it run live for everyone for 48 hours. After that we turned it off again to analyse the results, read player feedback and fix bugs. Today I'd like to discuss the most interesting results.

I plan to do an overview of how Galactron works in a future blogpost, but today I'm just going to focus on what we've encountered in last week's beta.

Ragequitters and the removal of latejoin


This is probably the hottest topic in Galactron. Latejoin was probably the most hated feature of the old Awesomenauts so we've removed it altogether. In Galactron players are never put in a match that's already in progress. We've also added a rejoin option so if you're kicked out of a match due to a network error you can get right back into it. Finally, if someone leaves a public match, he can only rejoin that same match and can't get into another. A lot of players like this, but there's also a vocal group who is strongly against the removal of latejoin and the increased punishment for ragequitting.

I think there's no way to make both groups happy: they just want something different out of Awesomenauts. I've seen some people suggest we should make two gamemodes: one where latejoin and ragequit are allowed and one where they aren't. However, this is not an option because our playerbase is not big enough to split it like that. If we had the playercounts of League of Legends we could try things like that, but with our current playercounts we can't afford to split the community. Matchmaking in both versions would be worse if we did.

Instead of talking about opinions, let's have a look at cold hard facts. A common complaint about the removal of latejoin is that if someone leaves a matches and doesn't rejoin, the match remains 3vs2 until the end, whereas in the past the player would be replaced by someone else. This replacement didn't always happen though: in the old matchmaking matches weren't all 3vs3 all the time either. We gather metrics on this, so let's have a look:



As you can see the percentage of time that matches are 3vs3 is actually higher in Galactron than in the old matchmaking. In other words: while it may feel lame that leavers aren't replaced by latejoiners anymore, the overall result of Galactron is that you're playing 3vs3 more often. So while I can imagine that ragequitters dislike the new punishment they get for leaving, the complaint that matches remain incomplete more because leavers aren't replaced turns out to be unjustified.

High ping


The other major complaint about Galactron is high ping. While Galactron is supposed to make the average ping lower, players complain that ping is higher. Again, let's look at the metrics to see whether they're right:



Here it turns out that the stats agree with the complaining players: average ping has indeed increased significantly in Galactron. This is a big problem that we need to solve. It's also a big surprise to us: Galactron contains a lot of clever tech to match players better, why isn't that working?

We've been analysing this extensively these past few days and have a bunch of theories, but we don't have the final answer yet. One thing we've noticed is that it seems like Galactron doesn't get enough ping data. While players are waiting for matchmaking the game pings all other waiting players. We expected this would give us ping data on at least 75% of all possible connections, but in practice this turns out to often be around 50%. This means that Galactron doesn't have enough information to make a good matchup. Figuring out why this happens is our highest priority now.

Another possibility is that Galactron may care about skill too much right now. Galactron needs to find the best match-up based on a bunch of criteria around ping and skill. It can't satisfy them all perfectly all the time so it tries to find a balance. We can tweak how important each criterium is and I think we might have made skill too important right now.

A final issue we're looking into is how to handle people with horrible internet. High ping can happen when you're playing against someone on the other side of the globe, but sometimes your opponent just has such a horrible internet connection that he has high ping to everyone, even to people near him. No matter who Galactron matches such players to, the connection will always suck. We haven't decided how to handle that yet. One option is to make such players only play against others with crappy internet and keep them out of the regular matchmaking, but that might give odd results in the leaderboards as they would always be playing against the same people. We'll have to observe this group more once the other issues are fixed.

Skill matching


This is an aspect where Galactron seems highly successful: matches are very balanced. There are still situations where players of differing skill play against each other, but this is now usually caused by premades in which the players don't have the same skill. We can't split premades so this can't be solved. What we can do however is balance such premades against another similar premade, or against other players with similar skill patterns. Galactron turns out to be able to do so quite often. We also see premades play against other premades much more often now than before.

It would have been nice to compare this to our snowballing stats, but unfortunately those weren't recorded in the Galactron live beta due to an oversight. We normally collect stats about how often one team has much more kills than the other team, and how often the losing team didn't destroy any turrets. It would be interesting to see those stats for Galactron, so it's a pity they weren't recorded during the last beta.

Waiting times


I've seen some complaints regarding long waiting times in Galactron. Galactron performs a matchmaking round once every 5 minutes. That means that if you start searching for a match just before the round starts you'll get a match really quickly, while if you start searching just after you'll have to wait the full 5 minutes. On average this means a waiting time of 2.5 minutes. That isn't that much longer than before: in the old matchmaking it might take up to 4 minutes before a match stars, and longer if it isn't full yet at that point.

It's easy enough to reduce the waiting time: we can just switch from 5 minutes per matchmaking round to 4 minutes. However, we're not sure we should: a shorter waiting time will also mean lower match quality. There's a balance we need to strike here between speed and quality, and for the moment we think 5 minutes is fine. If we keep seeing a lot of complaints about this we'll reconsider and might change it to 4 minutes at some point. Feel free to share your opinion on this in the comments below.

Crashes


There have been some reports on crashes in Galactron. We've investigated most of these and so far they seem to be caused by driver issues and by broken Steam downloads. The latter can usually be fixed by running Steam's Verify Integrity Of Steam Cache.

As for the driver issues: my theory is that the crashes that we have data on are caused by alt+tab. Unfortunately our crashdumps don't contain a history that tells us whether alt+tab happened just before, so we can't tell for sure.

If alt+tab crashes than your videocard drivers are problematic. The solution is really simple: run in Borderless Window instead of Full Screen. With Borderless Window the game still covers the entire screen, it's just a different way of asking Windows to do so. Borderless Window is much more stable when it comes to alt+tab, and it's also much faster at it. In general we advice never running Full Screen unless Borderless Window gives you problems. I think we might rename these options to reflect this, since I expect some people might not understand what Borderless Window means.

We're still investigating some of the crash reports. If we encounter any that are caused by Galactron, we'll fix them of course.

Bugs


Besides the things mentioned above there were also a bunch of random bugs. Most of those we've already fixed by now, or are working on fixing. Some of the most notable ones:
  • Global chat didn't work half the time.
  • One menu screen around the tutorial didn't accept any button input, making it impossible to progress if you got to this screen (ARGH!).
  • Some menu screens didn't support controllers properly; only keyboard+mouse worked there.

So there you have it, the current status of Galactron. If you have any further questions, complaints or suggestions on Galactron then please do post them below in the comments. We hope to do another beta 1.5 weeks from now. Hopefully that one will be good enough that we won't have to turn Galactron off again, but we'll have to see how it goes. Most important is that Galactron needs to be really good before it goes out of beta.

Sunday, 2 October 2016

The big list of ways to create varied weapons

When designing weapons, there are many ways of differentiating them and spicing them up. To help anyone who is working on that, I would today like to give a big list of weapon parameters that one can play with. Most can be applied to ranged, melee and special attacks, but obviously not all can be used for everything in every genre. I hope this list can be used as an aid, regardless of genre. Note that I only focus on gameplay here: graphics are also an important part of the feel of a weapon, but his post is entirely about gameplay.

I made most of this list a couple of years ago when I was thinking about creating an open world game with randomly generated enemies. As a first step I made a long list of weapon properties and side effects that could be combined to create attacks for those enemies. As with many of my game ideas I doubt I'll ever get around to actually making this game, but I figured this list might also be useful to others, so I've extended it into this blogpost.

Most of this list is inspired by what Ronimo designers Fabian and Jasper made for Awesomenauts. Awesomenauts currently has 27 characters, each with 3 different attacks. Each character also has 18 unique upgrades with several tiers each. In total that's over 1000 upgrades! Many are simple upgrades that modify things like damage, cooldown, stun or range, but some are very unique, like the ones that upgrade the "AI" of Leon's clone. I'm really impressed by the creativity that went into many of those and put many into this list.

This list is by no means complete: you can come up with new ideas and variations forever. If you feel I overlooked some cool things, then please post them below and I will add them.

Basic parameters


  • Melee, ranged or area: for example, is it a sword, a gun, or an area effect around the character?
  • Range: how far does the attack reach? Note that this is not only important for guns, but also for things like the length of a sword
  • Effect timing: how long after you pressed the attack button does the damage actually apply? In case of a large sword movement, it might take a second before the sword actually hits. This gives the opponent time to get away, but also gives the attack itself a more powerful and impactful feel.

How often you can hit


  • Attack speed / cooldown: how fast an attack can be repeated. The term "attack speed" is usually used for weapons that can be used several times per second, while "cooldown" is generally used for things that take several seconds before you can use them again.
  • Ammo: weapons might have a limited amount of bullets. If they run out, you need to pick up more bullets, switch to another weapon or wait for bullets to regenerate.
  • Ammo clips / reloading: even if you still have enough ammo, you might have to pause shooting once in a while to reload. Parameters are how often you can shoot before you need to reload, how long reloading takes and whether you can reload before the ammo clip is empty. A special variation here is the Active Reload made famous by Gears of War: if you press a button with a very specific timing, the reloading is super fast or might give you a small buff.
  • Mana: similar to ammo, but mana usually automatically regenerates over time and is shared between several weapons.
  • Interrupt immunity: can an enemy interrupt this attack through a counter-attack or stun, or will this attack always finish once started?

Controls


  • Charge by holding a button: the weapon doesn't fire until you let go of the button, and it charges as long as you keep it pressed. Charging might make you throw further, or deal more damage or have some other effect. An important choice here is whether you can still move while charging.
  • Other charges: the weapon gains strength in some other way. Maybe every time you hit an enemy with your basic attack your special attack grows stronger, or maybe it becomes stronger over time. Once you use the weapon the charge is usually lost and the weapon needs to be charged again.
  • Holding versus button bashing: when doing consecutive slashes or shots, do you need to hold the button pressed, or bash the button all the time? This will also influence the number of attacks per second: if a new attack doesn't start until you press again, there will usually be a couple of frames in between attacks even when the player presses the button really quickly.
  • Combo's: specific combinations of attacks or button presses create stronger or different attacks. To make this more challenging this usually requires a specific timing for the button presses, but it can also be as simple as every third hit being stronger.
  • Cross-weapon influences: in this case performing one attack adds an effect to the next usage of a different attack. For example, maybe after using your special skill the next gunshot is charged with stunning electricity.
  • Timing: when do you need to press the button? For example, do you do extra damage if you press again just when the last slash has its impact?

Basic impact


  • Damage
  • Heal
  • DoT / HoT: Damage Over Time and Heal Over Time. These are controlled by a couple of parameters: how long it lasts, the total amount, and how often it applies (10 damage once per second isn't the same as 1 damage 10 times per second!). An extra setting is whether DoT stacks or not: if you get hit again while a previous DoT is still happening, do they add up or not? You can also have different DoT types: in Awesomenauts two poison DoTs don't stack, but a fire DoT and a poison DoT can happen simultaneously, so they do stack.
  • Stun: keeps you from doing anything for a time and interrupts whatever you were doing.
  • Microstun: this is a stun that is so short that it breaks your current attack, but doesn't keep you stunned. So you keep control, but if you are for example already charging an attack, then this charge is cancelled. In case of Awesomenauts, a good example of this can be found in Leon, who has microstun on his slash and can use that to turn off a Yuri's jetpack, causing the Yuri to fall to the ground, making him vulnerable for further melee attacks by Leon.
  • Slow: slows down movement and maybe jump height. Doesn't influence cooldowns or attack speed!
  • Silence: keeps you from using weapons and/or skills. Often this only influences special attacks and lets you keep doing your basic slash.
  • Snare: keeps you from moving, but allows you to still use weapons and skills.
  • Blind: for a while you can't see anything, or not far enough, or only right in front of you. In shooters this is often done with a flare grenade.
  • Knockback: an enemy is pushed away, often also a bit upwards into the air.
  • Throw: similar to knockback, but here you pick up an enemy and throw her away.
  • Crit: there is a certain chance that an attack is extra strong. Settings are the chance of the crit, and the amount of extra damage/effect. You can also implement rules that keep consecutive crits from happening, or that make sure that there is always at least one crit every N attacks. Have a look at these two blogposts for an analysis of designing crit chances.
  • Lifesteal: gain life yourself whenever you damage an opponent.

Note that many of these effects limit the player's control. Especially stun is really bad at this, but slow, silence, snare, blind and knockback all have such effects. When designing a single player game, these can be great fun: stunning an enemy is great and allows for mechanics in which you combat large groups of enemies without being overwhelmed. However, in an online multiplayer game like Awesomenauts, anything you do influences another human player. Being stunned for a long time is incredibly frustrating, and the same goes for snares and strong slows. All limitations are frustrating, and limitations on the player's movement even more so. Keep this in mind, and in general avoid designing situations where combos of these keep the player out of control for too long.

Projectile movement and count


For ranged attacks, the projectile you fire (or bullet, or magic fireball, or whatever) also has a number of its own properties:

  • Projectile speed: how fast the projectile moves makes a big difference for how dodgeable it is. If it moves pretty slowly compared to an enemy, you might even have to aim in front of her to be able to hit. The most extreme speed here is the instant hit, where the simulation doesn't consider it a projectile at all. This is the case for guns in most shooters. In the case of Awesomenauts, aiming is quite easy, but so is dodging. Using a sniper in an FPS, aiming for a headshot is pretty difficult, but if you aim correctly then the other player can't dodge at all. This greatly influences game feel.
  • Projectile size and shape: how big is the projectile, and is it square, rectangular, round or some other shape.
  • Homing: whether the projectile automatically changes its path to follow an opponent. You can tweak how quickly the projectile can rotate towards the enemy, but also how long it takes before the projectile starts homing after it was shot. Another setting is whether the projectile chooses its target only when shot or can alter its target during flight.
  • Arcs: some projectiles might not fly in a straight line. Grenades might be thrown in an arc, making aiming more complex but also allowing a projectile to be thrown over a wall.
  • Movement patterns: some weapons might move in strange patterns. For example, a waving sine movement could be applied.
  • Projectile count: you might shoot several projectiles at once. Important choices here are in what direction the different projectiles go (all somewhat forward, 360 degrees all around?) and whether all projectiles fire at the same time, or they come after each other in a series of shots.
  • Mines and bounces: what happens when a projectile hits the ground? Does it disappear, or explode with area of effect, or bounce away in another direction? Or does it stay on the ground like a mine, waiting to explode when an enemy is near? Also, if it does become a mine, is it a spider mine that jumps towards enemies that come near, or does it only attack who steps on it?
  • Detonation: when does the projectile explode? When it hits something, or when the player presses a button? The latter is mostly relevant in combination with area of effect and can be used with both mines and slow moving projectiles. Detonating a projectile by hand is a fun thing to do, but also often tends to somewhat slow down the game as the player watches every projectile fly.
  • Return to player: after a while or after a button press the projectile flies back towards the player. Might be used for a weapon that the player needs to retrieve before being able to use it again, or for a penetrating bullet that can hit enemies again on the way back.
  • Morphs: does the projectile change into something else at some point? Maybe if it hits a wall it bounces but becomes weaker, or if it flies long enough it grows, or if it flies through fire it gets an added damage over time fire effect?
  • Splits: whether the projectile can split into several separate projectiles. This can happen at a specific timing, or when hitting an opponent or wall. The newly spawned projectiles can also be more of the same projectile, or a different one altogether.

Who and where you hit


  • Friendly fire: whether you can hit your own teammates or not makes a big difference for the playstyle of the game.
  • Self-damage: weapons like rockets might damage yourself when they explode too near to you. An extreme example of this is Clunk's explode in Awesomenauts: he does massive damage to anyone near him, but also damages himself and can even kill himself this way. Another example is the famous rocket jump, where the knockback of the explosion of your own rocket allows you to jump higher.
  • Area of effect (AoE): normally an attack hits only one enemy, with AoE it can hit everyone in an area. AoE has several settings to play with: the size and shape of the area, and in some cases also the maximum number of opponents hit. A weapon can also be a combination: maybe the enemy that was hit gets 10 damage directly, and there's also an AoE effect that deals 5 damage to everyone near her. Be warned: area of effect can be very difficult to balance if the player sometimes faces many opponents, and sometimes only a few.
  • Penetrating: if you hit someone, does the bullet fly on and hit another target, or does it just end. Often seen variations to this are that a bullet has a maximum number of targets or deals decreased damage to consecutive targets. It might even change direction on hit, like with chain lightning.
  • Wall piercing: whether a weapon can shoot through walls or not. This makes a big difference to where an opponent will feel safe. In Awesomenauts only a couple of weapons can go through walls, and knowing whether there's an opponent in the field who has such a weapon is extremely important for successfully fleeing when low on health.

Buffing and debuffing


  • Buff: when you hit a teammate, she becomes stronger in some way. This can influence any parameter, like giving her a shield, increasing her health, reducing her cooldowns or increasing her damage output.
  • Debuff: this is an effect that makes an opponent weaker. This is the opposite of a buff and can again affect any aspect of the gameplay.
  • Cleanse: instantly removing the debuffs from a teammate.
  • CC immunity: this is a very specific type of buff that makes a player immune to things like stun, slow and snare, but doesn't make her immune to damage. "CC" stands for "Crowd Control", since such effects don't kill someone, but allow you to have some control over what others do.
  • Shield: another specific type of buff. A shield reduces damage you receive. This can be done in several ways: a percentage of damage can be removed, a fixed amount of damage per attack can be absorbed, or you can temporarily become invincible altogether. This makes a big difference: absorbing a fixed amount is weak against very strong attacks, but it also makes you invulnerable against weak attacks that are below the threshold, making this super effective against rapid fire of small bullets. A shield might also turn off once it has absorbed too much damage, allowing opponents to destroy the shield. Shields can then regenerate, turn on again after a cooldown, or be lost until picked up again.
  • Time bubble: slows down all time in an area, usually only for opponents. Everything takes longer, so movement is slower, bullets slow down and even cooldowns are longer. That makes this quite different from a normal slow, which only affects movement. This can also be inverted to make time go faster for teammates.
  • Invisibility: you can make yourself or a teammate invisible, or really hard to see. This is often frustrating for the enemy so it might be needed to limit how long you can be invisible or to give certain enemies a skill that allows them to see you even when invisible.
  • Phasing out: a weapon might make you disappear from the battlefield temporarily, making it impossible to receive or deal damage. It might also phase you out partially: maybe you can move through enemies temporarily, but you can still attack and receive damage.
  • Self (de)buff: all of these effects can also apply to yourself. They can be the main effect of a weapon, or a side effect. For example, maybe using some super powerful weapon drains your batteries and makes all other attacks have a longer cooldown for a while.

Armor and damage types


Damage and armor can have specific types. Often this means that you deal less damage against opponents who have a procentual resistenance against certain attacks. Some variations on this are:

  • Elemental: a weapon might deal a certain type of damage (fire, ice, poison, etc) and an armor might give extra protection against certain types of damage. An extreme example of this can be found in Titan Quest, where there are many types of damage and all items can give all kinds of resistances. For example, a bow might give a subtle 2% extra fire resistance.
  • Opponent type: some weapons might be extra strong or extra weak against certain types of opponents. Maybe poison has no effect against mechanical enemies, or ice has no effect against the undead. An example in Awesomenauts is that most special skills are very weak against turrets to avoid lame outranging tactics.
  • Piercing: if a weapon is piercing, it ignores the armor of the opponent and does full damage.

These things not only serve to add variation to the weapons and upgrades in a game: they can also help in balancing. Especially armor types are great for this. If it turns out that one specific class is way too strong against one specific other class, but everything else in the game is quite fine, then it can be really difficult to find a way to fix the balance between those two classes without destroying all the rest of the balance. Armor types are a great way to fix this: make one of the two classes undead and make the weapons of the other weak against undead. To the player this might feel like a general rule that makes sense, while in fact it's an extremely specific nerf.

Own movement


A weapon or skill might also include movement of the player herself. This greatly changes the feel of a weapon and has a large tactical impact on when to use a weapon.

  • Dash: attacks that combine with fast movement in a certain direction. A dash can go through enemies or stop as soon as you touch someone. Also important is when the dash does damage: whenever it touches someone (like Froggy G's dash in Awesomenauts) or only at the beginning end/or end of the movement (like Vinnie & Spike's dive).
  • Jump: whenever you perform this attack, you also jump up. Like doing an upward punch or overcharging your jetpack.
  • Groundpound: attacks that can only be done downward to the ground from a point in the air.
  • Pull: pull yourself towards and enemy or pull an enemy towards you. Which it is makes a big difference: the first might put you in the middle of a group of enemies, while the latter might for example pull an enemy in range of your own turret.
  • Teleport: an attack can let you or an enemy teleport towards a position. Maybe you teleport towards the position of the bullet, or towards an object you left behind before, or towards your own position 5 seconds ago.
  • Swap position: swap position with an enemy or teammate.
  • Movement limitations: an attack can also limit movement. Maybe you become really slow while carrying a heavy gun, or maybe you turn into a turret and can't move at all for a while.

Other


  • Chain: keeps an opponent chained to a certain point, but she has complete freedom within the range of the chain. This is a great variation to a snare, because it's much less frustrating to be limited to an area than to not be able to move at all. An opponent can also be chained to an object or another character, allowing you to drag someone along.
  • Weapons that add weaknesses: a great way to add more variation is to add special weaknesses to certain weapons. Maybe the player loses a bit of health whenever she shoots, or moves slower when holding a specific weapon. By adding weaknesses to a weapon you can make its strengths much more extreme without making a weapon instantly overpowered.
  • Summons: a weapon can also spawn a new AI character that can fight for you. If you do this, the summoned character can also have tons of upgrades, like Leon in Awesomenauts who can upgrade the AI of his clones to make them move around or attack. The character can be spawned near the player, or in the spot where a project hit something.
  • Transform: changes an enemy or teammate into something else. A famous example is the Warcraft spell that temporarily turns enemies into sheep that can't attack. The transformation might also be permanent: in Awesomenauts there's an upgrade that lets Genji morph normal droids into flying droids.
  • Vision: some weapons might let you see different things, like a scope on a sniper that let's you look far away, or heatvision, or a weapon that temporarily shows you invisible enemies.
  • Attack visibility: whether the enemy can see your attack coming, and how easily. A nearly invisible bullet is much harder to dodge than a big purple one. Also, if your attack has a clear charge animation then enemies can see it coming and have more time to dodge the attack.

That's it! I hope this list can help other game designers add variation to their weapons. At the very least I had fun making a list and have a nice reference now for my own game designs. ^_^