Need help understanding how to build a modular spell/ability system

Hey folks,
So i’ve been trying to work out how to build a modular spell system recently. I want to be able to make spells to damage/heal and buff/debuff, but i also want to be able to make different types of spells like Beams, Projectiles, AoEs etc and with different casting types like Fire and Forget, Channel etc and then to top it all off i wanted to add passive abilities like flight or wall climbing in the same system to.

I started off browsing the internet for tutorials and resources and came across an unfinished series by BurgZerg Arcade and i also found this video and downloaded the project files they linked. It seems like this is just using a massive chain of “if/else if” statements to cast the spell, which when combined with all the targeting types and effects that i could theoretically want to eventually implement would end up creating a huge tree of “if/else if” statements which doesn’t seem right. The other issue with this method is that i want to include passive abilities like flight or wall climbing under the same system and i can see that potentially maybe causing some issues down the line.

The other thing i looked at which seemed more like what i need was Skyrim. I started looking in the Skyrim creation kit to get an idea of how their spell/ability system works and the entire thing seems component based which sounds just like what i need. So for example Skyrims fireball spell has a damage, stagger and fear component attached to the spell with all the spell targeting and casting type etc handled by the parent object. And any buffs or abilities from weapons/armor or race etc are handled within the same system.

The thing that i really need help to understand is how to plug it all together. I can make components that can deal damage, fear, knockback etc no problem. I just need some help understanding how to make a main spell handler that for example i could give a beam targeting component, a channeling type component and then a damage component for one spell, but could then create another spell which passively grants flight for example.

I don’t suppose anyone could maybe help me with some pseudocode or something just to help me try and get the basic concept of how to do this?

Thanks :slight_smile:

Firstly when you are developing this kind of feature, which is complex, I will make sure I have all the functions available to me that maybe are not directly linked to the spells. Like a GiveDamageToPosition, GiveDamageInRange, A Projectile System (to which I request a bullet, add direction and damage + maybe select some visual effects) and every thing you will need for your spells. Don’t think of when and how will a Fire Aoe Damage with flight passive work, but what function will it call to have my Player fly. When you think you have all your functions and systems ready, then it’s the time to make a modular spell system. I will make a BaseSpel class that will have the basic functionalities. Here you have to decide exaclty how a spell should look. Ex: a spell has a damage type, and you define some clear enums (fire, water etc)(Enumeration types - C# reference | Microsoft Learn), dispersion type (aoe, projectile, etc), + passive. You must be very clear of how many components does a spell have. From here you could make many types of system. First in my mind is make your SpellBase class have functions for all the spell parts :

LunchSpell - handles the dispersion type of the spell
GiveDamage - handles damage on an single enemy and takes into account damage type
GivePassive - give passive to a single enemy (yourself)

Also for each type of modular par of the spell you should make a class (which is not monobehaviour) but also extands a base class (ie: BaseDispersion)
When you are constructing a spell you create a new instance of each of this decided type

new FireDamage(), which extends DamageBase
new AoeDispertion(), which extend DispertionBase,
new FlightPassive(), which extends PassiveBAse

each base class should have a function like StartEffect, that will be override by each modular spell type

How one chain will work:

Firstly I create my spell
I will have lets say 3 variables:

DamageBase currDamage
AoeDispertion currAoe
PassiveBase currPassive

When you decide your spell type you will add proper elements in each variable:
currDamage = new FireDamage();
currAoe = new AoeDispertion();
currPassive = new FlightPassive();

Then when you want to cast a spell you will call something like currSpell (of type SpellBase).LunchSpell()

Here you call currDispertion.StartEffect(); because all the dispersion types will be a class that inherits the DispertionBase they will all have StartEffect.

In StartEffect you will write code for, in our situation aoe. That is you take every enemies at a point in a range and call currSpell.GiveDamage() to each one.

in currSpell.GiveDamage() you will call currDamage.StartEffect(enemy), in which you will receive an enemy and give fire damage to it, then call the passive effect with that enemy, or yourself, depending of spell type ex: currSpell.GivePassive()

and in givePassive you will use the same logistic as before.

This type of project you want to do is a bit complex and requires a good amount of oop knowledge. Make sure before starting that you understand 100% how inheritance and overriding works. Also don’t be afraid to reiterate your system and rewrite it until you like it. I bet that the skyrim guy rewrote there spell classes 3 or 4 times, or they have past experience in at least 2 or 3 different implementation of this kind of spells.

Now I don’t say that my solution is the best, but I think its an easy to maintain and add idea, and it is surly in the right direction. Pleas ask what part were unclear.

Here are some things I thought of while discussing a similar topic Damage/Effects Bundle - Unity Engine - Unity Discussions

This is probably the most important part: to avoid the huge list of if else statements, you should look into expression trees. Basically, this involves parsing something like an algebraic equation into a tree which describes the how and order of an algorithm to solve it.

Secondary, but important to keep your code clean: try to separate your concerns cleanly.

  • keep your ballistics (code that determines if/where things should happen) separate from your payload (code that causes things to happen)
  • keep your instantaneous effects separate from your continuation over time effects