5
\$\begingroup\$

I'm making an RPG and got to the point where I want a player to be able to use abilities. I decided to use a command pattern to keep keybindings flexible and now I've arrived with the following dilemma:

Command is an abstract class with an abstract method called Execute(). Every ability has a command class (for example FireBallCommand). I have a PlayerInput class. All buttons and the abilities that belong to it are defined there:

public class PlayerInput : MonoBehaviour {

private Command buttonF;
private Command buttonQ;

private PlayableCharacter character;

void Awake () {
    character = GetComponent<PlayableCharacter>();
    Initialize();
}

void Initialize()
{
    //Setup controls here
    //TODO: Get user's saved controls from db

    //Temporarily hardcoded
    buttonF = new BasicAttackCommand();
    buttonQ = new FireBallCommand();
}

void Update () {
    if (Input.GetKeyDown(KeyCode.F))
    {
        buttonF.Execute(character); 
    }
    if (Input.GetKeyDown(KeyCode.Q))
    {
        buttonQ.Execute(character); 
    }
}
}

The FireBallCommand class looks like this, (atm it creates a GameObject because Command does not derive from MonoBehaviour because I read you should/cant create object using 'new' with MonoBehaviours, in code):

public class FireBallCommand : Command {

public override void Execute(PlayableCharacter character)
{
    GameObject go = new GameObject();
    go.AddComponent<FireBall>();
    go.name = go.GetComponent<FireBall>().Name;
    go.GetComponent<FireBall>().Execute(character);
}
}

Next, the abstract class Ability contains an abstract method Execute() as well. It also contains some properties such as name and cooldown. The actually ability derives from Ability and actually executes the ability:

public class FireBall: Ability {

PlayableCharacter character;

public override void Execute(Character character)
{
    this.character = character as PlayableCharacter;
    StartCoroutine(Fire());
}

IEnumerator Fire()
{
    //Here it fires the fireball but there's no need to post that here.
}
}

This is the first time I am making a system like this. The dilemma is that I am not sure whether this is a right way to do it. It feels like casting one ability or spell is passing way to many classes and taking too much code in general. I thought of creating an AbilityManager that is always in the scene, which could be called to cast any spell/ability and takes a player as parameters.

In short: Do you think this would be a fine and fairly clean way to create an ability system? What other way(s) do you suggest? What could I change/remove/add to improve it?

P.S if anyone feels like answering this. I'm not sure where and how to implement cooldowns for the abilities in this system. Got an idea? :)

Thanks a lot in advance guys, if there's anything I should provide please let me know.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Please note that there are countless different ways to approach this problem in Unity. So there won't be a canonical answer to this question. The answers to this question will only provide you with inspirations. You have to decide which approach is the right one for your particular game. \$\endgroup\$
    – Philipp
    Oct 5, 2018 at 16:04
  • \$\begingroup\$ @Philipp I'll keep that in mind. I'm going to try a few ways to do this and then decide. \$\endgroup\$ Oct 5, 2018 at 16:58

2 Answers 2

4
\$\begingroup\$

A very "unity" solution which I am using in one of my project would be to use prefabs to represent abilities. When the PlayerCharacter class uses an ability, it does nothing but instantiate a prefab which was previously assigned through the inspector. The prefab is then responsible for doing everything else about the ability. The prefab either is a fireball projectile with Renderer, ParticleSystem, Rigidbody, Collider, scripts, etc., or it is just an invisible game object which just does its job and then immediately destroys itself.

To implement cooldowns you could create a class like this:

[System.Serializable]
public class AbilitySlot {
      public GameObject abilityPrefab;
      public float cooldownTime; 
      [HideInInspector] public float cooldownTimeLeft;
      public int manaCost;
      public KeyCode hotkey;          

}

and then have a public AbilitySlot[] abilitySlots in your player class to keep track of all the abilities available to the player. You should then be able to edit this array in the inspector to assign the ability prefabs, set the cooldown time, the mana cost and assign a hotkey.

When the MonoBehaviour which handles the movement and damage mechanics of your fireball is also sufficiently data-driven, then this architecture should allow you to add a new ability without writing a single line of code.

\$\endgroup\$
1
  • \$\begingroup\$ Alright. This is definitely a whole different approach to what I initially expected but it seems like a much smaller, clearer architecture so far. It's great to see the way someone else approaches it. I'll try this, it will help a lot while making my decision. Once again Philipp, thanks a lot for your answer. My last post was more than a year ago and you answered that one too. I'm glad I got you to answer this one again. Thank you! \$\endgroup\$ Oct 5, 2018 at 16:57
2
\$\begingroup\$

I agree that @Philipp's answer is a very "Unity approach" and isn't inherently bad, however I've found that I strongly dislike having prefabs lying around in wait.

You can, however, instantiate a prefab without having it already present and waiting. I tend to use a controller class for things like this (one for abilities/spells, one for maybe loot or the like, etc). The controller sits on a Controller prefab that doesn't destroy as scenes are switched, and can be called to generate anything anywhere in the scene as needed. It also allows for some interesting and quick changes - say using a fireball spell as a trap that originates from a rune or some mechanical spout.

Conversely, if you use this approach it may be worthwhile to use a object pool in the event that you need more than one of a given gameObject at once.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thank you for sharing your approach to this, I'll keep it and mind and try it as well. I guess that is what I meant with the Ability Manager I mentioned in my post. I'm going to try all approaches and then make a decision on this. Definitely going to implement object pools where necessary. \$\endgroup\$ Oct 5, 2018 at 18:17

You must log in to answer this question.

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