4
\$\begingroup\$

I am working on a skill system for use in a game project I'm chipping away at. The project is entirely in C# so I'm using .NET to its advantage.

In my preliminary implementation of a Skill Category -> Skill types system I'm using subclasses of ISkill and SkillCategory (base class with some virtuals) and using System.Type based reflection to generate a map of these at runtime (on demand).

The great thing about this implementation is that it entirely decouples specific skill types (such as Engineering / LaserWeapons from the SkillLibrary (the container for the generated skill mapping).

The goal is to remove the need to explicitly add each skill at some point as they are automatically detected by the library throughout the entire AppDomain.

Only game logic which relies on these skills use concrete types.

ISkill

public interface ISkill
{
  string GetName();
  string GetDescription();
  int GetSkillLevels();
  float GetMinutesOfTrainingForLevel(int level);
}

[SkillCategory(typeof(EngineeringSkills))]
[PrerequisiteSkill(typeof(ElectronicsSkill))]
public class LaserWeaponSkill : ISkill
{
  public string GetName()
  {
    return "Laser Weapons";
  }

  public string GetDescription()
  {
    return "Allows you to maintain and improve laser based weapons on your ship.";
  }
}

Usage example:

[RequiredUpgradeSkill(typeof(Skills.Engineering.LaserWeapons))]
public class LaserTurretWeapon : IShipWeapon
{
}

There's two slight ugliness's with being very System.Type based, though. One is that my Player._skillLevels dictionary can't serialize as Dictionary<Type, int> because Type isn't supported by DataContractSerializer (what I'm using at the moment).

So what I'm doing in SetSkillLevel<TSkill>(int level) and GetSkillLevel<TSkill>() is translating typeof(TSkill) to and from a string using typeof(TSkill).FullName (i.e. "Game.Skills.Engineering.LaserWeapons").

And the other is potential performance problems, which is something I should profile.

This all works, of course, but it feels like the weak point in an otherwise enum-less, strongly typed skill definition system.

I don't really want to use enums and a "list" of Skill structures, or move it to an external data definition file because then that requires more maintenance of created and deleted enum values and such.

Do you think this is worth it, or do topics such as performance of Type based operations and other considerations make it worth rethinking?

\$\endgroup\$

2 Answers 2

8
\$\begingroup\$

This is not a good use of object orientation or typing. You've fallen into the trap that inheritance and polymorphism and metaprogramming and language features are there to solve every problem.

You should reconsider using a data driven approach. Your current design is partially data driven as you explain it, but the data is embedded in code. You are spending a lot of time thinking about and implementing fancy, complex, indecipherable to anyone but you, and inflexible solutions to what is a very, very simple problem.

What happens when a designer wants to try tweaking a skill cost or attribute for a few battles? He has to edit code and recompile the entire game?

What happens when it comes time to translate the game to other languages?

What happens when the skill system core mechanics change after more extensive play testing and feedback?

What happens when you want to modify a weapon's skill requirements based on in-game state, such as a special ability that lets a player use a certain weapon without the right skill?

Classes should be used for logic. Interfaces should be used to bind together logic. Classes should not be used to encode data, express relationships between data, describe "real world" objects, or function as a data format consumed by the compiler.

Look into storing your skill names, attributes, and so on in data files. Store your items in data files. Store the relationships between skills and items in data files. Build tools for inspecting and modding that data. Improve your iteration time on game ideas and actual fun instead of improving on low-level tech tricks that have no impact on the game itself.

\$\endgroup\$
3
  • \$\begingroup\$ You make many good points on why this is just a silly idea! Maybe I was just having too much fun with .NET. I don't know if I should just use XML or look for a much more readable text data format. \$\endgroup\$ Commented Aug 14, 2012 at 7:31
  • \$\begingroup\$ @NickBedford Indeed, Sean's answer pointed out the flaws in your implementation. I posted some examples to give you ideas on how to improve your model. \$\endgroup\$
    – Marton
    Commented Aug 14, 2012 at 8:44
  • \$\begingroup\$ I find this design flaw easier explained in an MVC context. Usually, controllers are implemented/inherited from a generic controller class and ideally contain logic unique to that controller. Your design is like making multiple controllers that have similar logic, all loading the same views, and that only differ by the data members/model data used in them. Therefore, only one controller is needed. \$\endgroup\$
    – ChrisC
    Commented Aug 14, 2012 at 15:06
2
\$\begingroup\$

I agree with Sean Middleditch, that your approach should be changed for many reasons. Here's a couple of ideas you should consider:

  1. An interface can have properties.
  2. I would use an abstract base class for the skills instead of an interface. This way you don't have to implement most of the basic functionalities multiple times.

    public abstract class SkillInfo 
    { 
      public int ID { get; }
      public string Name{ get; } 
      public string Description{ get; }
    
      public float GetMinutesOfTrainingForLevel(int level)
      {
        //You can write your implementation here, 
        // that works with every (or almost every) subclasses of Skill.
      }
    } 
    
  3. Only create instances of a subclass of SkillInfo by serializing data stored in an external file (XML is the obvious choice, if you're using XNA's content serializer). You can also write a generic class to manage serialization of the data.

For example, in the WeaponSkillInfo : SkillInfo class, you'd have this method:

  public const int SKILL_ID= 5;
  private const string DEFAULT_FILEPATH= "weaponskill.xml";
  public static WeaponSkillInfo GetSkillInfo()
  {
    WeaponSkillInfo skill = ContentDataManager<SkillInfo>.GetById(SKILL_ID, filepath);
    return (WeaponSkillInfo)skill;
  }

and in the ContentDataManager.cs:

  public class ContentDataManager<T> where T : SkillInfo
  {
    public static T GetById(int id, string contentPath)
    {
      //Logic to deserialize data from the XML
    }
  }

4 . When you have for example 3 players with 5 skills each, you shouldn't store the name and description of those skills for all the players. Something I would do instead is:

  public class PlayerSkill
  {
    public int SkillInfoId;
    private SkillInfo _skillInfo;
    public int Level{ get; }
    public int ProgressToNextLevel{ get; }
  }

The PlayerSkill's constructor should create a reference to the relevant SkillInfo instance based on the SkillInfoId property. This way, if you serialize the player (when you save the game), only the SkillInfoId will be stored. Consider the scenario, when you want to create a patch for the game, that changes the Weapon skill's description. This way, you only have to upgrade it in one place (weaponskill.xml). And as Sean said, you (or your artists) won't have to recompile the game exe.

5 . The basic principle you should follow: The source code mustn't have any "content". Content here refers to descriptions of skills, skill names, NPC names, experience level values, sprites, 3d models, animation durations etc. Keep the logic and the structure in the code, and the content in external resources.

\$\endgroup\$
2
  • \$\begingroup\$ Gave your answer the +1 it deserves for explaining what to do instead of just condemning what not to do like mine. :) \$\endgroup\$ Commented Aug 14, 2012 at 16:16
  • \$\begingroup\$ Thanks Marton. I'm actually defining a simple markup language not unlike JSON but with more structure like a XML elements with attributes. Example: pastebin.com/URpm792v I'm not sure if anything already exists, but this is what I want to have to type instead of JSON or XML. XML being too verbose, and JSON being too free-form. Some sort of schema implementation might be needed too. \$\endgroup\$ Commented Aug 15, 2012 at 12:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .