Modular ability creation in editor

Newbie here

My goal:

I want to modular generate different abilties in the inspector that can be attached to a Monobehaviour. Abilty values can also be tweaked in the inspector seperatly from each other

What i came up with so far

Every unique Ability is a ScriptableObject asset. It contains an array of AbilityProperties. AbilityProperty is also an ScriptableObject and acts as a “superclass”. Unique AbilityProperties derive from AbilityProperty and implement a simple interface, which only holds a method called execute(). With a custom drawer you are able to attach unique AbilityProperties and tweak the values of every single one of them.

The problem

Now the big obvious flaw in this design is, when i tweak an AbilityProperty value in one Ability it will change in every other Ability too. Because there is only one AbilityProperty_Damage ScriptableObject. Or I have too create a ScriptableObject Asset for every property of every ability which seems nuts.

So anyone has an idea how to tackle this problem? Since I’m at the beginning I can redesign the whole thing.

The code

Sorry for the wierd formating but here i had an issue with the web editor:

[CreateAssetMenu(fileName = "Ability")]
public class Ability : ScriptableObject
{
    public Property[] properties;
}

public interface IProperty
{
    void execute();
}

public class AbilityProperty : ScriptableObject
{
    public string propName;
}

[CreateAssetMenu(fileName = "propDamage")]
public class Property_Damage : Property, IProperty
{
    public int damageAmont;
public void execute()
{
    // some damage specific logic
}

}


using UnityEngine;
using UnityEditor;
using System.Reflection;
[CustomPropertyDrawer(typeof(AbilityProperty))]
public class AbilityPropertyDrawer : PropertyDrawer
{
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        if (property.objectReferenceValue == null)
            return base.GetPropertyHeight(property, label);
    var modifier = property.objectReferenceValue as AbilityProperty;
    var fields = modifier.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);

    return base.GetPropertyHeight(property, label) * (float)(fields.Length + 1);
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    position.height = base.GetPropertyHeight(property, label);

    EditorGUI.PropertyField(position, property);

    if (property.objectReferenceValue == null)
        return;

    var modifier = property.objectReferenceValue as AbilityProperty;
    var fields = modifier.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
    var sObj = new SerializedObject(property.objectReferenceValue);

    position.x += 20f;
    position.xMax -= 38f;
    foreach (var field in fields)
    {
        position.y += position.height;
        EditorGUI.PropertyField(position, sObj.FindProperty(field.Name));
    }

    sObj.ApplyModifiedProperties();
}


}

Awesome I made something similar to this in my current project! Was pretty proud of it :stuck_out_tongue: I called them Character Actions though… :stuck_out_tongue: You will need to make a SO instance for each ability… but that is normal for SOs, when you start using SO variables it becomes second nature. Instead of an interface I made an abstract base class with a virtual method Perform, but I think it is pretty much the same…
I wanted to be able to add and remove actions for different characters, nothing hard coded, extendable modular and more decoupled! But how I implemented it was pretty neat too I made an informal finite state machine that used fuzzy pattern matching to select the state.
I did this by making another abstract class called Condition that is also an SO with a virtual method bool Check() so basically anything can be a condition, pressing a certain button, health level, anything! Then I made my actions have a list of conditions and a method bool CheckConditions(), then I can check if all the conditions are met. So then in the script that implements it all I have a list of CharacterActions that I sort based on how many conditions there are, I got the fuzzy pattern matching and sorting by number of conditions from a GDC talk on Left4Dead, you want the actions with the most conditions to fire off first… then I loop through it all like this:

    List<CharacterAction> characterActions;
    bool canAct=true;
    ..
    Update(){
    if(canAct){
    foreach(CharacterAction action in characterActions)
    {
        if(action.CheckConditions())
        {
            action.PerformAction();
            canAct=false;
            ResetCanAct(action.delay);
            break;
        }
    }
    }
    }
    void ResetCanAct(float p_value)
    {
        Invoke("InvokeReset",p_value);
    }
    void InvokeReset()
    {
        canAct=true;
    }
    // I just like Invoke should probably be a coroutine..

so you do have to make an SO for each ability, but it’s works pretty good.