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 Unity UI is not updated in Coroutine when the function to modify var is called from another script

Discussion in 'Scripting' started by unity_250097180, Feb 9, 2021.

  1. unity_250097180

    unity_250097180

    Joined:
    Aug 19, 2019
    Posts:
    7
    I was trying to build a clock in the game that the player can control its running speed by clicking a toggle or performing a certain activity.

    Currently, I have a script with an IEnumerator "timer" that will be run by calling StartCoroutine(timer) when the "Play" toggle is clicked with a variable "inc" which control how much second pass for each loop the while(true) loop inside "timer". I also have another toggle "Accelerate" that can increase "inc" in run time. The UI of the clock will be updated correctly when "inc" is changed through the toggle. Here's the initialization of the toggles:

    Code (CSharp):
    1. public class TimeUpdater : MonoBehaviour
    2. {
    3. public Text time;
    4. public Toggle pause;
    5. public Toggle play;
    6. public Toggle ff;
    7. private int inc = 0;
    8.  
    9. void Start() {
    10.         pause.onValueChanged.AddListener(delegate {
    11.             toggleValueChanged(pause);
    12.         });
    13.         play.onValueChanged.AddListener(delegate {
    14.             toggleValueChanged(play);
    15.         });
    16.         ff.onValueChanged.AddListener(delegate {
    17.             toggleValueChanged(ff);
    18.         });
    19.         time.text = "07:00:00 AM";
    20.         pause.isOn = true;
    21.         StartCoroutine(timer());
    22. }
    23.  
    24. public void toggleValueChanged(Toggle tog) {
    25.                 // set the inc
    26.         if (play.isOn) {
    27.             inc = 1;
    28.         }
    29.         else if (ff.isOn) {
    30.             inc = 20;
    31.         }
    32. }
    33.  
    34. IEnumerator timer(){
    35.         while (true) {
    36.             //Wait for 4 seconds
    37.             yield return new WaitForSeconds(0.01F);
    38.             second += inc;
    39.             time.text = second;
    40.          }
    41. }
    42. }
    Everything works perfectly so far. However, when I add another function to change "inc" and call this function from another GameObject/script, the "inc" will be modified correctly when the function is called. However, even though the time.text also gets updated correctly, the UI clock component "time" in the game will not be updated. Here's the function:

    Code (CSharp):
    1. public void TimeFly()
    2.     {
    3.         inc = 50;
    4.     }
    Here's where this function is called from another GameObject's script:

    Code (CSharp):
    1. GameObject.Find("Clock").GetComponent<TimeUpdater>().TimeFly();
    Also, the weirdest part is that if I simply replace line 17 in the first code block with TimeFly(), the UI will be updated correctly by clicking on the toggle.
     
    Last edited: Feb 9, 2021
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    Something isn't running the way you think it is.

    To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run?
    - what are the values of the variables involved? Are they initialized?

    Knowing this information will help you reason about the behavior you are seeing.
     
  3. unity_250097180

    unity_250097180

    Joined:
    Aug 19, 2019
    Posts:
    7
    Hi Kurt,

    Thanks for your quick response. I did hide the part where I Debug.Log() the "inc", "second", and "time.text" in real-time. They are updated correctly, but the changes of "time.text" are not reflected on the UI component.

    Here's the way it runs:

    inc = 0, second = 0, time.text = 0 (initialized)
    inc = 20, second = 20, 40, 60, ...; time.text = 20, 40, 60,... (After toggle ff) - the UI is updated correctly based on time.text
    inc = 50, second = 50, 100, 150, ...; time.text = 50, 100, 150, ... (after I call TimeFly() from another script) - the UI is not updated
     
  4. Schneider21

    Schneider21

    Joined:
    Feb 6, 2014
    Posts:
    3,510
    I want to preface this by saying I'm not crystal clear on how threading works in Unity. But it seems to me that the coroutine is grabbing the variable's value instead of its reference and working on that. So when you update the value outside the coroutine, it's not seeing the change.

    Would it be possible to try modifying your code to stop and restart the coroutine when the value of inc is changed? Just to see if that's what's happening?
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    Well, that's easily fixed! There is NO THREADING in Unity from the scripting level.

    If you introduce it yourself with the
    System.Threading
    class, then yeah, maybe. But that way lies madness and dragons. Horrible dragons.

    Coroutines are NOT THREADS.

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    It's all called on one big long happy linear thread, one thing after another, no surprises.

    Some love it, some hate it, but there it is. I think it's gloriously simple.
     
    Schneider21 likes this.
  6. Schneider21

    Schneider21

    Joined:
    Feb 6, 2014
    Posts:
    3,510
    I should have been clearer with my statement. I said threading when I meant "asynchronous tasks," which I believe coroutines falls into.

    In any case, I feel like I've seen this kind of thing before, but I thought it was when passing values into the StartCoroutine method that it happened. I haven't seen it happen when accessing a script's own private member, but I also avoid coroutines whenever possible.
     
  7. unity_250097180

    unity_250097180

    Joined:
    Aug 19, 2019
    Posts:
    7
    Hi Schneider,

    SO the strange thing here is that, when I use "Debug.Log()" to print out the variable, it is updated correctly. It is just the GameComponent (UI) in Unity is not updated based on the variable.

    Also, if I understand the graph Kurt shared correctly, does that mean a potential issue is the GUI rendering is not correctly triggered?
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    I suspect you are not updating the UI.Text you think you are.

    In Start(), make something that sets the text to "HELLO!" and then executes Debug.Break() to pause your game.

    Does the right UI.Text actually change?
     
  9. unity_250097180

    unity_250097180

    Joined:
    Aug 19, 2019
    Posts:
    7
    Yes. I just tried it and it does the change. The other function "toggleValueChanged()" did similar stuff as the "TimeFly()" function, so when I click the "ff" toggle, the variables "inc", "second", and "time.text" are all updated correctly with the UI.Text.

    Also, in case you missed another strange part of the issue, the UI.Text can be updated correctly with the variables "second" and "time.text" if I bind the function "TimeFly()" with the toggle "ff" by replacing the "toggleValueChanged()" function. However, it is not the case if the "TimeFly()" function is called during the game by the player by triggering certain activity, which is called from another script binding with the player. In the latter case, only variables ("seconds", "inc", "time.text") are updated correctly but not the UI.Text.
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    Is there something else (some other binding?) instantaneously driving it back? Try make a brand-new text object and only update that in one place.
     
  11. unity_250097180

    unity_250097180

    Joined:
    Aug 19, 2019
    Posts:
    7
    I just tried that. The same thing happens again. The new Text GameComponent called testUI is only changed in the "Start()" function (testUI.text = "0") and the IEnumerator "timer()" (testUI.text = time.text). It is still the same case that I can update the variable "testUI.text" and its UI correctly by clicking on the toggle (in the current script) which will trigger the "TimeFly()" function. However, if I call the "TimeFly()" function from another script during the game, only the variable "testUI.text" (printed out by Debug.Log()) is updated correctly but not the UI showed up on the screen.

    upload_2021-2-9_16-56-33.png