I’ve decided to implement an ability system for my game and set the following requirements:
- Abilities must be
MonoBehavior
s, that-is, components of Player/NPC gameobjects - Abilities must be able to be added/removed at runtime. Instead of all entites having all abilities on their gameobjects that are disabled/enabled, I’d like to dynamically add/remove abilities using
AddComponent
/Destroy(component)
Given these I’ve implemented the following:
-
Settings classes which inherit from a base
AbilitySettings
class which is aScriptableObject
. These contain configurable ability settings as well as an enum calledAbilityIdentifier
which identifies the ability (for example a jump ability would have the identifierAbilityIdentifier.JUMP
) -
IAbility
non-generic interface containing a few common ability methods (such asTriggerAbility
andCanTrigger
) -
AbstractAbility<T>
class which implementsIAbility
and T is a type that extendsAbilitySettings
. It implements some of theIAbility
methods and defines others as abstract. Actual abilities extend this class. -
AbilityManager
is aMonoBehavior
which contains an array of all possible settings for that entity (added through unity editor) and internally contains a dictionary of<AbilityIdentifier, IAbility>
. All of the entities abilities are added/removed using theAbilityManager
It looks something like this:
public class AbilityManager : MonoBehavior { [SerializeField] private AbstractAbilitySettings[] allAbilitiesSettings = { }; private readonly Dictionary<AbilityIdentifier, IAbility> abilities = new Dictionary<AbilityIdentifier, IAbility>(); // Add/remove ability methods }
For example, a jump ability pickup gameobject is set somewhere in the world as a trigger. When the player moves over the pick-up object and OnTriggerEnter
is executed. The script on the pick-up object gets the AbilityManager
and calls AddAbility(AbilityIdentifier.JUMP)
This sounds good but It’s far from perfect. First of all, I couldn’t figure out an elegant way of creating/removing a component when given the settings class so I’ve added the creation/destruction code to the settings class itself. That-is I’ve added the following abstract methods to AbilitySettings
public abstract IAbility InstantiateAbility(GameObject gameObject); public abstract void RemoveAbility(GameObject gameObject);
which are then implemented in each of the concrete settings classes like this:
public override IAbility InstantiateAbility(GameObject gameObject) { JumpAbility ability = gameObject.AddComponent<JumpAbility>(); ability.Settings = this; return ability; } public override void RemoveAbility(GameObject gameObject) { JumpAbility ability = gameObject.GetComponent<JumpAbility>(); Destroy(ability); }
And these methods are called in the AbilityManager
like this
public void AddAbility(AbilityIdentifier identifier) { AbilitySettings abilitySettings = Array.Find(allAbilitiesSettings, s => s.Identifier == identifier); abilitySettings.InstantiateAbility(gameObject); }
The implementation of InstantiateAbility
and RemoveAbility
is the same for every single ability, the only difference being the ability type. This is a big smell for me. I can’t make AbilitySettings
generic and generify the two methods as these settings are in an array.
My questions are:
-
Adding methods such as
InstantiateAbility
andRemoveAbility
to a scriptable object seems like a code smell to me. Take into account that I’m using theAbilityIdentifier
to specify to the manager which ability I want to create. I have thought of perhaps creating anAbilityFactory<T>
but since it’s a generic class it can’t be a part of an array/list so I’m facing the same problem I did with the settings. Is there a different way I could handle this without having the code in the scriptable object? -
Having the implementation of these two methods
InstantiateAbility
andRemoveAbility
be the same for every implementation with the only difference being the type is also a big code smell. Is there any way I can generify this but at the same time avoid problems with the inability of having an array or list of those generic classes?