views:

165

answers:

7

I am programming a game as an exercise and I've run into a design problem. My role playing game will have the typical classes like Fighter, Wizard, Theif, Cleric. How do I design my classes so that players can multi-class? For example, one player might start off as a Fighter (and gain the related skills fighters have), then multi-class to a Wizard (at that point they gain wizard spells), and later on multi-class yet again to a rogue (now gaining all abilities rogues have). So this player is now a Fighter-Wizard-Rogue. I don't know to represent this in C#.

At first I tried to use the decorator pattern but I'm unable to multi-class multiple times with this. Any pointers on how to design this?

Only thing I can think of is having an IList<CharacterBaseClass> property for each character and adding Fighter, Wizard, Rogue, etc to this as the player multi-classes. So something like this..

class CharacterBaseClass
{
   public IList<CharacterBaseClass> MultiClasses { get; set; }
   // constructors, etc
}

and each time they mutli-class I add to the IList

// player starts off as Fighter
Warrior player1 = new Warrior();

// now multi-class to Wizard
player1.MultiClasses.Add(new Wizard()); 

// now multi-class to Theif
player1.MultiClasses.Add(new Theif());    

I'm sure there must be a better way than this?

+1  A: 

If the classes have some common interface or base class, then multiclass is additional class (MultiClass) which also implements this interface or base class, then delegates to its contained instances.

For example:

public class MultiClass : Class {
    ...
    public MultiClass(params Class[] classes) {
        this.classes = classes;
    }

    public IEnumerable<Ability> GetAbilities() {
        return this.classes.SelectMany(с => c.GetAbilities());
    }

    ...
}

If you want to add more classes, you can add AddClass method to the base Class, which would create MultiClass from single class, or recreate multiclass with one more contained class for MultiClass.

Andrey Shchekin
I'm not exactly sure I understand. Can you please show me code to "recreate multiclass with one more contained class for MultiClass"?
mikedev
For example: `public override MultiClass AddClass(Class @class) { return new MultiClass(this.classes.Concat(new[] { @class }).ToArray()); }`, `then player.Class = player.Class.AddClass(Classes.Wizard);`. On the other hand, MultiClass flattening contained MultiClasses would work just as well.
Andrey Shchekin
+2  A: 

With the decorator pattern, you could possibly do it.

Character person = new Character("Rambo");
person = new Fighter(person); // decorate him with Fighter skills
person = new Thief(person);   // also decorate him with Thief skills

Personally I would probably look at attaching classes to the character instead:

Character person = new Character("Rambo");
person.AttachClass(new Fighter());
person.AttachClass(new Thief());

Of course, if you need complex interactions between the classes, so that not only does a Fighter/Thief gets bonuses and skills from each, but he gets something more as well, perhaps the only correct route for that might be to create specific multi-classes for all the combinations:

Character person = new Character("Rambo");
person.AttachClass(new FighterThief());

This would of course just explode with all the combinations.

What about a pure table-driven effort?

Place all applicable skills, spells, bonuses, effects, etc. in a hunking big table, then define the classes by linking a specific class to the specific items in that table. This way it would be much simpler to create hybrid classes by linking across different base classes.

To use a decorator pattern and still get proper access to everything, each class (in the programming sense of the word) needs to be implemented properly as a decorator class.

For instance:

public class BaseClass
{
    protected BaseClass(BaseClass underlyingCharacterClass);
    public abstract bool CanCastSpells();
    public abstract List<Spell> GetAvailableSpells();
    protected BaseClass UnderlyingCharacterClass;
}

public class Wizard : BaseClass
{
    public override bool CanCastSpells() { return true; }
    public override List<Spell> GetAvailableSpells()
    {
        List<Spell> result = new List<Spell>();
        if (UnderlyingCharacterClass != null)
            result.AddRange(UnderlyingCharacterClass.GetAvailableSpells());
        result.Add(new WizardSpell1());
        ...
        return result;
    }
}

