Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Is there a way to store functions in scripts that have no context for them?

Discussion in 'Scripting' started by ranjansikand, Jan 29, 2023.

  1. ranjansikand

    ranjansikand

    Joined:
    Apr 8, 2021
    Posts:
    103
    I'm trying to make a modular attack system that lets me drag and drop attacks into my player. One might move the player forward, for instance, while another moves them back, while yet another makes them invisible.

    The issue I'm currently trying to solve is that I can't think of a way to store the functions in a modular way (i.e. within a scriptable object), because there are a lot of variables that it would need reference to from the player class (like the movement script, or the renderer to disappear, etc.). All of the variables would be available at the time the function is called, however, so I thought there might be a workaround.

    Has anyone done something like this and have advice?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,008
    Can't really 'store methods' on their own, not as simply as you might want. UnityEvent's exist as a way to reference methods, but they work by referencing a Unity object, and serialising a string to the method in order to invoke it via reflection. I don't think it's useful in this case though.

    SerializeReference is one option. It lets you serialise non-Unity Object reference types (ergo plain C# classes) via a base class or interface (polymorphism). It makes a good option for pluggable behaviour, and is my general approach. Only problem is that Unity doesn't have inspector support for it out of the box, so you need third party tools to make it usable.

    Another option is regular inheritance. A base class scriptable object type with derived types that override the relevant methods. Then you can just plug these into regular unity object reference fields.

    As far as getting the relevant values from the player: just pass the player into your attack method.
     
    ranjansikand and Bunny83 like this.
  3. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    My choice would be a base abstract Attack-MonoBehaviour (Component). That way your base-class defines a
    public abstract void Attack(Player player);
    , and you can add the actual attack-components to your Player-GameObject.
    In my own Project I simply do a GetComponents<Attack>() in Awake() then index that array when doing a specific attack.
     
    ranjansikand likes this.
  4. ranjansikand

    ranjansikand

    Joined:
    Apr 8, 2021
    Posts:
    103
    Thanks for the advice! I think inheritance is going to be the way to go. Otherwise, I might try calling functions via animation events, but that seems like a bit of a messy solution.
     
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,571
    I just quickly created a generic SerializableInterface class which comes with a property drawer that allows you to create a serialized field of a certain interface type. With that you can do:

    Code (CSharp):
    1. public SerializableInterface<IMyInterface> someVariableName;
    That's all. In order to access the serialized reference you can use the Instance property. So assuming IMyInterface looks like this:
    Code (CSharp):
    1. public interface IMyInterface
    2. {
    3.     void Attack(Player player);
    4. }
    you can simply do

    Code (CSharp):
    1. someVariableName.Instance.Attack(player);
    The property drawer allows you to drag a whole gameobject onto the field. It would search for the first component on that gameobject that implements that interface. If there are multiple components on the same gameobject it would display a context menu with all components that match the interface type in the order they appear in the inspector. That may be relevant if the same component is attached several times to the same gameobject. Though the usual two inspector approach does of course work as well.
     
    Last edited: Jan 31, 2023