Calling a function multiple times (at the same time)

Hello everyone!
I am trying to highlight some of the UI in my game, but now I need to highlight multiple GameObjects at once. Is there a way to do that with the same function? I am using HighlightUI_Open() for starting the highlight:

UI_Elements_PointerDown[15] = true; StartCoroutine(HighlightUI_Open(15, 0.01f));
UI_Elements_PointerDown[16] = true; StartCoroutine(HighlightUI_Open(16, 0.01f));

But this won’t work. only the first function will be called.
Is this because the same function cannot be called multiple times at the same time?

I also tried this, but it fails, too:

UI_Elements_PointerDown[15] = true;
UI_Elements_PointerDown[16] = true;
for (int i = 15; i < 17; i++) StartCoroutine(HighlightUI_Open(i, 0.01f));

Many thanks in advance for any ideas,
Greetings,
Shu

What occurs in HighlightUI_Open???

Here it is, HighlightUI_Open():

IEnumerator HighlightUI_Open ( int UI_ElementNumber, float AnimationWaitTime ) {
    if ( !AlreadyHighlightingUI ) {AlreadyHighlightingUI = true;
        UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = ColorNull;
        UI_Highlight[UI_ElementNumber].SetActive(true);
        for (int i = 0; i < UI_HighlightColors.Length; i++) {
            UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = UI_HighlightColors[i];
            yield return new WaitForSeconds(AnimationWaitTime);}
        for (int i2 = 0; i2 < 3; i2++) {
            UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = UI_HighlightColors[UI_HighlightColors.Length-2-i2];
            yield return new WaitForSeconds(AnimationWaitTime);}
        AlreadyHighlightingUI = false;
    }
    if ( !UI_Elements_PointerDown[UI_ElementNumber] ) {
        StartCoroutine(HighlightUI_Close(UI_ElementNumber, AnimationWaitTime));
    }
}

In your routine you check ‘AlreadyHighlightingUI’ and if it’s true you don’t do anything.

Here’s the thing, the code in that routine operates immediately, all the way up to the first ‘yield’ statement.

So, the first StartCoroutine for it sets ‘AlreadyHighlightingUI’ true, starts the highlighting anim, and yields its first waitForSeconds.

This is when the NEXT call to StartCoroutine occurs, it enters the routine, ‘AlreadyHighlightingUI’ is now true, so it skips over the animation code, and exits the coroutine (it does first check if PointerDown stuff as well).

Your routine just isn’t set up to support multiple elements at the same time.

2 Likes

Ah is see! Thank you for pointing this out!
I though the functions would run at exactly the same time. So I will have to change that.
I will update the script here when I come up with a somewhat useful solution.

Personally I think you might be over using arrays to represent state in this case.

Objects can be a lot easier in representing states.

I don’t have all your code in front of me… but with what information I have in front of me, this is how I’d probably set it up:

public class SomeScript : MonoBehaviour
{

   public UIElementState[] UI_Highlight;

   public void Foo()
   {
     UI_Highlight[15].pointerDown = true;
     StartCoroutine(UI_Highlight[15], 0.01f);
 
     UI_Highlight[16].pointerDown = true;
     StartCoroutine(UI_Highlight[16], 0.01f);
   }


   IEnumerator HighlightUI_Open(UIElementState state, float animWaitTime)
   {
     if (!state.highlightAnimActive)
     {
       state.highlightAnimActive = true;
   
       var wait = new WaitForSeconds(AnimationWaitTime); //cache for reuse
       var img = state.target.GetComponent<Image>(); //cache for reuse
       img.color = ColorNull;
   
       for (int i = 0; i < UI_HighlightColors.Length; i++)
       {
         img.color = UI_HighlightColors[i];
         yield return wait;
       }
       for (int i = 0; i < 3; i++)
       {
         img.color = UI_HighlightColors[UI_HighlightColors.Length - 2- i];
         yield return wait;
       }
   
       state.highlightAnimActive = false;
     }
     if(!state.pointerDown)
     {
       StartCoroutine(HighlightUI_Close(state, animWaitTime));
     }
   }

   IEnumerator HighlightUI_Close(UIElementState state, float animWaitTime)
   {
     //unknown implementation
   }

