- Home /
Coroutines WaitForSeconds – uneven spacing
I'm learning Unity, and I wanted to fire evenly spaced bullets at a predetermined interval. Consider, for instance, a function that accepts these two inputs:
bulletCount: 5 interval: 0.2f
This would mean fire five bullets over the course of one second, spaced out every 0.2 seconds. So you'd get 5 bullets over the course of one second, evenly spaced.
My code looks like this:
IEnumerator _MakeChain(string type, float speed, float angle, int clipSize, float delay) {
for (int shot = 1; shot <= clipSize; shot++) {
ShootBullet(type, speed, angle);
yield return new WaitForSeconds(delay);
}
yield return null;
}
The result of this code is, well, some unevenly spaced bullets:
The first bullets are always bunched up, for instance, but if you look closely you can see the others are also not evenly spaced.
Is this just a limitation of Coroutines and WaitForSeconds, or am I doing something wrong? I reached for Coroutines because of these two facts:
Enemies need to be able to start multiple of the same type of "chain" of bullets at once
The chains of bullets end after a set number of shots
It seems like it would be a headache to try to manage this with InvokeRepeating
, since those go indefinitely, and there's no fine-grained control over canceling these on a per-invoke status (as I understand it, canceling the invoke will cancel all ongoing invocations of the same method).
I read that WaitForSeconds can be off by a few frames, so for really "tight" spacing it might be off. I wouldn't expect that for time frames around 200ms though for a game running at 60fps, given that each frame is an order of magnitude smaller than the delay time that I'm passing. The first few bullets look to be about at least 100ms off. Is WaitForSeconds really that unreliable?
I'm sure this is some basic stuff, but I looked around and didn't see anything in particular that stood out as a possible fix. Is there a common approach to get around this? Thanks!
[2]: /storage/temp/99291-screen-shot-2017-08-05-at-93732-pm.png
Answer by jdean300 · Aug 06, 2017 at 04:45 AM
I'm guessing that this is due to the fact that the game only runs in fixed timesteps. When you say yield return new WaitForSeconds(.2)
you are not guaranteed to have the following code execute after exactly .2 seconds - it could be anywhere from .2 to .216 seconds (if your game is going at 60fps). If your game is moving slower than 60 fps, it could be worse.
If this is indeed the cause the only way I could think about fixing it is to check how late you are and to spawn the projectile slightly forward from it's normal start point to compensate.
Now, if the given picture came from the settings you described (5 shots, 1 every .2 seconds) than this is unlikely. If however it is from a much faster firing rate that this is likely the cause.
Thanks for the reply, @jdean300 . The screenshot wasn't the specific example of 5 shots at 0.2 seconds, but that example looks similar. Here is the specific example:
I added time logging to this loop, and the results are surprising. The offsets between the shots seems to contradict the spacing you'd expect between them.
The gaps suggest that the offset to compensate for between the third, fourth, and fifth bullets would be larger than the second bullet. Larger spacing between them = longer time between them, right? However, I'm logging the offsets, and the second bullet has the longest offset.
In a reduced test case of 3 bullets, the first offset time is .292s, and the second offset is 0.209s. I would expect the second bullet, with this larger offset, would appear further away from the first bullet, as compared to the third bullet from the second bullet. But that's clearly not the case.
The code I'm using for logging is:
for (int shot = 1; shot <= clipSize; shot++) {
Debug.Log("Delta time: " + (Time.time - lastFireTime))
lastFireTime = Time.time;
ShootBullet(type, pos, speed, angle);
yield return new WaitForSeconds(delay);
}
To manually fix the bunching, I added an extra delay of 0.26f to the second bullet only, and that leads to evenly-spaced bullets.
I must be missing something obvious here, right? :)
Since you have the speed of the projectiles, and the time at which the should be fired, you can calculate the position correction exactly - it'd be something like:
float offsetAmount = (timeError * speed);
Vector3 correctedPos = pos + (travelDir * offsetAmount);
Thanks for the follow-up! I can handle the correction code. In my last comment, I meant to point out that the necessary offset to correct the bunching was the duration of 17 frames, which suggests a different cause.
It turns out that first frame (Time.time of 0) is incredibly slow, and that was causing the delay. I guess the pooling/instantiation of objects creates a laggy frame, or something. I posted the answer, but the bunching effectively completely goes away when I wait for a frame other than the first one.
I'll still be adding in some correction code, though.
Thanks again for all of the help, @jdean300 !
Answer by jamesplease · Aug 06, 2017 at 07:01 AM
Looks like I found the culprit: starting the coroutine on the first frame. I assumed the issue was with the delay of subsequent bullets, then realized it could just as easily be a delayed first bullet causing that early bunching.
I realized if the first frame was doing too much, and lagging, then that first bullet would be delayed and cause the bunching with the second bullet.
To test this theory, I updated the script to wait 1 second before firing, and the distance between the bullets is much more sane, even without the position correction suggested by @jdean300 . I still plan to add that, but it's good to know the cause of this anomaly.
I guess one possible takeaway here is not to do too much on that first frame? I'm not too sure, but I'll now be delaying the testing of things to a few frames later.