- Home /
Problems with Color.Lerp in a Coroutine
I've set up a Color.Lerp in a coroutine to trigger off a collision between sprites, instead of fading from one color to another it instead flicks from the base color to the end color instantly despite a timer set up inside the coroutine.
Any input would be appreciated!
void OnCollisionEnter2D(Collision2D Col) {
if (Col.gameObject.name == "Invira-A" || Col.gameObject.name == "Invira-A(Clone)") {
StartCoroutine(InfectionA());
Destroy(Col.gameObject);
}
if (Col.gameObject.name == "Invira-B" || Col.gameObject.name == "Invira-B(Clone)") {
StartCoroutine(InfectionB());
Destroy(Col.gameObject);
}
}
public IEnumerator InfectionA() {
Debug.Log("Starting Infestation!");
float ElapsedTime = 0.0f;
float TotalTime = 6.0f;
while (ElapsedTime < TotalTime) {
ElapsedTime += Time.deltaTime;
SR.color = Color.Lerp(Red, Green, (ElapsedTime / TotalTime));
}
Debug.Log("Ending Infestation!");
yield return new WaitForEndOfFrame();
}
Hm I've tried both answers and neither have fixed the problem, I tried some further changes but to no avail. I'm sure the correct answer must be close given it's partial function but it just won't seem to lerp slowly at all.
Putting up the whole script with Johats changes and a few of my own from experimentation, here it is:- using UnityEngine; using System.Collections;
public class RedCellScript : $$anonymous$$onoBehaviour {
public Color Red;
public Color Green;
public Color Yellow;
public bool Infected;
private float ColorSpeed;
private SpriteRenderer SR;
void Start () {
SR = gameObject.GetComponent<SpriteRenderer>();
ColorSpeed = 2f;
}
void Update () {
}
void OnCollisionEnter2D(Collision2D Col) {
if (Col.gameObject.name == "Invira-A" || Col.gameObject.name == "Invira-A(Clone)") {
if (Infected == false) {
StartCoroutine(InfectionA());
Destroy(Col.gameObject);
}
}
if (Col.gameObject.name == "Invira-B" || Col.gameObject.name == "Invira-B(Clone)") {
StartCoroutine(InfectionB());
Destroy(Col.gameObject);
}
}
private IEnumerator InfectionA() {
Debug.Log("Starting Infestation!");
Infected = true;
float ElapsedTime = 0.0f;
float TotalTime = 6.0f;
Color StartingColor = Red;
Color EndingColor = Green;
if (ElapsedTime < TotalTime) {
ElapsedTime += Time.deltaTime;
SR.color = Color.Lerp(StartingColor, EndingColor, (ElapsedTime / TotalTime));
Debug.Log("Changed Color!");
yield return null;
}
Debug.Log("Ending Infestation!");
}
public IEnumerator InfectionB() {
yield return null;
}
}
Answer by Johat · Mar 22, 2015 at 12:32 AM
Hey, this is actually a simple problem to solve =). As maccabe says, a coroutine will only yield execution when you tell it to.
The reason why the you end up going straight to the end colour within the same frame is because there's no call to yield execution within the while loop.
There are a variety of things you can yield -- WaitForSeconds(numberOfSeconds) is a common one (which does as you'd expect) and WaitForEndOfFrame will wait until the end of that frame. The thing is, neither of these are suitable in this case since you want to lerp every single frame.
Instead you can simply return null: yield return null. In this case nothing is returned and execution will simply pick up again on the next frame. This is a good way to iterate over a series of frames.
I've removed the WaitForEndOfFrame and added in yield return null in your code below. This should sort everything out =).
void OnCollisionEnter2D(Collision2D Col) {
if (Col.gameObject.name == "Invira-A" || Col.gameObject.name == "Invira-A(Clone)") {
StartCoroutine(InfectionA());
Destroy(Col.gameObject);
}
if (Col.gameObject.name == "Invira-B" || Col.gameObject.name == "Invira-B(Clone)") {
StartCoroutine(InfectionB());
Destroy(Col.gameObject);
}
}
public IEnumerator InfectionA() {
Debug.Log("Starting Infestation!");
float ElapsedTime = 0.0f;
float TotalTime = 6.0f;
while (ElapsedTime < TotalTime) {
ElapsedTime += Time.deltaTime;
SR.color = Color.Lerp(Red, Green, (ElapsedTime / TotalTime));
yield return null;
}
Debug.Log("Ending Infestation!");
}
It reaches the end of the coroutine but no color changes seem to occur at all now, not even a single frame switch to the ending color. The logic you laid out sounds like it should work so there must be something off with the color.lerp function... Perhaps something is off with the time?
Hi =).
Color.Lerp should work fine. You can confirm this (and confirm the coroutine is yielding) by using Debug.Log to print out the colour Color.Lerp returns each frame. You should find it changes.
$$anonymous$$y guess would be that something else is going on. What is SR.color and where is it defined? Does anything else change it? Are multiple objects calling and running your coroutines at the same time?
Could you post your full script so we can see if anything else is going on? =)
As a quick litmus test though, to confirm the above works properly try this:
Create an empty scene and put a cube in it.
Create a script called ColorLerp and attach it to the cube.
Press play and then space, you should see the colour correctly change. (Let me know if you don't!)
The code to put in ColorLerp:
using UnityEngine;
using System.Collections;
public class ColorLerp : $$anonymous$$onoBehaviour
{
public Color StartColor = Color.green;
public Color EndColor = Color.red;
public float TransitionTime = 5f;
private $$anonymous$$aterial _my$$anonymous$$aterial;
// Just to make sure we don't try to lerp if we're already doing so
private bool _transitioning = false;
private void Awake()
{
_my$$anonymous$$aterial = GetComponent<Renderer>().material;
}
private void Update()
{
if(Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.Space) && !_transitioning)
{
StartCoroutine(DoLerp());
}
}
private IEnumerator DoLerp()
{
_transitioning = true;
float timeElapsed = 0f;
float totalTime = TransitionTime;
Color startColor = StartColor;
Color endColor = EndColor;
while(timeElapsed < totalTime)
{
timeElapsed += Time.deltaTime;
_my$$anonymous$$aterial.color = Color.Lerp(startColor, endColor, timeElapsed/totalTime);
yield return null;
}
_transitioning = false;
}
}
I haven't had the chance to quickly test that in Unity, so a couple of typos may have slipped through, just so you know!
The above test should be able to confirm that Color.Lerp with coroutines works okay. So you know the problem must be something else.
As I say, put up the rest of your code so we can take a look =).
One thought, just re-looking. Coroutines are run on $$anonymous$$onoBehaviours, so will stop (be destroyed) if that $$anonymous$$onoBehaviour is destroyed. Do you see "Ending Infestation!" show up in the console? If not, I think this might be the problem.
In OnCollision2D, you destroy a GameObject. Is that the same GameObject that the coroutine is running on? (i.e. this script?)
If so, then that's the problem. With your original version, the colour lerped to the end before the call to destroy, resulting in something that instantly jumped to the end colour. In the new version, the coroutine only gets to run one frame before it is destroyed along with the $$anonymous$$onoBehaviour, resulting in code that would work but doesn't get a chance and so the starting colour stays.
Try making sure either the destruction happens after the coroutine is finished, or run the coroutine on an object that doesn't get destroyed (whichever object is changing colour would make the most sense). Let me know if you need help setting that up (I don't know what the rest of your code looks like).
I think that might be your problem anywho.
The red cell, that has this script, is the one that changes color and the virus that bumps it is destroyed and kicks off the infection process. I've put up the script with your advice added in, there is still no color change although all the debug.logs get printed just fine so the whole coroutine is gone through at least.
Hmm. That's odd. =S
Couldn't see anything else weird in your script.
Can you try replacing: Color.Lerp(Red, Green, (ElapsedTime/TotalTime))
with: Color.Lerp(Red, Green, 1f) just to confirm the SpriteRender goes green okay (should be instant again).
Let me know what happens.
Answer by maccabbe · Mar 21, 2015 at 02:48 PM
Coroutines only wait when you tell them. Since there are no yields functions in the while loop
while (ElapsedTime < TotalTime) {
ElapsedTime += Time.deltaTime;
SR.color = Color.Lerp(Red, Green, (ElapsedTime / TotalTime));
}
will run through all the values of elapsed time in a single frame because the coroutine is not told to yield between each frame. Change it to
while (ElapsedTime < TotalTime) {
ElapsedTime += Time.deltaTime;
SR.color = Color.Lerp(Red, Green, (ElapsedTime / TotalTime));
yield return new WaitForEndOfFrame();
}
Didn't seem to work, I tried a few extra things similar and none of them changed the outcome.
Don't use WaitForEndOfFrame. This will wait until you reach the end of the current frame. Once you're there and you call it again it will be returned immediately as you are already at the end of the current frame. If you want to wait a single frame, use "yield return null;".
Thanks for the explanation Bunny83. I wasn't familiar with WaitForEndOfFrame() and couldn't figure out how it was different from yield return null (which I usually use) so I left it in there.
Answer by GamesDeveloper12 · Mar 21, 2015 at 02:49 PM
rather than use a while loop change it to
yield return new WaitForSeconds(TotalTime);
I have used this lots of times to achieve the same effect. Hope this helps
That makes not much sense as he has code in that while loop that should be executed each frame.
Your answer
Follow this Question
Related Questions
C# simple delay execution without coroutine? 2 Answers
Networked Coroutine DOESNT behave on client 1 Answer
How to make an image transparent with color.lerp after delay, in a coroutine 3 Answers
Editor script coroutine/timer/delay...? 3 Answers
If statement not being fullfilled until GameObject inspected in inspector. 0 Answers