How to store functions in Scriptable Objects

Hello! I recently tried to program a game and wanted to create weapons with different abilities rather than just stats. So while I was searching for answers, I came across this post, which answered how to add functions to scriptable objects. However, I am a little confused about the application. In my project, I have a simple scriptable object called WeaponHandler

public class WeaponHandler : ScriptableObject
{
    public string Name;
    public GameObject Object;
    public Sprite Icon;
    //animation
    
    public float Cooldown;
    public float AttackDamage;
    public int Ammo;
    public virtual void OnUse()
    {
        Debug.Log("customize this");
    }
    //on hit
    
}

And In it, I created a scriptable object called “Katana” in the editor. I tried to mutate the OnUse function by making another class named Katana in the same file and mutating the function like this.

class Katana : WeaponHandler
{
    public override void OnUse()
    {
        Debug.Log("Katana");
    }
}

However, when I call the function from the Katana object it writes “customize this”. How would I actually store a custom function in a Scriptable Object?

You could try marking your OnUse function as abstract instead of virtual. Abstract functions only have a definition but no implementation, so calling OnUse from the Katana class should print “Katana”.

To answer your question you can add a method (a class function) to an object by declaring it like:

// Method signature with return type
AccessModifier ReturnType NameOfMethod(Param1Type param1Name, ...) 
{
     // Do some stuff here
     return returnTypeInstance;
}

e.g.

public string GetName(string firstName, string surname)
{
    return $"{firstName} {surname}";
}

// Or a method without a return type would use a void return type and would not need to invoke the statement

public void DoSomething(string value)
{
    // Do something here
}

You can also use delegates/ lamdas to store functions into variables by declaring them using the Action or Func types Funcs having a return value and Actions being functions without.


That said for this I would probably learn about interfaces (in the loose sense either interfaces or abstract classes) and use a strategy pattern combined with composition i.e. keep the scriptable objects just as lean data only POCOs and then add an instance or collection of them as a member on another class and do all the weapon behaviour logic on that instead:


You have things mixed up, the 1st script you create a scriptable object and then you say you make an instance of it called katana, but then you make a new katana script that inherits from WeaponHandler, but your scriptable object instance is still a WeaponHandler not a katana.

To use scriptable object you need to make the the instance of katana script not WeaponHandler script.

So to put an end to this as my original post that you refer to was not clear enough. This is how it is intended to be used and this is how it works (tested).

using UnityEngine;

public class BaseObj : ScriptableObject
{
    public virtual void OnUse()
    {
        Debug.Log("Base Obj");
    }
}

public class Derived : BaseObj
{
    public override void OnUse()
    {
        Debug.Log("derived");
    }
}

public class testscript : MonoBehaviour
{
    public BaseObj EditorAssignedScriptableObjectTest;
    public void Start()
    {
        var baseObj = new BaseObj();
        var derivedObj = new Derived();
        BaseObj baseDerivedObj = new Derived();
        baseObj.OnUse();
        derivedObj.OnUse();
        baseDerivedObj.OnUse();
        EditorAssignedScriptableObjectTest.OnUse();
    }
}

In ScriptableTest.cs i added this class as well to show that this can be used by loading from resources as well:

[CreateAssetMenu(fileName = "ScriptableTest", menuName = "ScriptableObjects/testObj", order = 1)]
public class ScriptableTest : BaseObj
{
    public string objName;
    public override void OnUse()
    {
        Debug.Log("derived name: " + objName);
    }
}

Then rightclick in your resources, go to Create → ScriptableObjects → testObj

Set some name for the object. Mine looked like this:

Then you assign that to your testscript behaviour:

This will create the following 4 messages in the following order:

  • Base Obj
  • derived
  • derived
  • derived name: Whatever name you assigned in the obj you created

If this is not how you are using this then please update your question with some more details of how you apply this.

What if, you make a scriptableObject that acts as a data container, with a gameObject variable called prefab. Then each scriptableObject gets its own prefab, and each prefab gets its own script that utilizes an interface, acting as the dynamic function.

You can then code each instance of the interface function separately, yet invoke them all with the same call (via the prefab in the scriptableObject).


Here would be the scriptableObject:

[CreateAssetMenu(fileName = "NewSO", menuName = "ScriptableObjects/SO")]
public class MySO : ScriptableObject
{
     public float health;
     public float damage;
     ...

     public GameObject prefab;   // prefab gets dragged here in the inspector 
}

Here would be the interface:

public interface IMyInterface
{
     public void Use();
}

Here would be an example of a prefab script that implements the interface:

This would be on the prefab that gets dragged into the scriptableObject’s prefab variable.

public class MyClassNameHere : MonoBehaviour, IMyInterface
{
     public void Use()
     {
          // CODE GOES HERE
     }
}

Of course, you would need to make a new prefab along with a new script that uses the interface for each new scriptableObject you create and define. You would also need to cache the scriptableObject on the gameObject you wish to call the interface function from. So you wouldn’t be able to just inject the data from the scriptableObject and move on, you would need to store it in a variable to invoke the custom interface function on demand.


Finally, an example of an object’s script that might invoke the interface:

public class MyObject : MonoBehaviour
{
     public MySO data;   // the scriptableObject storing the prefab  

     public void Use()
     {
           // invoking the implementation of the prefab's interface stored on the scriptableObject

          IMyInterface _object = data.prefab.GetComponent<IMyInterface>();
          _object.Use();
     }

     public void InjectData(MySO data)
     {
           // to set the data at runtime

          this.data = data;
     }
}