提问人:Kureyş Alp Kılıç 提问时间:9/12/2023 最后编辑:derHugoKureyş Alp Kılıç 更新时间:9/12/2023 访问量:65
Unity 协程组合延迟比预期的要长,有多个 WaitForSeconds
Unity Coroutine combined delay is longer than expected with multiple WaitForSeconds
问:
我正在启动一个协程,该协程迭代循环多次,并在每次迭代之间等待一定时间。但是循环的压缩时间比预期的要长得多。代码如下:
private IEnumerator CO_AddBladesInTime(int count, Blade blade)
{
var _spawnPos = blade.transform.position;
var _counter = 0;
var _firstTime = Time.time;
var _calculatedTime = 0f;
Debug.Log("entered coroutine at " + _firstTime);
while (_counter < count)
{
_counter++;
// AddNewBlade(_spawnPos, blade.Level);
var _waitTime = bladeSpawnFreq / count;
_calculatedTime += _waitTime;
yield return new WaitForSeconds(_waitTime);
}
var _secondTime = Time.time;
Debug.Log("exited coroutine at " + _secondTime);
Debug.Log($"waited {_secondTime - _firstTime}");
Debug.Log($"expected wait time {_calculatedTime}");
}
结果如下:
我错过了什么吗?
答:
0赞
derHugo
9/12/2023
#1
如前所述,目前您的协程将等待最少的帧数。count
WaitForSeconds
基本上有点类似于
for(var time = 0f; time < timeToWait; time += Time.deltaTime)
{
yield return null;
}
因此,即使非常非常小,例如 它仍然会等待一整帧 - 假设帧速率为 秒!timeToWait
0.00001
60 f/s
0.017
也不知道你为什么使用它
var _waitTime = bladeSpawnFreq / count;
我假设已经指每秒生成多少个对象。相反,我宁愿期望你计算bladeSpawnFreq
var _waitTime = 1 / bladeSpawnFreq;
两次生成之间的时间(以秒为单位)。
所以现在这可能太花哨了,可能会有更直接的解决方案,但你可以做的是异步。
// Wrapper for a thread-safe counter
private class CounterWrapper
{
private int counter;
public void Add()
{
Interlocked.Increment(ref counter);
}
public bool Reset(out int value)
{
value = Interlocked.Exchange(ref counter, 0);
return value > 0;
}
}
private async Task ScheduleSpawns(int count, CounterWrapper counter)
{
var millisecondsBetweenSpawns = Mathf.RoundToInt(bladeSpawnFreq * 1000);
for(var i = 0; i < count; i++)
{
counter.Add();
await Task.Delay(millisecondsBetweenSpawns);
}
}
private IEnumerator CO_AddBladesInTime(int count, Blade blade)
{
var _spawnPos = blade.transform.position;
var counter = new CounterWrapper();
var task = Task.Run(async () => await ScheduleSpawns(count, counter));
var _firstTime = Time.time;
var _expectedDuration = 1 / bladeSpawnFreq * count;
while(!task.IsCompleted || counter.Reset(out var toSpawn))
{
// each frame you pawn as many items as scheduled by the task to fulfill the spawn rate
for(var i = 0; i < toSpawn; i++)
{
AddNewBlade(_spawnPos, blade.Level);
}
// this yields for one frame
yield return null;
}
var _secondTime = Time.time;
Debug.Log("exited coroutine at " + _secondTime);
Debug.Log($"duration {_secondTime - _firstTime}");
Debug.Log($"expected duration {_expectedDuration}");
}
所以这里发生的事情是
- 将任务作为异步任务启动(->甚至可能在不同的线程上运行)。
ScheduleSpawns
- 它以毫秒为单位发生,并且比 Unity 帧更精确(如前所述,对于 60 fps,仅一帧就已经是 17 毫秒长)
Task.Delay
- 对计划的生成使用线程安全计数器
- 异步任务更精确地增加了应生成多少项的计数器
- Unity 主线程上的 Corotuine 每帧都会生成所有必需的项目,同时再次重置计数器
请注意,由于经常提到的帧的性质是相当长的;),因此最终可能会非常长。
下一个:评估时出现意外退出条件
评论
Time.deltaTime