building a large ability system with Scriptable Object (SO)

I know there are posts and videos about this already but those don’t quite fit my needs.
The ones I have found all suggested this way

public abstract class ActiveSkill : ScriptableObject
{
    public abstract void Activate();
}

public  class fireball : ActiveSkill
{
    public override void Activate(){}
}

However, each skill I create would take up a slot in the create menu

If I have over 100 different skills, it’s going to get quite messy.
Is there better way to do this?
if not I suppose I can build a library that returns a function based on the skill ID.
for that I have additional questions.

  1. if I do a switch statement, with strings or numerical, is the time complexity O(n) or O(1), Disregarding the string length.
  2. would building a dictionary be better? I feel like it’d take up quite a lot of resources.

The abstract void Activate(); method looks like a “textbook” example to me, in a real time game you would need more control unless you have very simple effects, which only spawn a projectile or something.
I would say the minimum are something like:

  • ActivateEffect(GameObject user) → when the button is pressed
  • ContinueEffect(GameObject user, float deltaTime) → after the button was pressed, while you hold the button
  • DeactivateEffect(GameObject user) → after the button was released

I don’t know which posts or videos you watched, but either they are wrong__*__ or you misunderstood them. The whole point of the idea is that you don’t need a own class for every skill.

Instead you use a abstract and derived class for the skill effects, something like “DamageEffect”, “HealEffect”, “SpawnProjectileEffect” etc. and then you have one Skill class, which has a list of effects.

You don’t need a Fireball class, instead you will just create a SO asset of the skill class, call it FireBallSkill and add the “SkillEffects” in the list, for example a FireBall skill would maybe require a “SpawnProjectileEffect” and a “DamageEffect”

*(The one class per skill implementation can work for simple cases where you only have like 5-10 skills and all of these skills have very different effects)

Another case where I wish Unity had proper built in inspector support for SerializeReference. It’s the real solution for cases like this, letting you take a proper, flexible OOP approach. Where you only need the one skill scriptable object, and then you can compose your actual skill effects inside said asset.

And because I’m bored I wrote a working, basic example:

public sealed class SkillAsset : ScriptableObject
{
    [SerializeReference]
    private ISkill _skill;

    public void ActivateSkill() => _skill?.ActivateSkill();
}

public interface ISkill
{
    void ActivateSkill();
}

public sealed class FireballSkill : ISkill
{
    [SerializeField]
    private float _damage;

    public void ActivateSkill()
    {
        // big fireball effect!
    }
}

public sealed class HealSkill : ISkill
{
    [SerializeField]
    private float _healAmount;

    public void ActivateSkill()
    {
        // glowy heal effect
    }
}

#if UNITY_EDITOR
[CustomEditor(typeof(SkillAsset))]
public sealed class SkillEditor : Editor
{
    private SerializedProperty _skillProperty;
    private GUIContent _skillTypeContent;

    private void OnEnable()
    {
        var so = this.serializedObject;
        _skillProperty = so.FindProperty("_skill");
        UpdateDropdownContent(_skillProperty);
    }

    public override void OnInspectorGUI()
    {
        if (EditorGUILayout.DropdownButton(_skillTypeContent, FocusType.Keyboard))
        {
            OpenSkillSelector();
        }

        var skill = _skillProperty.managedReferenceValue;
        if (skill != null)
        {
            EditorGUILayout.PropertyField(_skillProperty, _skillTypeContent, includeChildren: true);
        }
    }

    private void OpenSkillSelector()
    {
        var genericMenu = new GenericMenu();

        var types = TypeCache.GetTypesDerivedFrom<ISkill>();
        foreach (var type in types)
        {
            genericMenu.AddItem(new GUIContent(type.Name), false, UpdateSelectedType, type);
        }

        genericMenu.ShowAsContext();
    }

    private void UpdateSelectedType(System.Object obj)
    {
        var type = (Type)obj;
        var constructor = type.GetConstructor(Type.EmptyTypes);
        if (constructor != null)
        {
            var value = constructor.Invoke(null);
            _skillProperty.managedReferenceValue = value;
            this.serializedObject.ApplyModifiedProperties();
            UpdateDropdownContent(_skillProperty);
        }
    }

