Fluent Game Design With Fluent Interfaces

February 3, 2010

Game designers often find themselves writing code in modern games.  Often, they have little to no programming experience and therefore must be taught the basics of programming (sequence, conditionals and loops).  I propose utilizing a technique that simplifies the code written by game designers in their games.  This technique is known as “Fluent Interfaces”.

What is Fluent?

Fluent interfaces allow game designers to write more fluid and readable code.  Through the use of method chaining, English like sentences can be written to express game functionality.  Fluent interfaces can be implemented in any object oriented programming language.  Below is an example of a line in the game design document, a standard implementation example and a fluent example:

Game Design Document:

“Do 5 damage to all enemy tanks within range 2 of an entity”

Standard Example:

foreach( var unit in player.UnitsWithinRange(2) )
{
    if(!unit.IsType(Enemy) || !unit.IsType(Tank))
        continue;

    unit.Damage(5);
}

Fluent Example:

player.UnitsWithinRange(2)
  .Where(UnitIs.Enemy)
  .Where(UnitIs.Tank)
  .DoDamage(5);

This fluent example closely matches the design document and is easier to read.  It also uses less language constructs like loops and conditions.  With Intellisense, designers are given a context sensitive list of operations they can perform.  Designers simply build up the expression that describes the original line in the game design document they are implementing.

How to implement Fluent Interfaces

Fluent interfaces are actually quite easy to implement.  Objects expose methods that return a reference to the object itself allowing method chaining.  The best way to describe this is by showing the implementation required for the examples above.

First, we create the object we will be working on (I’ve called it UnitsList in my examples).  This gives us the first part of the fluent call (Get.UnitsWithinRange(2)).

public class ScriptObject
{
    UnitsList UnitsWithinRange(int range)
    {
        return new UnitsList(range);
    }
}

The UnitsList object must have a set of methods that return references to the object itself allowing method chanining:

public class UnitsList
{
    public UnitsList Where(UnitIs condition)
    {
        this.conditions.Add(condition);
        return this;
    }

The fluent expressions are terminated by returning void from a function.  This lets the designer know they have no more options and often that the actual operation will be performed.

public class UnitsList
{
    public void DoDamage(int damage)
    {
        foreach( var unit in this.mainUnit.UnitsWithinRange(range) )
        {
            if(!PassesConditions(unit))
                continue;

            unit.Damage(damage);
        }
    }

    private bool PassesConditions(Unit unit)
    {
        foreach( var condition in conditions)
        {
            if(!unit.IsType(condition))
                return false;
        }
        return true;
    }
}

There are a couple of key things to notice:

  1. This last code example looks a lot like the original standard example
  2. A lot more “engine” code is required to setup a fluent interface than a standard interface

So, in effect, an extra layer of abstraction is being placed over the original code.  Rather than designers working on loops and conditions, they are calling (well named) methods.  This makes their life a lot easier and simplifies maintenance of their gameplay code.  It pushes the burden of maintenance down from the gameplay to the engine level and therefore on programmers, who are better suited to maintaining code.  If there is a change in the engine or game, this extra level of abstraction serves to buffer the designers and reduce the amount of code that needs to be written.

Good Interface Design

Care needs to be taken when designing the interfaces and exposing methods to the designers.  Rather than exposing all functions off a single object my recommendation is to define different objects for different situations.  The example provided starts with the player and retrieves a list of units around it.  Filters are then added before the final operation is performed on the resulting list.  By limiting the methods available to the designer to simple filters there is little risk of them making a mistake.  Also, the fact that the “DoDamage” function returns void stops them from chaining anything further.

Other Syntaxes

One small point is that designers can structure their fluent “sentences” in any way they please:

player.UnitsWithinRange(2).Where(UnitIs.Enemy).Where(UnitIs.Tank).DoDamage(5);
player.UnitsWithinRange(2)
  .Where(UnitIs.Enemy)
  .Where(UnitIs.Tank)
  .DoDamage(5);

There is no difference between the above two examples.  It’s simply a matter of coding style preferred by the writer.

Conclusion

What do you think of Fluent Interfaces?  Have you used a similar technique before?  Do you think the extra engine code and maintenance is too much hassle for the gain in clarity to designers?

As an experiment, try exposing a small set of functionality through a fluent interface and see whether your designers like working with it.

Share:
  • Digg
  • Reddit
  • Facebook
  • del.icio.us
  • DZone
  • LinkedIn
  • email

Find Us On Facebook

12 Responses to “Fluent Game Design With Fluent Interfaces”