   [System.Serializable()]
   public class UIElementState
   {

     public GameObject target;
 
     [System.NonSerialized()]
     public bool pointerDown;
     [System.NonSerialized()]
     public bool highlightAnimActive;

   }

}

Honestly, if you have 2 or more arrays inside a class whose indices are paired in some sort of 1 to 1 relation. It is likely that a single array of some class to represent state could serve more appropriately. (a struct can also be used in instance of fast recycle time for GC reasons… but I’m not going to get into that complex conversation right now)

Note how in this ‘pointerDown’ or ‘animActive’ isn’t related to the target ui element based on their index in various arrays of each.

Instead they’re matched together directly on the state object. Anywhere that state object goes, its state information follows along. If you want to add a new state property, you just add it to this object… instead of creating yet another oddly named array that you then have to constantly sync with the dominant array, and you have to ensure any code that uses the state information has access to all those arrays.

For example, what if you decided you now wanted each element to have its own colours to animate through (the UI_HighlightColors array). Now you’d need an array of arrays! UGH!

Rather instead you make an array on the state object for the highlight colors, and now each element has its own colour array to work with.

2 Likes

I will take a look at your post now! Thanks for the detailed information!
This is what I tried and it works to some extend. I guess I have to take another look at it or completely rewrite it:

IEnumerator HighlightUI_Open ( int UI_ElementNumber, float AnimationWaitTime, int AdditionalHighlights ) {
    if ( !AlreadyHighlightingUI ) {AlreadyHighlightingUI = true;
        UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = ColorNull;
        UI_Highlight[UI_ElementNumber].SetActive(true);
        for (int i = 0; i < UI_HighlightColors.Length; i++) {
            UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = UI_HighlightColors[i];
            yield return new WaitForSeconds(AnimationWaitTime);}
        for (int i2 = 0; i2 < 3; i2++) {
            UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = UI_HighlightColors[UI_HighlightColors.Length-2-i2];
            yield return new WaitForSeconds(AnimationWaitTime);}
        AlreadyHighlightingUI = false;
    }
    if ( AdditionalHighlights == 1 ) {
        if ( !AlreadyHighlightingUI_1 ) {AlreadyHighlightingUI_1 = true;
            UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = ColorNull;
            UI_Highlight[UI_ElementNumber].SetActive(true);
            for (int i = 0; i < UI_HighlightColors.Length; i++) {
                UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = UI_HighlightColors[i];
                yield return new WaitForSeconds(AnimationWaitTime);}
            for (int i2 = 0; i2 < 3; i2++) {
                UI_Highlight[UI_ElementNumber].GetComponent<Image>().color = UI_HighlightColors[UI_HighlightColors.Length-2-i2];
                yield return new WaitForSeconds(AnimationWaitTime);}
            AlreadyHighlightingUI = false;
        }
    }
    if ( !UI_Elements_PointerDown[UI_ElementNumber] ) {
        StartCoroutine(HighlightUI_Close(UI_ElementNumber, AnimationWaitTime, AdditionalHighlights));
    }
}

Alright, I guess it’s time to check out some of the stuff you suggested, because I haven’t worked with most of that.
But I do have a (somewhat OT) question there:

Isn’t the value cached automatically when passing it to another function? Or is it more efficient to cache the whole new WaitForSeconds()?

WaitForSeconds is an object independent of the float that you passed into the routine.

Every time you say ‘new WaitForSeconds(AnimationWaitTime)’, you’re creating a new object. This actually causes build up of garbage and can incur garbage collection more frequently.

You can reuse WaitForSeconds objects though, so it’s better to just cache it.

I see. Thank you for that clarification. Also, I ran into a problem with the way I tried it first. I got it running now, but calling the function the second time has some visible delay to it.
I guess it would be a bad habit to just copy and paste the whole function for secondary selections, right?
Because that’s what came to my mind at first, but I though it would be inefficient.

EDIT:
I got rid of most of the delay by passing AdditionalHighlights as 1 only for the secondary selection, which seems like a no-brainer to me now.
If you know how it’s done and you look exactly, there is still some tiny bit of lag between the functions. I just hope it will run smoothly on mobile, even though I may completely rewrite the whole thing later when I figured out all of your code.