Search Unity

Adjusting shader float via script - only works one way

Discussion in 'Scripting' started by xamur, Jun 20, 2017.

  1. xamur

    xamur

    Joined:
    Mar 27, 2014
    Posts:
    109
    Hello folks,

    I am currently creating a movement style, similar to the power "Blink" in Dishonored. As you can see in this video (just for showcasing, not my video, but he did what I want to achieve), when the player wants to teleport himself (holding a button), the symbol on his hand starts to glow. That works in my prototype too. But when the player releases the button and teleport himself, the glowing symbol doesn't decrease its intensity. The glow is always set to maximum.

    I am using this code to start the coroutines:
    Code (CSharp):
    1.  
    2. if (Input.GetButton ("Fire2"))
    3.         {
    4.             StartCoroutine (IncreaseIntensityOfStigma(0.0f, 3.0f, 0.0f));
    5.  
    6.             // other stuff
    7.         }
    8.  
    9. if (Input.GetButtonUp ("Fire2"))
    10.         {
    11.             StartCoroutine (DecreaseIntensityOfStigma(0.0f, 3.0f, 3.0f));
    12.  
    13.             // other stuff
    14.         }
    15.  
    Well, as I said. The first part works -> increasing the intensity. But not decreasing.
    Here is the code of both coroutines:

    Code (CSharp):
    1. IEnumerator IncreaseIntensityOfStigma(float startIntensity, float endIntensity, float currentIntensity)
    2.     {
    3.         while (currentIntensity < endIntensity)
    4.         {
    5.             currentIntensity += 1.5f * Time.deltaTime;
    6.  
    7.             //if (currentIntensity == endIntensity)
    8.             //{
    9.             //    currentIntensity = 3.0f;
    10.             //}
    11.  
    12.             stigma.GetComponent<Renderer>().material.SetFloat("_Glow", currentIntensity);
    13.             yield return null;
    14.         }
    15.     }
    16.  
    17.     IEnumerator DecreaseIntensityOfStigma(float startIntensity, float endIntensity, float currentIntensity)
    18.     {
    19.         while (currentIntensity != startIntensity)
    20.         {
    21.             currentIntensity -= 1.5f * Time.deltaTime;
    22.  
    23.             //if (currentIntensity == startIntensity)
    24.             //{
    25.             //    currentIntensity = 0.0f;
    26.             //}
    27.  
    28.             stigma.GetComponent<Renderer> ().material.SetFloat ("_Glow", currentIntensity);
    29.             yield return null;
    30.      
    31.         }
    Everything works fine (similar as in the video), I can teleport and the two location markers appear correctly. Only the adjustments of the shader value isn't working correctly.

    Thank you for any suggestion. :)
     
  2. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Questions:

    1) Are you sure the decrease coroutine is actually running? The parent gameobject hasn't been made inactive, which kills the coroutine?

    2) Is the first coroutine still active? You could have two coroutines overwriting each other

    3) This seems pretty sketchy:

    Code (csharp):
    1. while (currentIntensity != startIntensity)
     
  3. xamur

    xamur

    Joined:
    Mar 27, 2014
    Posts:
    109
    Hello BlackPete,
    thank you very much for your reply.

    1) Yes, the coroutine is actually running, I tried it with Debug logs. But what do you mean with "The parent gameobject hasn't been made inactive, which kills the coroutine?"? Should I make the parent inactive each time I want to stop a coroutine??

    2) I guess it is. I tried to stop it using "StopCoroutine...", but that did not change anything. I am pretty new to coroutines, or at least did not use them a lot in the past.
    I thought that "yiel return null" also stops a coroutine.

    3) Well, it seems sketchy because it is sketchy actually. I can't imagine what I should use to make it happen that it decreases until currentIntensity is zero again. Do you have a suggestion which could work?
     
  4. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Did you put a Debug.Log in both coroutines? That should confirm to you if you only have one coroutine running or if both are running.

    No, I was asking if the gameobject was ever made inactive, which might explain why you don't seem to see the coroutine running (because it can't start in the first place). Based on your reply, it seems like we can rule this possibility out, though.

    How were you stopping it? Like this?

    Code (csharp):
    1.  
    2. Coroutine _coroutine = StartCoroutine(DoSomething(....));
    3. ....
    4. StopCoroutine(_coroutine);
    5.  
    In either case, what I _think_ is going on in your case is you may have both coroutines running, and since both are changing the value of currentIntensity, you can never break out of the while loops in both. Putting a Debug.Log in both coroutines whill confirm whether this is happening, though.

    Use
    Code (csharp):
    1. while (currentIntensity > startIntensity)
    (i.e. use "greater than" instead of "not equals to"). With floats, you almost never use "equals to" or "not equals to" because comparing floats is trickier than comparing ints. You can very easily overshoot and end up with a negative number, in which case, you're stuck with an infinite loop.

    "yield return null" (or yield return anything) will continue running on the next frame. If you want to bail out of a coroutine and actually end it, use yield break.
     
  5. xamur

    xamur

    Joined:
    Mar 27, 2014
    Posts:
    109
    Yes you're right, both coroutines are running at the same time.

    The gameobject wasn't ever made inactive.

    Not exactly. I have read this thread (/question) and the answer. He explains pretty well how to stop a coroutine but I am not sure how I should implement it in my script. I want to use this version: "The IEnumerator version is actually cumbersome. The Coroutine version is much better:"
    Code (CSharp):
    1.  Coroutine co;
    2. // start the coroutine the usual way but store the Coroutine object that StartCoroutine returns.
    3. co = StartCoroutine(MyCoroutine());
    4. StopCoroutine(co); // stop the coroutine
    But does that mean that I have to create a variable for each new coroutine, just to stop it (with StopCoroutine)?
    And where should I implement it that the other coroutine stops if another one should start? Thanks!

    Alright, thank you for the tip! You can always learn something new. :)

    Good to know either.
     
  6. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    You don't need to create a new variable for each new coroutine. Here's a barebones example of how you could structure it:

    Code (csharp):
    1.  
    2.    public class Foo : MonoBehaviour
    3.    {
    4.        private Coroutine _coroutine;
    5.        private float _value = 0;
    6.  
    7.        private void Update()
    8.        {
    9.            if (Input.GetButtonDown("Fire2"))
    10.            {
    11.                if (_coroutine != null) StopCoroutine(_coroutine);
    12.  
    13.                _coroutine = StartCoroutine(IncreaseValue(100));
    14.            }
    15.  
    16.            if (Input.GetButtonUp("Fire2"))
    17.            {
    18.                if (_coroutine != null) StopCoroutine(_coroutine);
    19.  
    20.                _coroutine = StartCoroutine(DecreaseValue(0));
    21.            }
    22.        }
    23.  
    24.        private IEnumerator IncreaseValue(float maxValue)
    25.        {
    26.            while (_value < maxValue)
    27.            {
    28.                ++_value;
    29.                yield return null;
    30.            }
    31.        }
    32.  
    33.        private IEnumerator DecreaseValue(float minValue)
    34.        {
    35.            while (_value > minValue)
    36.            {
    37.                --_value;
    38.                yield return null;
    39.            }
    40.        }
    41.    }
    42.  
    BTW: While typing out this sample code, I realized I'd overlooked another issue in your code: Input.GetButton returns true on every frame as long as that button is held down (and thus StartCoroutine is called every frame). You actually want Input.GetButtonDown if you only want it to fire once.
     
    xamur likes this.
  7. xamur

    xamur

    Joined:
    Mar 27, 2014
    Posts:
    109
    Thank you very much for this example, I've got it working now! :)

    I know. But it is supposed to be true every frame because the player has to hold the button to teleport correctly. "GetButtonDown" isn't the best solution in this case. :)
     
    BlackPete likes this.
  8. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Oh, in that case you might not even need coroutines if all you care about is whether "Fire2" is held down or not.

    Something like this:

    Code (csharp):
    1.  
    2.     public class Foo2 : MonoBehaviour
    3.    {
    4.        private float _value = 0;
    5.        private float _minValue = 0;
    6.        private float _maxValue = 100;
    7.  
    8.        private void Update()
    9.        {
    10.            var step = Input.GetButton("Fire2") ? 1.5f : -1.5f;
    11.  
    12.            if (step > 0 || _value > _minValue)
    13.            {
    14.                _value += step * Time.deltaTime;
    15.                _value = Mathf.Clamp(_value, _minValue, _maxValue);
    16.  
    17.                // Do something with _value...
    18.            }
    19.        }
    20.    }
    21.  
    A nice bonus is you get to avoid the overhead of starting a new coroutine every frame.
     
  9. xamur

    xamur

    Joined:
    Mar 27, 2014
    Posts:
    109
    Cool, thank you!