How to make a flexible skill / ability system?

Hello, I’m trying to create a flexible ability system for my card game. The combat is turn-based. Each card represents a skill or ability. Some cards can’t be played in certain conditions. The effects for a card include cause damage, add a buff, move some distance, etc. To achieve that, here’s what I’m working on:

public class CardData : ScriptableObject
{
    //Other data...
    public int Cost;
    public int Range;

    public Condition[] conditions;

    public Effect[] Effects;
}

Each card contains arrays of conditions and effects. Conditions are used to check if this card can be played or this effect should be ignored.

[System.Serializable]
public struct Effect
{
    public enum Type
    {
        Damage, //value = (physical, magic)
        AddBuff, //value = buffID
        Move, //value = moveDistance
        //...
    }
    public Type type;
    public float value1;
    public float value2;
    public Condition[] conditions;

    public void Perform(Character attacker, Character target)
    {
        type switch
        {
           Type.Damage => target.CurrentHealth -= value,
           Type.AddBuff => target.AddBuff(Id: (int)value1),
           Type.Move => attacker.Move(value1);
        };
    }
}

[System.Serializable]
public struct Condition
{
    public Type type;
    public float val;

    public bool IsSatisfied(Character character, Character target)
    {
        return type switch
        {
            Type.NotInAir => character.IsInAir,
            Type.NeedWeapon => character.HasWeapon,
            Type.InRange => Game.GetDistance(character.transform, target.transform) <= val,
            Type.OutOfRange => Game.GetDistance(character.transform, target.transform) > val,
            _ => false
        };
    }

    public enum Type
    {
        NotInAir,
        NeedWeapon,
        InRange,
        OutOfRange,
    }
}

To use the system in combat:

public class CombatManager
{
    public bool CanPlayCard(CardData card, Character attacker, Character defender)
    {
        foreach (var condition in card.conditions)
        {
            if (!condition.IsSatisfied(attacker, defender))
                return false;
        }
        return true;
    }

    public void PlayCard(CardData card, Character attacker, Character defender)
    {
        foreach (var effect in card.Effects)
        {          
            effect.Perform(attacker, defender);
        }
    }
}

Sample of the card data:


Currently this works okay for me, but it’s not very flexible. And the “valueX” thing in Effect is stupid as I have to remember meanings of the values for every Type. I am thinking of finding a way to add different types of node to the effects list. e.g. the AddBuff type will show a field where I can drag a BuffData in instead of using ids. Any advice on improving this system?

I think your system is basically perfect if instead of having a single, catch-all “Effect” and “Condition” class, you had subclasses. DamageEffect, BuffEffect, EquipmentCondition, RangeCondition, etc. That’ll make your data more meaningful and adaptable – like if you want something with three values, for example – and I think with some property drawers, you won’t have to write too many complicated editors.

1 Like