ScriptableObject Ability system with nested Effects and Targets

I’m trying to make an ability system, but I can’t quite figure out if what I’m trying to do is possible with ScriptableObjects or not, and the threads I can find on the issue don’t seem to answer the question.

I’m hoping to make an ability system that’s something like this.

AbilityData is a ScriptableObject type that defines the inputs and the effects contained in an ability. Inputs represents that users actions that are fed into the ability execution, i.e. in the majority of cases selecting one more more targets.

Inputs are represented by InputData, which consists of two parts, a base InputType (i.e. unit, position, vector), and then a list of filters the represent restrictions on that type, for example for each type possible filters could be

Unit - ally, enemy, corpse, etc.
Position - empty, obstacle, etc.

and so forth.

Effects are pieces of game logic that process inputs to manipulate game state in some way. For example, a simple DealDamage effect would take in a target unit and apply damage. A teleport effect would take in a unit and position and change the unit’s position to the input position.

So the data structures would like something like

InputData
InputType inputType
List inputFilters

AbiltityData
List inputData
List effectData

EffectData
List inputs
void Execute(List inputs)

The order of the inputs in AbilityData represents the order in which the user will have enter the inputs when using the ability.

As a concrete example, we can imagine a ForcePush ability that deals damage to an enemy unit and then pushes it in a direction. It should look something like

ForcePush (InputData)

inputData = [
InputData{ unit, [enemy]},
InputData{ vector, [starting point=input[0].position])
]

effectData = [
DealDamage(EffectData)(input[0]),
Push(input[0], input[1]
]

The part I can’t figure out is how to have the InputData refer to previous inputs in the same ability and how to map the inputs to the parameters of the effect data.

In the push example, we want the filter on the second input (which is a vector) to be that the vector must start from the position of input[0] (i.e. the enemy selected).

And then for the effects I need some way to map the inputs themselves to parameters. For example damage should be done to the first input (which must be a unit), and the Push effect should be done on the first input (which must be a unit) in the direction of the second input (which must be a vector). I also can’t figure out how to validate that the types match. Finally, in some cases it’s possible that the parameters won’t be user inputs, but rater static selections, for example Caster (a unit), CasterForward(vector in which the caster is facing), etc.

Are there any tutorials on how to do something like this? I imagine pretty much every ability system needs this kind of flexibility.

Does anyone know if there are any resources on this? I would imagine practically every RPG needs some kind of ability or skill system that allows flexible type of targeting and mapping the targeting to a list of effects.

The resources I have found (e.g. Create an Ability System with Scriptable Objects - Unity Learn) have not shed any light on this issue, which seems strange because every RPG must have an ability system that allows targeting and mapping to effects.

Uf, all right, there’s a ton of stuff here for me to be able to wrap my head around right now. How about something like this for starters:

public interface InputData
{
    InputType inputType;
    List<InputFilters> inputFilters;
}

[System.Serializable]
public class TargetParams : ScriptableObject, InputData
{
    public InputType inputType;                    // This is your "unit"
    public List<InputFilters> inputFilters;        // This is your "[enemy]"
}

[System.Serializable]
public class AbilityData : ScriptableObject
{
    public List<InputData> inputData;
}

So now you should be able to create a single AbilityData object and fill it with your inputs of any type you want. Now, as I said, I’m still trying to wrap my head around what you’re actually trying to do here, so it’s going to be a ton of back on forth discussions here before we get to a solution.

P.S. - Sorry about syntax errors, I wrote this in notepad++

Thanks for the reply.

So you don’t recommend using a ScriptableObject to hold ability data? How would the different ability files actually be stored, then?

Also the part that I was having trouble with is mappping the inputs to the effects to the inputs of the ability.

Like if I have a push ability where you select an enemy, then select a vector radiating out from that enemy to push that enemy, then I want to apply damage and a push to that enemy as two separate effects, then the damage would take the first input (which must be a unit), and the push would take the first and second inputs (which must be a unit and a direction). I don’t know how to enforce that the mapping is valid though.

The best resource I was able to find was this:

However he does a subclass for each single type of targeting. This isn’t very flexible because you can imagine an infinite combination of different types of inputs for an ability. For example a shackle ability might require the user to select a unit and then a ground point. The aforementioned push ability might require a unit and a vector.

Another system I was able to find was this:

https://www.healplzgame.com/unity-scriptableobject-spell-system-part-1/

However this doesn’t have any implementation details, and it seems like it only supports single target spells (so something like AoE or spells with heterogeneous inputs like a unit and a vector wouldn’t work).

1 Like

So far this has been the best resource I found:

The one issue I see with that is the issue pointed out by HiddenMonk - all SkillAim classes will need to return the same type from their GetTarget function. He suggests making a base class Target (with derived classes like Unit, Point, etc.), and then each ability would then check what the actual subclass of target is before using it.

However, this means you could create an ability that could have incompatible Aim components and Effect components. For example, if you have an ability that uses a PointAim component that returns Point (a derived class of Target) and a Damage component that expects a Unit input, then when you try to use that ability it will probably trigger a run-time exception or the ability won’t fire correctly because you’re trying to use a Point as a Unit.

I’m wondering if there’s any way to check from this when you’re actually constructing ScriptableObjects in the editor. I.e. is there any way to enforce in the editor that the type of targeting you pick actually matches the expected type of target input of the effects in that ability.

Another issue I’ve run into is how to represent the nested components of abilities. For example, an ability contains targeting and a list of effects. Should there be Targeting and Effect ScriptableObjects? If so, it seems like this would create a lot of repeated and duplicate assets, for example you’d need a different SingleTarget ScriptableObject asset for every ability that uses SingleTarget targeting, and the only thing that would change would be the number. You wouldn’t be able to share target SOs between abilities, because changing one would change them all.

This thread touches upon this issue: ScriptableObject skill system, how to better organize?

Madgvox attempts to provide an editor solution that automatically creates sub-assets, but there seems to be issues with it (get stranded sub-assets that you can’t delete).

The other solution seems to be to represent these submodules like targeting as Serializables and place them inside the ability ScriptableObject, but this doesn’t seem to play well with the editor.

As an example of what I mean by nested abstract Serializable classes not working well in the editor, consider this simple example:

using System.Collections.Generic;
using UnityEngine;


[CreateAssetMenu(menuName = "Abilities/Ability")]
public class Ability : ScriptableObject
{
    public string abilityName = "New Ability";

    [SerializeField]
    List<AbilityInputInfo> inputInfos;
}
[System.Serializable]
public abstract class AbilityInputInfo
{

}
[System.Serializable]
public class UnitSelectInputInfo : AbilityInputInfo
{
    int range = 5;
}

With this code, if you try to make an Ability, then you will not be able to edit the elements of the inputInfos list in the editor. It will simple show Element0, Element1, etc. without allowing you to see or edit the contents. Ideally, for a given element in the inputInfos list, you’d be able to select the subclass (e.g. UnitSelectInputInfo) and then fill in the contents (e.g. range), but this doesn’t seem to be possible.