Search Unity

Object desynchronisation and framerate between FixedUpdate/Update

Discussion in 'Scripting' started by vptb, Sep 26, 2014.

  1. vptb

    vptb

    Joined:
    Jan 15, 2014
    Posts:
    28
    I'm making a kind of rogue-like game, where each level has more than 50 traps. And even when they are off-screen they need to be playing their scripts, because the game is extremely time-focused, and I can't disable traps because of that.

    After I had everything implemented, I started to get a serious problem, some of my traps would get desynchronised with others, here's one example:

    upload_2014-9-26_13-14-32.png
    I have these saws that move around those trails in pairs and at a constant speed. When the FPS are good they move as intended, but when I have low and oscillating FPS, the saws in each pair start to move differently, sometimes they get closer to each other, and other times they get farther away. I was doing my calculations on the Update() function with a Lerp and using Time.deltatime. I mean isn't Time.deltatime supposed to take the frame rate in account? Why are objects getting desynchronised?

    I made a question on UnityAnswers with this, but I couldn't get a solution.

    Then I tried moving my calculations to FixedUpdate(). And surprisingly it started working, wherever the FPS were, the objects would always move as intended. But now I have a bigger problem, since sometimes I can have more than 100 objects running their FixedUpdates() several times per second, my frame rate is getting really low. I am thinking about increasing the Fixed Timestep but I wanted to try a better solution. Can you guys help me out?
     
  2. Tanel

    Tanel

    Joined:
    Aug 31, 2011
    Posts:
    508
  3. vptb

    vptb

    Joined:
    Jan 15, 2014
    Posts:
    28
    Thank you for the advice but I don't think there's anything wrong with my implementation of Lerp:

    Code (CSharp):
    1. transform.position = Vector3.Lerp(_curStartPos, _destinyPos, _sawTimer);
    2. if (_sawTimer < 1) _sawTimer += (Time.deltaTime * _velocity) / _distance;
    3. else
    4. {
    5. ....
    6. }
     
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    The way you're calculating the t (fraction) parameter for Lerp is a little odd. You're taking the deltaTime, figuring out how far the thing would move in that much time, and then converting that to a fraction of the total distance.

    That ought to basically work, but it strikes me as a roundabout way to do it, and if there is any source of error (e.g. you miss an Update call now and then), those errors are going to accumulate. A better way to do it would be to keep set the start time and duration of the motion when the motion begins, and then compute t from that, e.g.:
    Code (CSharp):
    1.   transform.position = Vector3.Lerp(_curStartPos, _destinyPos, (Time.time - _startTime) / _moveDuration);
    This way, the position depends only on the global clock, and no matter what errors are creeping into time.deltaTime or even whether some objects miss a frame or two, they're all going to stay exactly together.

    (Of course you'll still need to be sure that _curStartPos, _startTime, and _moveDuration are all set properly for all the traps to keep them together.)
     
  5. vptb

    vptb

    Joined:
    Jan 15, 2014
    Posts:
    28
    Thanks Joe, that is in fact a good idea but the problem persists, since the destiny position of the saws is continuously changing, I have to update those variables that you mentioned. And if I miss an Update().... same thing happens. That's why I went with FixedUpdate(), that way I know I won't miss one.
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I don't FixedUpdate vs. Update is the problem. And no, the destination position of the saws should not be continuously changing. So I think maybe you've misunderstood what I was suggesting; you would only change the destination when the saw needs to change direction.

    But now I see that, if you're not careful, this still won't be good enough. A saw might reach the corner, and in fact would go a little beyond it in one frame. In that case, it needs to go a little beyond the start on its way toward the next frame.

    So! What you need to do is take it further, and write a function that calculates the absolute position of the saw for any point in time.

    This isn't as hard as it sounds; you would use a look-up table, and then simply index into that with the time, modulo the total time for one cycle. (The % operator in C# works fine with floats.) I have a LUT (lookup table) implementation lying around here somewhere, though I'm having a bit of trouble finding it at the moment. But it's not hard to write — let me know if you need help with that.

    So you just have this LUT defining where a single saw should be at any point in a single cycle; then you index into this for each saw, using the current time plus whatever time offset you want (so you don't need a separate LUT for each saw).

    Done this way, it is absolutely impossible for the saws to ever get out of sync; you can plug in any time, even 12.73 years from now, and find out exactly where all the saws will be.

    Cheers,
    - Joe
     
  7. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    What do you actually mean by missing one update?
    Is that even possible? I've never heard of it before.

    Fixed Update may not be the best solution for it, but is it working or not?

    As for the problem, i could imagine that this has something to do with the Lerp function because the objects will arrive at the target position even if they had to move a bit further. Thus, the distance between the objects reduces slowly, but constantly.
     
  8. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Argh @JoeStrout, looks like we had the same idea. xD You were one minute faster though. And sorry for the double post.
     
  9. vptb

    vptb

    Joined:
    Jan 15, 2014
    Posts:
    28
    @Suddoha what I meant by missing an Update() is what you and Joe said, the saws would "miss" an Update and go further, and because they have different paths sometimes (one is on a smaller edge and the other is on a longer edge) it will start to create errors.

    @JoeStrout I totally forgot about that, do you know the name of those kind of functions in mathematics? That would definitely solve the saw problem, but as I said on my first post, the Saws are just an example, on some levels I have saws that need to be synchronised with arrow dispensers and other kind of traps. FixedUpdate() seems to work good in this situation because I can synchronise all traps. The only problem is performance-wise. I'm already implementing a system to make each level modular and try to not have everything active at the same time.

    Again, thanks for the suggestion, I will remember about the LUP solution in the future.
     
  10. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Using a lookup table is a great idea, but probably not neccessarily needed.

    What i had in mind in order to solve this was something like:

    1. find out which distance you would move (totalDist)
    2. find the distance to the next target position (nextPosDist)
    3. totalDist - nextPosDist = remainingDist
    4. move the saw to the corner, afterwards into the other direction by remaining dist

    As for the other issue, you may do some kind of approximation. It should work quite well, assuming it's set up as i imagine. Maybe you want to provide some more information.
     
  11. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    @Suddoha, that would certainly work, but it'll get thorny to maintain... I suspect that, once you have a decent LUT table in hand, you'd find it easier to use and harder to screw up. After all, what you're describing is the same thing that a LUT does, just done manually for each case rather than in a more general way.

    @vptb, you can use the LUT approach with arrow dispensers and such too, though it may be a little trickier. I would treat arrows the same way as saws, with cyclic LUT functions that define the position of each arrow at each point in time... with negative values (or zero) meaning "not dispensed yet". Then each arrow can position itself and show/hide according to that LUT. Again, nothing can get out of sync if the position (and visibility) of everything is determined entirely by the time value.