我写了一个简单的函数,可以在有限的时间内逐步修改任意变量的值到指定的目标:
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();
}
}
例如,如果我想在5秒内将一个变量的值从0提高到10,我可以像这样调用上面的函数:
float v = 0;
var tokenSource = new CancellationTokenSource();
SetValueInTime(5, () => 10, () => v, (x) => { v = x; }, tokenSource.Token);
它通常工作得很好,直到我用它来设置一些渲染器组件的材质属性块属性。奇怪的事情发生了。下面的代码将在3秒内改变列表中GameObjects渲染器中颜色属性的强度,从0到10:
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);
}
但奇怪的是,它没有像我预期的那样工作。即使GameObject列表只包含1个GameObject, for循环只运行一次,同样的行为也会出现。第一次循环一切正常,但第二次,它卡在SetValue()。通过卡住,我指的是我在一些调试后学到的东西,它既不中断,也不继续,而是在第二个循环的SetValue()之后停止做任何事情。
更奇怪的是,如果我手动将目标GameObject的索引设置为0并将其移出for循环,它现在就可以工作了。什么都没变,只是现在不再是for循环了。我可能错过了一些东西,但我相信它是在for循环中调用一次还是没有for循环,应该没有任何区别。
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);
感谢大家的帮助!
我很确定这与Unity只喜欢monobehaviour在主线程上运行有关。当你创建一个任务来操作一个线程时,在第一种情况下,你只是处理一个浮点数,工作得很好。在第二部分中,你将尝试着设置一些组件属性,而这听起来似乎是在Unity系统中失败了。如果将上述代码更改为协程这应该能解决问题。
关于如何在Unity中最好地处理异步逻辑,google写了一篇很好的文章
在本文中,它展示了如何专门设置TaskScheduler,以便新任务留在主线程上。(它更好地解释了这一点)。看起来像…
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();