11

Coding isn't that hard actually. The hard part is to write code that makes sense, is readable and understandable. So I want to get a better developer and create some solid architecture.

So I want to do create an architecture for NPCs in a video-game. It is a Realtime Strategy game like Starcraft, Age of Empires, Command & Conquers, etc etc.. So I'll have different kinds of NPCs. A NPC can have one to many abilities (methods) of these: Build(), Farm() and Attack().

Examples:
Worker can Build() and Farm()
Warrior can Attack()
Citizen can Build(), Farm() and Attack()
Fisherman can Farm() and Attack()

I hope everything is clear so far.

So now I do have my NPC Types and their abilities. But lets come to the technical / programmatical aspect.
What would be a good programmatic architecture for my different kinds of NPCs?

Okay I could have a base class. Actually I think this is a good way to stick with the DRY principle. So I can have methods like WalkTo(x,y) in my base class since every NPC will be able to move. But now lets come to the real problem. Where do I implement my abilities? (remember: Build(), Farm() and Attack())

Since the abilities will consists of the same logic it would be annoying / break DRY principle to implement them for each NPC (Worker,Warrior, ..).

Okay I could implement the abilities within the base class. This would require some kind of logic that verifies if a NPC can use ability X. IsBuilder, CanBuild, .. I think it is clear what I want to express.
But I don't feel very well with this idea. This sounds like a bloated base class with too much functionality.

I do use C# as programming language. So multiple inheritance isn't an option here. Means: Having extra base classes like Fisherman : Farmer, Attacker won't work.

2

4 Answers 4

14

Composition and Interface Inheritance are the usual alternatives to classical multiple inheritance.

Everything that you described above that starts with the word "can" is a capability that can be represented with an interface, as in ICanBuild, or ICanFarm. You can inherit as many of those interfaces as you think you need.

public class Worker: ICanBuild, ICanFarm
{
    #region ICanBuild Implementation
    // Build
    #endregion

    #region ICanFarm Implementation
    // Farm
    #endregion
}

You can pass in "generic" building and farming objects through the constructor of your class. That's where composition comes in.

7
  • Just thinking loud: The 'relationship' would (internally) be has instead of is (Worker has Builder instead Worker is Builder). The example / pattern you've explained makes sense and sounds like a nice decoupled way to solve my problem. But I'm having a hard time to get this relationship.
    – Brettetete
    Commented Aug 22, 2014 at 19:08
  • 3
    This is orthogonal to Robert's solution but you should favor immutability (even more than usual) when you make heavy use of composition to avoid creating complicated networks of side effects. Ideally your build/farm/attack implementations take data in and spit data out without touching anything else.
    – Doval
    Commented Aug 22, 2014 at 19:08
  • 1
    Worker is still a builder, because you inherited from ICanBuild. That you also have tools for building is of secondary concern in the object hierarchy. Commented Aug 22, 2014 at 19:10
  • Makes sense. I'll give this princpile a try. Thanks for your friendly and descriptive answer. I really appreciate that. I'll let this question open for some time though :)
    – Brettetete
    Commented Aug 22, 2014 at 19:14
  • 4
    @Brettetete It might help to think of the Build, Farm, Attack, Hunt, etc. implementations as skills, which your characters have. BuildSkill, FarmSkill, AttackSkill, etc. Then composition makes much more sense.
    – Eric King
    Commented Aug 22, 2014 at 21:00
4

As other posters have mentioned a component architecture could be the solution to this problem.

class component // Some generic base component structure
{
  void update() {} // With an empty update function
} 

In this case extend the update function to implement whatever functionality the NPC should have.

class build : component
{
  override void update()
  { 
    // Check if the right object is selected, resources, etc
  }
}

Iterating a component container and updating each component allows the specific functionality of each component to be applied.

class npc
{
  void update()
  {
    foreach(component c in components)
      c.update();
  }

  list<component> components;
}

As each type of object is wanted you could use some form of the factory pattern to construct the individual types you wanted.

npc worker;
worker.add_component<build>();
worker.add_component<walk>();
worker.add_component<attack>();

I have always run into problems as the components get more and more complex. Keeping component complexity low (one responsibility) can help alleviate that problem.

3

If you define interfaces like ICanBuild then your game system has to inspect every NPC by type, which is typically frowned upon in OOP. For example: if (npc is ICanBuild).

You're better off with:

interface INPC
{
    bool CanBuild { get; }
    void Build(...);
    bool CanFarm { get; }
    void Farm(...);
    ... etc.
}

Then:

class Worker : INPC
{
    private readonly IBuildStrategy buildStrategy;
    public Worker(IBuildStrategy buildStrategy)
    {
        this.buildStrategy = buildStrategy;
    }

    public bool CanBuild { get { return true; } }
    public void Build(...)
    {
        this.buildStrategy.Build(...);
    }
}

...or of course you could use a number of different architectures, like some kind of domain specific language in the form of a fluent interface for defining different types of NPCs, etc., where you build NPC types by combining different behaviors:

var workerType = NPC.Builder
    .Builds(new WorkerBuildBehavior())
    .Farms(new WorkerFarmBehavior())
    .Build();

var workerFactory = new NPCFactory(workerType);

var worker = workerFactory.Build();

The NPC builder could implement a DefaultWalkBehavior that could be overridden in the case of an NPC that flies but can't walk, etc.

2
  • Well, just thinking loud again: There is actually not much difference between if(npc is ICanBuild) and if(npc.CanBuild). Even when I would prefer the npc.CanBuild way from my current attitude.
    – Brettetete
    Commented Aug 22, 2014 at 19:28
  • 3
    @Brettetete - You can see in this question why some programmers really dislike inspecting the type. Commented Aug 22, 2014 at 19:29
0

I'd put all the abilities in separate classes that inherit from Ability. Then in the unit type class have a list of abilities and other stuff like attack, defence, max health etc. Also have a unit class that has current health, location, etc. and has unit type as a member variable.

Define all unit types in a config file or a database, rather than hard coding it.

Then the level designer can easily adjust the abilities and stats of unit types without recompiling the game, and also people can write mods for the game.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.