5
\$\begingroup\$

For my RTS game's ability system, I need an efficient ability system where each ability is its own class that implements certain functions from an inherited abstract base class, Ability. Every ability is identified by a byte ID (which gets sent over the network and interpreted by other players). I want to be able to call an ability's overrided Execute() Method with its ID. Currently, I've been thinking of storing all the abilities in dictionaries with their IDs as their keys but I'm having trouble storing these derived classes, abilities, in the dictionary.

I've also heard of reflection but I'm a bit hesitant because I've heard it's a quite expensive operation. Would it be recommended to use up to 60 reflection processes in one frame or will this cause performance issues?

Note: I'm using C# and Unity3D.

\$\endgroup\$
3
\$\begingroup\$

First of all, I would consider making an ability a component, so that it can be attached to any GameObject:

abstract class Ability : Component {
    public abstract byte Id { get; }

    public abstract Execute();
}

By making the byte id a property, you can easily override the property in each class and give it a unique value, without having to worry about keeping a list:

class MyConcreteAbility : Ability {
    public override byte Id {
        get { return 8; } // Byte value of your choosing
    }

    public override void Execute() {
        // Your code here
    }
}

(Just make sure not to use the same value twice.)

Additionally, I would add an overloaded version of the method Execute to the abstract Ability class, to serve as a message handler:

public void Execute(byte id) {
    if(Id == id) {
        Execute();
    }
}

The ability's Execute method can then easily be invoked on all objects it is attached to by calling:

BroadcastMessage("Execute", 8)

Or on a specific object and all its children by calling:

gameObject.SendMessage("Execute", 8)

Of course, you can also simply call it directly.

\$\endgroup\$
  • \$\begingroup\$ I'm wary of using SendMessage/BroadCastMessage since it has performance implications. The property ID and overloaded methods work great though. To call the correct classes, I've added the byteID's to a dictionary corresponding to the class call the method if the byteID exists as a key in the unit's ability dictionary. \$\endgroup\$ – JPtheK9 Apr 23 '15 at 16:44
3
\$\begingroup\$

I don't really get it, you are using polymorphism and then you don't?

So far I understand it as the following, you have a base class Ability and derived classes that actually implement the ability. So something like:

abstract class Ability
{
    abstract public void Execute();
}

class CastMagicMissile : Ability
{
    override public void Execute() 
    {
        // some logic to case magic missile
    }
}

Why do you want to pass the id of the ability to Execute? The ability already knows what type it is. The only thing that remains to actually do is find a mapping from ID to ability, but this can either be solved by a dictionary or a switch statement. I can think of two situations where this may be needed, construction and invocation via the network.

With construction, I would use a factory function, like so:

#define MAGIC_MISSILE_ABILITY 1337

abstract class Ability
{
    static public Ability Create(byte id)
    {
        switch (id)
        {
            case MAGIC_MISSILE_ABILITY:
                return new CastMagicMissile();
            default:
                // throw some appropriate error
        }
    }

    //...
}

You may want to have a more flexible factory facility, but you get the idea.

With the network invocation issue, I would put an id on each ability, with something like a GetType method and the loop that in the invocation. Something along the lines of:

class Unit 
{
    private List<Ability> abilities;

    public void ExecuteAbility(byte id)
    {
        foreach (Ability ability in abilities)
        {
            if (id == ability.GetType())
            {
                ability.Execute();
                return;
            }
        }
        // throw invalid ability error or ignore
    }
}

Note: If you share the ability instances among the units, you may need to pass the reference to the Execute method, but that is about it.

\$\endgroup\$
1
\$\begingroup\$

You have separate classes for each Ability, although the type of each ability is already defined by ByteID. As such, an "ability" is really just a link between each ByteID and a unique callback. You are unnecessarily defining the callbacks via inheritance.

A static Execute() can "index" which execute() to use with only ByteID.

In this way, all ability instances are identically-sized and -typed and will fit in a strongly-typed dictionary.

If all of your callbacks have the same signature, your entire ability system could be replaced with:

Dictionary<byte, delegate> Abilities;

Abilities[ByteIDTypes.Something].invoke();

In practice, you will need to modify the signature:

static Execute(byte whichAbility, byte applyToWhichObjectID)

void ExecuteAbilityOnMe(byte byteID) //Within base-classes supporting execution
{
    if (byteID == 1) return; //1 is not valid for this class
    Abilities[byteID].invoke(byteID, myObjectID); //Do as instructed
    if (byteID == 1) Abilities[1].invoke(1, myObjectID); //Only allow Ability1
    switch (byteID)
    {
        case 1: //Allow Ability1
        case 2: //And Ability2
          Abilities[byteID].invoke(byteID, myObjectID);
          break;
        case default: //But no others
          break;
    }
}

and/or ("my" refers to the enemy being clicked):

void OnClick(bool leftButton) //Within base-classes supporting execution
{
    if (leftButton) Abilities[ByteIDTypes.Attack].invoke(ByteIDTypes.Attack, myObjectID);
}
\$\endgroup\$
  • \$\begingroup\$ You are basically faced with deciding between a large amount of indexing and switch statements or a large amount of casting and inheritance-caveats and I'm recommending the former. \$\endgroup\$ – Jon Apr 2 '15 at 5:21
  • \$\begingroup\$ I'm gonna have a huge switch statement :C. I was thinking of storing the abilities in a dictionary based on their ByteID for easy lookups. \$\endgroup\$ – JPtheK9 Apr 2 '15 at 23:28
  • \$\begingroup\$ Are you open to reorganizing the meaning of ByteID? I will modify my answer, if so. \$\endgroup\$ – Jon Apr 3 '15 at 9:56
  • \$\begingroup\$ I've actually changed it to a string and am sending input code instead of the ability name. The input code is distributed to selected units and every unit calls the ability with the corresponding code attached to them. \$\endgroup\$ – JPtheK9 Apr 3 '15 at 22:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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