Multiple instances using delegates in unity c#

44 views Asked by At

I have created a delegate on a Script named BattleManager to handle the methods when the round is over.

Everything seems to work just fine except when there are multiple instances of the same object class adding to the delagate. Only the last method added to the deleagate works.

The Battle manager has the delegate like this:

public delegate IEnumerator OnRoundEnd();
public OnRoundEnd onRoundEnd;

I have a Effect class that adds to the onRoundEnd delegate. I have 3 objects with the class Effect and when they add to the delegate only the last one works. Other objects adding to the onRoundEnd work fine unless they are the same type. I have not found anything to get arround this issue

On the Effect class I add the method like this

public virtual void Init()
{
    battleManager.onRoundEnd += OnRoundEnd;
}
1

There are 1 answers

2
derHugo On BEST ANSWER

Your

public delegate IEnumerator OnRoundEnd();

is basically nothing else than using Func<IEnumerator>.

You are expecting a return value - one single return value!

To understand a bit what happens here you have to understand what happens inside a += chained delegate (aka MulticastDelegate) - which internally is basically nothing more (ok it is a bit more) than a collection of the methods to call - once it is executed (the following samples are pure pseudo code and oversimplified - just for explaining the picture):

  • First let's go back and take a delegate void

    // no return
    foreach(method in invokationList)
    {
        method();
    }
    

    So far so good - all methods get executed in order, great!

  • Now a delegate int

    int result = 0;
    foreach(var method in invokationList)
    {
        result = method();
    }
    

    You can see now: The result will get overwritten in each iteration => you end up only with the last value!

    Still though all the methods are executed.

  • Finally a delegate IEnumerator

    IEnumerator result = null;
    foreach(var method in invokationList)
    {
        result = method();
    }
    

    So same as above! result is overwritten only with the last value.

The additional "problem" about IEnumeator is now: It is "lazy" and nothing within it is executed until this IEnumeator is actually iterated using MoveNext!

Unity's Coroutine system does this for you internally and basically calls MoveNext on a by StartCoroutine registered/scheduled IEnumerator once a frame (except if using special timing YieldInstructions like e.g. WaitForEndOfFrame etc).

So by the time you finally retrieve your IEnumerator and schedule it using StartCoroutine (or manually call MoveNext) you are only aware of this very last IEnumerator instance and will only get the behavior of that one.


I have not found anything to get around this issue

As mentioned I don't fully see how exactly you consume onRoundEnd but as also mentioned you basically just have a collection of methods to call / IEnumerators to execute

=> it might be easier to just stick to e.g. a Queue - assuming each effect should only be executed once anyway:

private Queue<IEnumerator> onRoundEnd;
public void ScheduleForRoundEnd(IEnumerator routine)
{
    onRoundEnd.Enqueue(routine);
}

and then later on

private IEnumerator OnRoundEnd()
{
    while(onRoundEnd.Count > 0)
    {
        var routine = onRoundEnd.Dequeue();

        yield return routine;
    }
}

and in your Effect.Init instead do

battleManager.ScheduleForRoundEnd(OnRoundEnd);

or use a List<IEnumerator> instead

private List<IEnumerator> onRoundEnd;

public void ScheduleForRoundEnd(IEnumerator routine)
{
    onRoundEnd.Add(routine);
}

private IEnumerator OnRoundEnd()
{
    foreach(var routine in onRoundEnd)
    {
        yield return routine;
    }

    // skip this if you actually wanted to keep the events forever
    onRoundEnd.Clear();
}