Beginner question: List of method pointers/references with parameters each?

Hello everybody

I’m very new to unity, had a little bit of programming experience before, but now I’m kinda getting into some more advanced stuff and I have hit a wall:

Currently I’m trying to create a simple, UI-based 2D game. I have a MonoBehaviour script called “EventConductor” that contains a bunch of methods to show stuff on-screen: Currently, these methods display speechbubbles, character images, emotes, and so on…

I also have a “DataController” script that contains several pre-generated variables and object instances (think Items, Characters, etc…). All other scripts pull from this one if they need any predefined data (I’ll probably outsource most of this to XML files or something similar later and the DataController just loads and parses it).

Now, I want to save a “Sequence” of EventConductor methods into a list (or similar), so I can basically construct a preconfigured “cutscene” in my DataController. The EventConductor then pulls that list and executes the methods in the correct order with appropriate delays between them to show the “cutscene” on-screen.

How would the more experienced among you go about this? I’ve tried a lot of things - first, I made all the EventConductor methods IEnumerators and saved them into a list… quickly realized that’s a bad idea. Then I tried Delegates, but my EventConductor methods have lots of different parameters with different types each and I can’t seem to pass any parameters with a delegate…

I mean, this is such a basic piece of functionality for any game with cutscenes, I feel really dumb that I can’t figure it out… been stuck on this for four days now.

Thanks in advance for any tips!

I’d make a special one of these event things that can contain and sequence any number of other ones.

Or else make a “runner” that takes a list of them, perhaps with time information too.

Or you could look at Unity Timeline for making cutscenes. Not sure what the state of it is.

I wouldn’t reach for XML if I were you, since it is 2021. I’d either use JSON (with the Newtonsoft JSON .NET package free off the asset store), or else look into ScriptableObjects as your data backend containers.

I guess you mean Unity Events, which admittedly I haven’t really looked into so far. I kinda assumed they were mostly used for input handling and the likes, but I guess that is a misconception. However, as far as I can see, it is not possible to pass parameters with events, which is a problem for me.

Considering the data backend, I’ll do some research on best practices for that. But I’ve always thought XML is still faster to process than JSON, which just has the advantage of being more readable…

Not sure why parameters would be a problem with delegates. With Action being the easiest to handle imo, match the type parameters of Action to the parameters of the function you’re pointing it at.

using System;
using UnityEngine;

public class ActionDelegate : MonoBehaviour
{
    void Start()
    {
        Action<float, int, string> handlerFunctionPointer;

        handlerFunctionPointer = HandlerFunction;

        handlerFunctionPointer.Invoke(1.0f, 1, "hello");
    }

    void HandlerFunction(float myFloat, int myInt, string myString)
    {
        //do thing
    }
}

Use Func if your handler function has a return type other than void.

using System;
using UnityEngine;

public class FuncDelegate : MonoBehaviour
{
    void Start()
    {
        Func<float, int, string, bool> handlerFunctionPointer;

        handlerFunctionPointer = HandlerFunction;

        handlerFunctionPointer.Invoke(1.0f, 1, "hello");
    }

    bool HandlerFunction(float myFloat, int myInt, string myString)
    {
        //do thing

        bool someBool = true;

        return someBool;
    }
}

Good luck!

1 Like

Thanks for the answer mopthrow:

I am aware that you can pass parameters to delegates upon calling invoke(), but my problem is that I define the parameters in the data, not at runtime. So, I need a way to pass my parameters with the delegate.

As in your example, what I need is something along these lines:

    using System;
    using UnityEngine;
    
    public class ActionDelegate : MonoBehaviour
    {
        void Start()
        {
            Action<float, int, string> handlerFunctionPointer;
           
            //this obviously doesn't work
            handlerFunctionPointer = HandlerFunction(1.0f, 1, "hello");
    
            handlerFunctionPointer.Invoke;
        }
    
        void HandlerFunction(float myFloat, int myInt, string myString)
        {
            //do thing
        }
    }

Basically, the method that calls invoke() on the delegate does not know what parameters it needs to use. This information is stored in the DataController object.