public class Thief : BaseClass
{
    public override bool CanCastSpells()
    {
        if (UnderlyingCharacterClass != null)
            return UnderlyingCharacterClass.CanCastSpells();
        return false;
    }
    public override List<Spell> GetAvailableSpells()
    {
        List<Spell> result = new List<Spell>();
        if (UnderlyingCharacterClass != null)
            result.AddRange(UnderlyingCharacterClass.GetAvailableSpells());
        return result;
    }
}
Lasse V. Karlsen
Remember, though, that the decorator would only work if all the types shared a common interface, or at least the interface of the level below. If a `Thief` decorates a `Fighter`, `Thief` still needs the `Fight()` method. If a `Wizard` in turn decorated the `Thief`, the `Wizard` would still need to `Fight()` and `Steal()`.
Jay
The problem I couldn't figure out with the decorator approach was gaining access to the other classes. For example, `Character = new Thief(new Wizard( (new Fighter (person))))`. I can now cast to either a `Character` type or `Thief` type but I can't gain access to `Wizard` or `Fighter`.Your "AttachClass" approach is what I was looking into but it felt wrong. I end up with multiple references to things like Hitpoints, Attributes, Inventory, etc. But maybe it's not a big deal?Table driven approach is also very interesting!
mikedev
You only need access to one of each, the outermost class' properties. For instance, if you say "c.HitPoints", this would call Thief.HitPoints, which would in turn call Wizard.HitPoints, which would in turn call Fighter.HitPoints, which would in turn call Person.HitPoints. This final call would return some base value, and then all the recursive calls up the stack would just add their own bonus to this. A level 10 wizard gets 20 hitpoints? Then add that, and return it. And so on.
Lasse V. Karlsen
If you look at how World of Warcraft has done it, though they don't support multi-classes, they support many spells and skills that have the same effect, they have seemingly gone for a big table-driven approach. If you look at www.thottbot.com, and click in on spells, you'll eventually get down to effects that are applied, which have their own ID numbers. These are in many cases used by many items, skills and spells alike. In other words, you can drink a potion to give you +10HP/sec for an hour, or cast a spell that does the same, but the effect is only defined once.
Lasse V. Karlsen
For the decorator, the problem I had was this - Wizards have an IList<Spells> property. But if I do `Character = new Thief(new Wizard(person)))` then I can no longer access the list of spells. I can't even cast to `Wizard` because the compiler tells me the actual type is `Thief`. Happen to know a way around this?
mikedev
Yes, all classes needs to be implemented properly as decorator methods. The list of spells from the "Thief" class would basically be: Call underlying class (if present), and add nothing (thief can't cast spells by itself). As for "Can this class cast a spell"? that method would also need to call the base class, if present. Of course, if the outermost class is "Wizard", it doesn't need to call underlying class to know that you can cast spells. Let me add some code to my answer.
Lasse V. Karlsen
@Lasse thank you so much for teaching me how to properly implement my decorator methods! I take it this will work no matter how many times I multi-class since every decoration points to the underlying object.
mikedev
@Lasse could you show me how your person.AttachClass(new Fighter()) would look?
mikedev
+1  A: 

Not everyone's cup of tea, but you could use state pattern.

public interface Player
{
   void Fight();
   void CastSpell();
   void DoRoguishThings();
}

public class PlayerImpl : Player
{
   Player fighter;
   Player wizard;
   Player rogue;

   Player current;

   public void Fight(){ current.Fight(); }
   public void CastSpell(){ current.CastSpell(); }
   public void DoRoguishThings(){ current.DoRoguishThings; }

   public void MakeWizard(){ current = wizard; }
   public void GoRogue(){ current = rogue; }
}
public class Fighter : Player
{
   public void Fight(){ // do fighting }

   public void CastSpell()
   {
      Console.WriteLine("You can't cast a spell, you are but a mere pugilist.");
   }

   ...
}

public class Wizard : Player
{
   public void Fight(){ // do wizardly fighting }
   public void CastSpell() { // do spell-casting }
   public void DoRoguishThings() { // whatever }
}
Jay
I like your console.writeline hehe. But I don't think this will work since I don't know before hand what the player will choose to multi-class to. It can be any combination they want. Or are you saying I would put every single possibility into one class?
mikedev
Yes, that is the state pattern. Everything is there, but what actually happens when you call a particular method is determined by the state of the class at that time, because the call is delegated to the state object. So, when the user is a `Wizard` and wants to `Fight()`, the `Fight()` method of `Wizard` is called, not the `Fight()` method of `Fighter`. What you can't do, at least not without creating combination state types, like `RogueWizard` or the rather interesting `ClericThief`, is take into account the player's combination of qualities. This is not a problem if the sequence is linear.
Jay
+1  A: 

I think your characters should be able to have multiple Facet/Role implementing "Archetypes". Then each one having multiple skills or attributes. Let's say...

class Archetype
{
    string Name;
    Dictionary<string,Type> Properties;
    Dictionary<string,Action> Skills;
}

class Character
{
    string Name;
    string Alias;

    Dictionary<Archetype,Dictionary<string,object>> FacetData;
}

class TheGame
{
    public static void Main()
    {
        var Pilot = new Archetype();
        Pilot.Name = "Combat-Pilot";
        Pilot.Properties.Add("FlightHours", typeof(int));
        Pilot.Properties.Add("AmbientTypes", typeof(List<string>));

        var Jedi = new Archetype();
        Jedi.Name = "Jedi";
        Jedi.Properties.Add("ForceLevel", typeof(int));
        Jedi.Properties.Add("Title", typeof(string));
        Jedi.Properties.Add("IsCombatVeteran", typeof(bool));
        Jedi.Skills.Add("LightSaberFight", FightWithLightSaber());

        var Anakin = new Character();
        Anakin.Id = 100;
        Anakin.Name = "Anakin Skywalker";
        Anakin.Alias = "Darth Vader";

        Anakin.FacetData.Add(Pilot, new Dictionary<string, object>()
            { { "FlightHours", 2500 },
              { "AmbientTypes", new List<string>() {"Atmospheric", "Space", "Hyper-Space"} } };

        Anakin.FacetData.Add(Jedi, new Dictionary<string, object>()
            { { "ForceLevel", 7 },
              { "Title", "Padawan" },
              { "IsCombatVeteran", true } };

        Anakin.ApplySkill(Jedi, "LightSaberFight", Target);
    }

    public static void FightWithLightSaber(Character Target)
    {
        ShowBrightLightSaberPointingTo(Target);
        EmitCoolSound();
    }
}

If you get the Idea, then you could store properties/data and call skills/tasks with some degree of indirection and flexibility. Good luck!

Néstor Sánchez A.
A: 

You may want to consider composition.

interface IWarrior
{
    void Slash();    
}

interface IMage
{
    void Cast();
}

class Warrior : IWarrior
{
    public void Slash() { }
}

class Mage : IMage
{
    public void Cast() { }
}

class WarriorMage : IWarrior, IMage
{
    private readonly Warrior _Warrior;
    private readonly Mage _Mage;

    public void Slash()
    {
        _Warrior.Slash();
    }

    public void Cast()
    {
        _Mage.Cast();
    }
}
ChaosPandion
+2  A: 

Just because your characters are wizards and warriors, that doesn't mean you have to create subclasses for them. Instead, ask yourself, "At the code level, what does a character's class do?" Likely, you won't want to have C# subclasses for character classes at all. Instead, figure out what the class actually does, and then determine the right way to model that in code.

For example, if character class restricts the equiment a character can use, then you can define a class for AllowedEquipment:

public class AllowedEquipment
{
    public static AllowedEquiment Warrior()
    {
        return new AllowedEquipment() {
            Daggers = true;
            Swords = true;
            Shields = true;
            Armor = true
        };
    }

    public static AllowedEquiment Wizard()
    {
        return new AllowedEquipment() {
            Daggers = true;
            Swords = false;
            Shields = false;
            Armor = true
        };
    }

    public bool Daggers { get; set; }
    public bool Swords { get; set; }
    public bool Shields { get; set; }
    public bool Armor { get; set; }
}

Don't feel you need to use subclasses to model every "is-a" relationship in your game.

Another option is to use the Type Object pattern to model your character classes. If you do that, it'd be easy to give each character a set of those Type Object instances instead of a single one, giving you, in effect, multiple inheritance.

munificent
A: 

Néstor Sánchez A. provides you with a good solution. Drop your OOP thinking for a while and read this:

http://www.devmaster.net/articles/oo-game-design/

Not every problem can be solved with plain OOP in an elegant way.

Mikeon