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. Dismiss Notice

Question Changing all elements inside an array simultaneously with a single Coroutine

Discussion in 'Scripting' started by hp_jp, Feb 26, 2023.

  1. hp_jp

    hp_jp

    Joined:
    Aug 15, 2022
    Posts:
    2
    Hi.
    I'm trying to fade in/out multiple objects (TMPro text in this example) using a single Coroutine.

    Code excerpts

    First, I create an array, then assign objects to it:
    Code (CSharp):
    1.  
    2.     private TextMeshProUGUI[] messagesToFade; // An array
    3.  
    4.     // TMPro Messages
    5.     [SerializeField] private TextMeshProUGUI message1;
    6.     [SerializeField] private TextMeshProUGUI message2;
    7.     [SerializeField] private TextMeshProUGUI message3;
    8.  
    9.     void Start()
    10.     {
    11.         // Here I set the references of an array of game objects
    12.         messagesToFade = new TextMeshProUGUI[] { message1, message2, message3 };
    13.     }
    Note that this is an example, and in my actual code I access messages as variables from other scripts.

    Then, I call a coroutine to Fade In or Out my messages:
    Code (CSharp):
    1. void Update()
    2. {
    3.     // Calling a Corutine on Key press
    4.     if (Input.GetKeyDown(KeyCode.E) || Input.GetMouseButtonDown(0))
    5.       StartCoroutine(FadeInCode());
    6. }
    7.  
    8.     // Fade In Coroutine
    9.     public IEnumerator FadeInCode()
    10.     {
    11.         // Here I use foreach to access all messages and fande them simultaneously
    12.         foreach(TextMeshProUGUI messageToFade in messagesToFade)
    13.         {
    14.             float currentTime = 0f;
    15.             while (currentTime < fadeSpeed)
    16.             {
    17.                 float alpha = Mathf.Lerp(0f, 1f, currentTime / fadeSpeed); // From alpha 0f (0% = transparent) to 1f (100%)
    18.                 messageToFade.color = new Color(messageToFade.color.r, messageToFade.color.g, messageToFade.color.b, alpha);
    19.                 currentTime += Time.deltaTime;
    20.                 yield return null;
    21.             }
    I've been using this scrpt to fade in/out single TMPro objects (without an array) for a while and it works flawlessly.
    Important note: this fade code wasn't invented by me. Instead, I used a ready solution brought by Scrowneck in this tread (many thanks, I am very happy to be part of such a wonderful community).

    So what is the problem?

    The script kind of works, but not as expected.
    • My goal is to make all messages fade simultaneously (every single message inside an array).
    • What I get instead is a sequence of messages fading one after another in a strict order: it is only after one message finished fading, the next one in an array starts to fade.

    My thoughts

    Of course I can create as many coroutines and call them all at once for every message. But I'm looking for an elegant solution. WIth this code what I'm trying to achieve is some kind of a all-purpose scrip manager able to fade as many messages as I want with a single coroutine (or at least, as little code as possible). In my project, I have a lot of text popping on screen, so I never know in advance how many separate game objects should I fade simultaneously. If I create multiple coroutines for every occasion, the script would get bulky and overloaded with unnecessary lines of code. This is what I am trying to avoid.

    My assumption is that a Coroutine works as a single thread and cannot work with all array elements simultaneously. But maybe I'm wrong and there is a workaround? (Update: seems like I was wrong in the end, see the discussion below.)

    I have to say that I am very new to scripting. Mayble I'm looking at this issue from a wrong direction. In any case, I would be grateful for any advice.
     
    Last edited: Feb 26, 2023
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,713
    hp_jp likes this.
  3. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    i might be missing your intent, but why not just switch the while and the for loop? like such:

    Code (CSharp):
    1.  float currentTime = 0f;
    2. while (currentTime < fadeSpeed)
    3. {
    4.         foreach(TextMeshProUGUI messageToFade in messagesToFade)
    5.         {
    6.                 float alpha = Mathf.Lerp(0f, 1f, currentTime / fadeSpeed); // From alpha 0f (0% = transparent) to 1f (100%)
    7.                 messageToFade.color = new Color(messageToFade.color.r, messageToFade.color.g, messageToFade.color.b, alpha);
    8.  
    9.             }
    10.            currentTime += Time.deltaTime;
    11.            yield return null;
    12. }
    this would have all of the colors having the same exact fade amount however, which may or may not be what you want.
     
  4. hp_jp

    hp_jp

    Joined:
    Aug 15, 2022
    Posts:
    2
    Oh my, this actually worked. The soultion turned to be so simple, though I'm still trying to understand why this tiny change makes such a big diffirence. Anyway, thanks for your help, your saved my day!

    That's an interesting approach, but first I have to try it out to see if I could actually make it work with my limited skills. Thanks for the quick reply.

    There is still room for another, theoretical question that slightly disturbs me. Am I correct in my assumption that Coroutines and arrays don't work well together in situations similar to the one mentioned in this thread, when it comes to this kind of script-based animations/sequences (for the lack of a better term to describe simple fading). I've read the post you linked above and I understand that you general advice is to avoid this kind of approach, and use other workarounds instead.
    Still, while I'm trying to cope with some basics of scripting in Unity, I can't help but wonder what exactly I am doing wrong.
    Update: after seeing that a simple swipe of two lines of code inside a Coroutine, as adviced by TzuriTeshuba, actually solve the problem, I still find myself a bit confused. Oh well, maybe I'll just keep learning the basics of programming.
     
    Last edited: Feb 26, 2023
  5. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    In your own post, you're
    yielding null
    inside of the foreach. This means that for every TMP_Text, you'll yield & wait for the next frame.
    In the code that @TzuriTeshuba posted, that yield is outside of the foreach. The code thus runs through ALL TMP_Text objects, then yields only once.
    Since all objects are getting the same alpha-value set to them, you can move the calculation of that alpha outside of the foreach as well:

    Code (CSharp):
    1. float currentTime = 0f;
    2. while (currentTime < fadeSpeed)
    3. {
    4.     float alpha = Mathf.Lerp(0f, 1f, currentTime / fadeSpeed); // Calc Alpha
    5.     foreach(TextMeshProUGUI messageToFade in messagesToFade)
    6.         messageToFade.color = new Color(messageToFade.color.r, messageToFade.color.g, messageToFade.color.b, alpha); // Set to ALL Texts
    7.     currentTime += Time.deltaTime; // Update currentTime for next frame
    8.     yield return null; // Wait for next frame
    9. }
     
    hp_jp likes this.
  6. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    @dagonikki glad it helped! And learning the basics of programming is really the biggest favor you could do for yourself if you plan on continuing development of any kind. I would advise starting with general C# before learning it specifically for unity. Spoiler: there isn't very much different in unity, more of just HOW it is used. Definitely continue your work in unity alongside if you enjoy it or have deadlines to meet for projects though.
     
    hp_jp likes this.