    private void UpdateDropdownContent(SerializedProperty property)
    {
        var value = property.managedReferenceValue;
        _skillTypeContent = value != null ? new GUIContent(value.GetType().Name) : new GUIContent("Select Skill");
    }
}
#endif

Looks average, but it a big step up IMO:

4 Likes

@spiney199 thanks for the reply!
I do have a few questions though.
I’ve never edited the editor so I don’t quite understand how it works. Can you give me a run down?
Also why must the skillAsset class be sealed?

@R1PFake
thanks for the suggestions.
I am following the unity scriptable object session

That’s how they set it up in 51:26. Each “brain” is a separate class equivalent to my “skill.”
What you’re describing with the list of effects is similar to what I am attempting, a dictionary or a giant switch statement that returns the function/effect.
I haven’t decided which is better yet. If the switch statement is O(N), then I probably go with the dictionary; however, the dictionary feels like it would take up more resources.
I was hoping there’d be an easier way to modify the unique functions before diving into that hole.
The void return is just a place holder. I am trying to get the set up right first.

1 Like

Definitely start with any one of the custom editor tutorials out there… no sense in one of us retyping it all poorly here in this little text box.

Spiney wrote a little custom editor for the Skill class, that’s all. With those editors you can get as simple or as crazy as you like. Technically the 3D ProBuilder tool is just a custom editor.

You won’t find that by asking on a forum because your constraints are likely different than what you state, either constraints on your comfort level or else in your game design.

Start work now because you will only begin to learn as you actually bump against rough spots and change course.

That’s how software engineering and learning work in general. :slight_smile:

Imphenzia: How Did I Learn To Make Games:

1 Like

It doesn’t need to be, though it just emphasises the point of only needing the one scriptable object class without any inherited types.

2 Likes

If your just worried about cluttering your CreateItem menu, you can just just add a “/Abilities” to the end of your CreateAssetMenu. i.e CreateAssetMenu(menuName=“CreateItem/Abilities”. Changing your whole class infrastructure just to get a small UX gain seems like overkill.

1 Like

Creating custom editors or sub menus is fine, but I still think that they hide the actual “issue”. I don’t think implementing 100+ different skill classes is the correct way.

Your first question should be if you really need that many skills, if you do then I would suggest you to rethink your design, unless all of these 100+ skills have unique effects (not just different values, but different logic).

@partimevillain I see, in the video it makes more sense to create a own class per brain, because they have a specific implementation, but RPG skills are a bit different in my opinion, because it makes sense to reuse the “effects” instead of creating a specific class for every skill. Doesn’t matter if your fireball deals damage or a weapon ability deals damage, both of them can share a “deal damage effect” instead of implementing the code twice.

There is a other Unity tutorial for a ARPG, it’s kinda basic and doesn’t show skills, but it has items with different effects which is almost the same idea and it also contains some custom editors if you want to learn more about them, but the tutorial is kinda “old” so there might be better ways to handle it (for example SerializeableReferences instead of nested SO assets for the effects), but should be enough to get you started, you can always improve later.

The (free) Unity asset which contains the finished code with examples:
Warning, do not import this in your game project, create a different project just for the example and import it there!

The learn page for the asset (The link should lead you to the health poition example)

1 Like

@R1PFake
Thank you. I will definitely check out the links.
and yes for my skills I do plan on having different effects. They’d execute at different times. They’d have, multi-hits, different uses. some might even summon instead of dealing damage or give player a new set of abilities.
could you link me to the ARPG tutorial you mentioned? I’d love to see how they set up their architect

@Bazz_boyy
I’ve found that I could just remove the createitem line after creating the SO could work too.

@spiney199
I see. thank you for the input

SerializeReference is immensely powerful and in many cases it’s a better alternative than using SO, or it can be used in conjunction with SO. However it’s most powerful usage is with proper editor support which Unity doesn’t offer out of the box.

For anyone looking for a lightweight solution for SerializeReference property drawer, I highly recommend this package. 30KB, MIT license. Great alternative to Odin Inspector if all you were looking for was SerializeReference editor support.

1 Like