0
\$\begingroup\$

I am looking to create a "stack" of functions that I can call at will. In my game, multiple events can trigger at the same time. These things should not be shown at the same time however, and only happen 1 after the other.

These functions can come from multiple different GameObjects, but I would like to store the reference in a central place and call them from there.

Example of what I'd want

List<FunctionReference> fr = new List<FunctionReference>();

void AddFunction(FunctionReference f)
{
    fr.Add(f);
}

void CallNextFunction()
{
    if(fr.Count > 0){
        fr[0].CallAndExecuteThisFunction();
        fr.RemoveAt(0);
    }
}

In a different GameObject

function ExampleFunctionThatHasBeenStored()
{
    Debug.Log("Execute all things");
    StoredFunctionList.CallNextFunction();
}

What I tried

  • I read about SendMessage which is heavily cautioned against, and also only invokes functions from the same GameObject.
  • I read about Events, but I am unsure if this is what will solve my problem.

How should I go about approaching this setup? And also to keep in mind parameters involved in the call.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Since you wrote GameObjects, is this for Unity directly or for a general c# answer? \$\endgroup\$ Commented Aug 2, 2023 at 11:52
  • \$\begingroup\$ Events, delegates, actions (and UnityEvent in case of Unity) in combination with a queue will get you quite far. Just report back when they are done, don't let them execute the next step in the queue, let the queue/ list logic handle that. \$\endgroup\$ Commented Aug 2, 2023 at 12:58
  • \$\begingroup\$ Yes, this is for Unity. @Zibelas \$\endgroup\$ Commented Aug 2, 2023 at 14:15

1 Answer 1

2
\$\begingroup\$

What you are looking for is a C# feature called delegates.

A delegate is a variable that holds a reference to a method. You can store a reference to a method in such a variable, change which method is stored in it, and execute the currently stored method at a later time, passing whatever data you have available at that time as arguments.

The C# standard already implements some generics that cover the most common use-cases. For example the delegate Func<TResult> that can be used to hold a call to any function that takes no arguments but returns one. Or the delegate Action that represents a method that does not return anything. Both Action and Func also have variants with multiple additional parameters. For example a Func<string, int, float, int> can hold a function that takes an string, an int and a float as parameters and returns an int. Or a Action<GameObject, Transform> represents a method that has no return value but takes a GameObject and a Transform. These two families of delegate templates cover almost all use-cases. So making your own delegate types is usually optional and only required if you want some extra type safety.

For example, if you want to have a script that is supposed to contain "canned calls" to multiple functions that return integers and can then return the sum of the return values when all those functions get executed could look like this:

class FunctionStack() {
    private List<Func<int>> functions = new List<Func<int>>();

    void AddFunction(Func<int> function)
    {
        functions.Add(f);
    }

    int GetResult()
    {
        int ret = 0;
        foreach(Func<int> function in functions) {
            ret += function();
        }
        return ret;
    }
}

Feeding that class with functions can be done via methods or via lambdas:

FunctionStack instance = new FunctionStack();

// add a lambda expression that returns 4
instance.AddFunction(() => 4); 
// add a lambda function that returns 7 + 3
instance.AddFunction(() => { return 7 + 3; });
// add a call to an existing function. 
// Note the missing () behind the function name.
instance.AddFunction(someObject.SomeMethodThatReturnsAnInteger);
// if you want to call an existing function with arguments, then you need to
// wrap those in a lambda:
instance.AddFunction(() => { return Mathf.FloorToInt(transform.position.x); });

// note that none of the functions above get executed until you do this:
Debug.Log(instance.GetResult());
\$\endgroup\$
1
  • \$\begingroup\$ I managed to solve the setup with your answer. Instead of Func/Actions I had to use UnityAction which look pretty much the same. I then store those in a List using your last example with the lambda and call then when needed by just adding parentheses to the stored call, as per your final sentence. \$\endgroup\$ Commented Aug 3, 2023 at 9:46

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.