  1. They could also write:

    Get.UnitsWithinRange(2).Where(UnitIs.Enemy).Of(player).And(UnitIs.Tank).DoDamage(5);

    Which has a whole different meaning. I like the idea (I’ve seen it called the Builder pattern, and applied to other things like building XML documents) but for something with as large a scope as a scripting system I think you’d have to design the interfaces *very* carefully to avoid potential subtle bugs like this.

  2. I really enjoy fluent interfaces – from the user side at least. My first contact with them was in NMock2, later NUnit (to a degree) and now Ninject. I don’t think that they safe much time compared to a (well-designed) traditional interface for someone who is already used to a library, but they do a great job inviting other developers to read and modify the code because they’re so simple to pick up.

    Up until now, I haven’t tried to implement fluent interfaces myself for anything because I imagine it would take a lot of time.

  3. Ben-

    I’ve updated my example a little as I went a little too far with trying to simplify it :) . Having the player as the starting point should reduce some of the problems that may arise.

    Agreed that interfaces need to be build very carefully. I’m in the middle of implementing this for all our scripts so I’ll let you know how I go with handling it. My main priority is to simplify the scripts themselves at the cost of more complex engine code.

  4. Markus-

    I’m glad you’ve raised some concerns about the “safety” of these interfaces over traditional interfaces. I saw it as being a much better solution, so long as good though is put in to the initial design. I definitely think a defensive stance needs to be taken to make sure this doesn’t cause more issues than it solves.

  5. Its startting to look like a bit like SQL, would the next step be to create a language expressly for this purpose:

    Select UnitsWithinRange 2 of player
    Where isTank
    Then DoDamage 5

  6. Arowx-

    I completely agree. I had written up a little on DSL’s (Domain Specific Languages) but decided to leave it out so the article didn’t get too long. I might throw my thoughts in to the post tomorrow.

  7. While these two examples are the same functionally, I was wondering what effect the fluent example has on complexity. I realize it’s unlikely to have a large enough number of conditions to cause a huge difference, but won’t the extra loop add some unnecessary variables to the stack? If the code has a large number of these calls, could that eventually effect the game’s efficiency? Otherwise I think this is a really interesting concept.

  8. I find myself doing this kind of thing too…

    But something about having And(…) and Where(…) performing the same function irks me. Those two words represent different ideas and the idea of being able to use them interchangeably (lines do get deleted, and random ones cut/copied and pasted from place to place).

    Maybe something like this reads just as well, and hopefully is less error-prone:

    player.UnitsWithRange(2)
    .OnlyWhere(UnitType.Enemy)
    .OnlyWhere(UnitType.Tank))
    .DoDamage(5);

  9. Bill-

    A couple of people have said the same thing to me. I can see that it brings confusion, I’ll update my example to better fit :)

  10. The standard example should read:

    foreach (var unit in player.UnitsWithinRange(2)) {
    if (unit.IsType(Enemy) && unit.IsType(Tank)) {
    unit.Damage(5);
    }
    }

  11. Take a look at inference engines. Basically they are rule based system that contain rules that you have to specify as you wrote here, save some syntax differences.

    The thing is that our game designers ARE writing such rules, and are successful in doing so. Slightly subtle is that they are not communicating with the engine directly, which is probably why it works so well for us.

  12. Fascinating as this method is, I think I still prefer the simpler (To code, although monolithic):

    ApplyDamage(5, 2, Team_Enemy, Type_Tank);

    Which is easier to work with these days due to function signature tool tips.

    OR

    DamageApply d;
    d.SetDamage(5);
    d.SetRange(2);
    d.SetAffectedResctriction(Enemy);
    d.SetAffectedResctriction(Tank);
    d.Apply();

Leave a Reply