Stuck moving a prefab based spell system to scriptable object based

Hey folks,

I started working on a side project to learn how to make a modular spell system ages ago and after doing tons of research i ended up with a system that is entirely built from monobehaviours. One of the key features i wanted was a conditional effect system, so for example a healing aoe spell can only affect units in an area if they are the correct faction, or a vampiric attack would only heal a player if the damaging part of the attack hits. It all works nicely but i want to move the entire system over to scriptable objects because the entire system is just data (and i thought it would be a good opportunity to properly experiment with scriptable objects).

My problem is i’ve been fiddling around for a few days now and i can’t work out a way to make the same modular style system with scriptable objects. My original system uses the scripts below, which can all be extended to make any sort of effects and conditions i’ve wanted so far.

I’ve watched Richard Fine’s talk on Scriptable Objects, the Pluggable AI tutorials, the Ability System Tutorial and i tried pulling apart the adventure game demos condition/reaction system, but i’m really struggling to work out how to make a modular system to work with Scriptable Objects instead. Could anyone give me some help to point me in the right direction?

Ability

public class Ability : MonoBehaviour {
  
    public List<Target> targets; 

    public void UseAbility(Unit caster)
    {
        //For each target script in ability
        foreach (Target t in targets)
        {
            //initiate list and gather targetable units
            List<Unit> tUnit = new List<Unit>();
            tUnit = t.GetTargets(caster, requireLineOfSight);

            //for each effect on a targeting script
            foreach (Effect e in t.Effects)
            {
                //apply effect to each unit in targeting script
                foreach (Unit u in tUnit)
                {
                    e.ApplyEffect(caster, u);
                }
            }
        }
    }
}

Target

public class Target : MonoBehaviour{
    List<Effect> _effects;
    public List<Effect> Effects { get { return _effects; } }
    public virtual List<Unit> GetTargets(Unit caster, bool requiresLos) {
    }
}

Effect

public class Effect : MonoBehaviour {
    public virtual void ApplyEffect(Unit caster, Unit effectTarget)
    {
    }
}

ConditionalEffect

public class ConditionalEffect : Effect {
    [SerializeField]
    List<Condition> condition;
    public List<Condition> Condition { get { return condition; } }
    [SerializeField]
    List<Effect> effect;
    public List<Effect> ConditionalEffects { get { return effect; } }
    public override void ApplyEffect(Unit caster, Unit effectTarget)
    {
        bool canApply = true;
        foreach (Condition c in condition)
        {
            if (!c.IsConditionTrue(caster,effectTarget))
            {
                canApply = false;
                break;
            }
        }
        if (canApply)
        {
            foreach (Effect e in effect)
            {
                e.ApplyEffect(caster,effectTarget);
            }
        }
    }
}

Condition

public class Condition : MonoBehaviour {
    public virtual bool IsConditionTrue(Unit caster, Unit target)
    {
        return true;
    }
}

I’m not getting the idea behind your scripts, but this might help anyway:

You might have a list of basic data for each spell, stuff the game’s spell book would show:

class SpellData {
string name=“fireball”;
int damage=5;
Condition[ ] C; // size 0 = causes no conditions
}

Then some dontDestroyOnLoad script would hold an array of them. Just a Monobehave on some empty
wth public SpellData[ ] SD; in it. You’d enter spell data in the object, in that array.

Your actual prefab for a fireball would have red flames, the collider … and another script with active spell data, like who cast it and when. It would just have a link to the master spell list. In this case, the index. When the spell is cast, you’d look it up in the master SpellData object:

class ActiveSpell : MonoB {
int spellTypeID=4; // fireball is #4 in the master array
SpellData S; // looked up using the spellTypeID

mobScript caster; // active data
float startTime;

Awake() {
S = something.something.SD[spellTypeID]; // fetch using the idNum
}
}

That’s not the worst thing, but you can improve it by getting rid of the master spell object and the spell lookup. Just make SpellData into a scriptableObject. Now you can create each spell type as an Asset. Name the fireball asset “fireballData”, since why not. You still manually enter in the spell data in the Inspector, but now you just select the asset to do it. There’s no spell-array-holding script, and no empty gameObject holding spell descriptions.

In your ActiveSpell script, you can now get rid of spellTypeID. Instead, just drag the link from your fireballData asset into the fireball prefab’s SpellData slot (prefabs can link to Assets, so this is legal.) Now you no longer need Awake.