I wrote a simple function that modifies the value of any desired variable gradually to the specified destination within the limited time:
public async Task SetValueInTime(float duration, Func<float> GetDstValue, Func<float> GetCurrentValue, Action<float> SetValue, CancellationToken cancellationToken)
{
float startTime = Time.time;
while (true)
{
bool aboutToBreak = Time.time + Time.deltaTime - startTime >= duration;
if (!aboutToBreak)
{
float percentage = (Time.time - startTime) / duration;
float currentValue = GetCurrentValue();
Debug.Log("pre set");
SetValue(currentValue + (GetDstValue() - currentValue) * percentage);
Debug.Log("post set");
}
else
{
SetValue(GetDstValue());
break;
}
if (cancellationToken.IsCancellationRequested)
{
break;
}
await Task.Yield();
}
}
For example, if I want to raise the value of a variable from 0 to 10 in 5 seconds, I'd call the function above like this:
float v = 0;
var tokenSource = new CancellationTokenSource();
SetValueInTime(5, () => 10, () => v, (x) => { v = x; }, tokenSource.Token);
It usually works fine, until I used it to set the material property block attributes of some renderer components. Something weird happened. The following code should change the intensity of a color property from the renderers of the GameObjects in a list from 0 to 10 in 3 seconds:
public Color startColor = Color.white;
public GameObject[] glowGO;
for(int i = 0; i< 1; i++)
{
SetValueInTime(3, () => 10,
() =>
{
MaterialPropertyBlock block = new MaterialPropertyBlock();
glowGO[i].GetComponent<Renderer>().GetPropertyBlock(block);
float intensity = Mathf.Log(block.GetVector("_Tint").x / startColor.r, 2);
return intensity;
}, (x) =>
{
MaterialPropertyBlock block = new MaterialPropertyBlock();
var tempColor = startColor * Mathf.Pow(2, x);
block.SetVector("_Tint", new Vector4(tempColor.r, tempColor.g, tempColor.b, 1));
glowGO[i].GetComponent<Renderer>().SetPropertyBlock(block);
}, glowToken.Token);
}
But strangely, it doesn't work as I expected. Even when the GameObject list contains only 1 GameObject, that the for loop runs only once, the same behavior appears. Everything is fine the first loop, but the second time, it stuck at SetValue(). By stuck I am referring to what I've learned after some debugging, that it's neither breaking, nor continuing, but instead just stops doing anything after SetValue() of the second loop.
What's even more strange is that if I manually set the index of the target GameObject to 0 and move it out of the for loop, it just works now. Nothing's changed except it's now no longer in a for loop. I might be missing something but I belive there shouldn't be any difference whether it's called once inside a for loop or without a for loop.
glowToken = new CancellationTokenSource();
SetValueInTime(glowTime, () => 10,
() =>
{
MaterialPropertyBlock block = new MaterialPropertyBlock();
glowGO[0].GetComponent<Renderer>().GetPropertyBlock(block);
float intensity = Mathf.Log(block.GetVector("_Tint").x / startColor.r, 2);
return intensity;
}, (x) =>
{
MaterialPropertyBlock block = new MaterialPropertyBlock();
var tempColor = startColor * Mathf.Pow(2, x);
Debug.Log(tempColor);
block.SetVector("_Tint", new Vector4(tempColor.r, tempColor.g, tempColor.b, 1));
glowGO[0].GetComponent<Renderer>().SetPropertyBlock(block);
}, glowToken.Token);
Appreciate everyone trying to help!