I’m probably just thinking in a completely wrong way here - keep in mind, I’m not that experienced in programming. I thought maybe the solution would be to store the parameters in a different place and read them out when invoking, but that just complicates things even more.

1 Like

You can pass parameters into c# events and UnityEvents with generics:

public class Example {
   event Action<float, int string> cSharpEvent;
   UnityEvent<float, int string> unityEvent;

   void AddListeners() {
      cSharpEvent += EventListener;
      unityEvent.AddListener(EventListener);
   }

   void InvokeEvents() {
      cSharpEvent.Invoke(12.25f, 100, "Hello World");
      unityEvent.Invoke(12.25f, 100, "Hello World");
   }

   void EventListener(float myFloat, int myInt, string myString) { }
}

I believe you’re hitting this dilemma:

  1. Your delegates all need unique parameter lists – that is, you need to keep track of many types of delegates.
  2. You need to store your delegates in a homogenized collection, so that you can call them in a standard fashion.

This is a common problem, and is certainly not limited to delegates. The same problem crops up any time you have uniquely typed objects which you want to interact with in a common way.

One possible solution to this problem is the Command pattern. Essentially, you create a common abstraction for all your objects, and then store the logic for how to interact specifically with an object inside the command. This might include state, parameters, or any other runtime information. I encourage you to read more about that pattern on your own, but I’ll try to keep my answer focused to your issue at hand.

If you apply this pattern to delegates, you can take advantage of anonymous delegates, which allow you to wrap delegates of various types, and “enclose” the needed state / arguments straight into the wrapping delegate. For example

Action<string> stringAction = MyStringMethod;
Action<int> intAction = MyIntMethod;

We can call these different methods like so:

stringAction("Hello World");
intAction(1234);

Alternatively, we can abstract one level and create a common wrapping type: Action.
The information they need to execute is stored directly in the wrapping delegate; it’s a form of the Command pattern.

This way of storing the information inside a delegate is called a “closure” and I suggest you familiarize yourself with common pitfalls of that before diving in.

//using original syntax that worked all the way back with .net 1.0
Action takeStringAction = delegate { stringAction("Hello World") };
Action takeIntAction = delegate { intAction(1234) };

//since .net 3.5, we can use the lambda syntax
Action takeStringAction = () => { stringAction("Hello World") };
Action takeIntAction = () => { intAction(1234) };

Note that these two variables are now the same type: a void delegate with no parameters; today this is known as an instance of the Action delegate.

Since they are the same type, they can be stored in a homogenized collection, and operated on using a single abstraction

List<Action> actions =
  new List<Action>()
  {
    takeStringAction,
    takeIntAction
  };

foreach (var action in actions)
  action();
3 Likes

eisenpony, thank you! that’s exactly what I was looking for!

“Wrapping” an action call into another action never occured to me… time to read up more on that closure stuff. However, I briefly tested it in my code already and so far it works like a charm!
Now it seems really weird to me that I never stumbled upon that info within my hours over hours of googling this issue…

Also, I looked into unity timelines aaand… I’m kinda disappointed. It seemed like exactly what I need at first glance, but then it turns out the only way to interact with scripts from a timeline is by “signals”, which seem to be just super convoluted unity event calls (which, what a surprise, also don’t really take parameters). I really wonder… is every Gamedev that uses unity exclusively building cutscenes at runtime? Or uses only animations for everything?

However that may be, thanks again for the help!

Both animations and the Timeline signals could be used for this, you just pass the index number of the method you want to call, assuming you use a list of Actions as shown above.

Animation events can call methods only on the object the animation is attached to, and can pass up to 4 parameters (one each of int, float, string, object) if you use an AnimationEvent as the argument, otherwise just use one int, float, or string.

Timeline signals can call methods on any object, and they can use parameters, though are limited to just one.

Either setup will work to manage the timing of your method calls in a graphical way, so that could be helpful for setting things up. Just attach them to an empty GameObject, because all they’re doing is giving you a timeline to populate with method calls, not necessarily doing any of the other things they can do like change properties etc. Bottom line is that it’s probably easier to shuffle time markers around than tweak timing numbers in code!