Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

The truth about FixedUpdate()

Discussion in 'Scripting' started by gregzo, Mar 1, 2014.

  1. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
  2. AidanofVT

    AidanofVT

    Joined:
    Nov 10, 2019
    Posts:
    104
    @Marrt So, if I'm understanding this correctly, if the fixedupdate interval is T seconds, then following a fixedupdate, the next fixedUpdate won't be in T seconds, but rather will be at the same time as (or right before) the first regular update that is at least T seconds in the future?
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Well not necessarily T seconds in the future since the regular update frequency is unrelated to the physics update frequency.

    Imagine this... say you have your physics fixedupdate interval set to 50 times per second. But your game was running at 1 frame per second.

    Well every frame FixedUpdate would be called 50 times in succession. How long it took would depend on how long each tick of the fixedupdate took... but regardless that single frame would process 50 fixedupdatees because the simulation expects 50 simulated steps per second, but the game is only running at 1 frame per second.

    If the game sped up to 2 frames per second... then each frame would process 25 fixedupdates.

    3 fps = ~16.66 fixedupdates per frame
    5 fps = 10 fixedupdates per frame
    10 fps = 5 fixedupdates per frame

    as you approach 50fps, you're simulating physics at a nearly 1:1 ratio. Each frame processing a single fixedupdate.

    Of course sometimes you might be off by a little and one frame will get 0 fixedupdates, but then the next frame might get 2. This happening because the duration from the last frame was just shy of 1/50th of a second (or 1/frequency of physics) so it summed just short of the next tick for simulation.

    And of course if you run at say 100fps, then fixedupdate is getting called every other frame.

    (this is why accessing the Input class is problematic in FixedUpdate. Input is updated every frame... but FixedUpdate can be called twice in a single frame, or not at all in another frame. This means that say something like the Input.GetKeyDown method which only returns true in the frame the key was initially pressed. Well... if fixedupdate was called twice that frame then you'd get the keydown event twice in a row, behaving sort of like a very very very fast double tap. And if fixedupdate wasn't called that frame at all, you completely miss that input down. Instead you only get the subsequent GetKey, but not GetKeyDown and any code relying on that 'down' aspect won't receive it)

    ...

    Keep in mind this is all because the physics is processed independent of real time. It's just ticked forward and the simulation calculates all of it's movements at that defined simulated rate.

    Basically if it's 50 steps per second, then every time the physics is ticked forward than it simulated 1/50th of time. So if you're moving at 1 m/s, then you move 1/50th meters in that single tick.

    This means that if you tick the engine 5 times, you move 5/50th's of your simulation.

    How long did it take to actually tick the engine 5 times? Doesn't matter. Any moving object will now be in the position that it would be if 5/50th's of a second had passed in the simulation.

    This actually implies that if you were to tick the simulation more frequently than the rate at which it simulates (tick 100 times per second, while simulating a world expecting 50 times per second) you effectively would get a world visualized at twice speed. Like in fast forward. And is basically what happens when you adjust the timescale... set it to 50 times per second simulation, but the timescale to 2 times, then it'll tick the simulation twice as frequently resulting in 100 ticks per second... so everything simulates as if it moved 2 seconds worth of simulation ever second that passes.

    ...

    But yeah... "at least T seconds in the future" is roughly right in that in normal simulating scenarios the framerate isn't hot garbage. In which case... yeah... fixedupdate basically processes every T seconds, give or take the nearest frame. But you can't rely on code dependent on a specific frame (like Input).
     
    Last edited: May 1, 2021
    _geo__, AidanofVT and Bunny83 like this.
  4. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Yes! Sounds correct if T is Unity-time not realtime.

    In one sentence:
    In every loop-cycle (=renderFrame), Unity will check how much bigger the realtime-timer is compared to the physicstime-timer and perform 0 to x physics cycles (increasing the physicstime-timer every cycle) until the difference is small enough that no more physics frames will fit.
    https://docs.unity3d.com/ScriptReference/Time.html

    Therefore physicframes are NOT performed in a realtime interval, they are emulated onto the "erratic" render frame interval.

    Example and Exception:
    That means if you are having a super lag of 10s, the physics system would try to perform 500 cycles (if phTimestep is 0.02). You can imagine that 500 frames might take a long time and that the physics would then cause another huge lag to the renderframes. But the Time setting "Maximum Allowed Timestep" will effectively stop the physics system after that much cycletime. If that happens, the physics time and calculation will be stopped, so the next render frame can proceed.
    Think of it as: if the physics system is way behind, physics time will be decoupled from the real time. The outstanding physicscycles will be calculated for only 0.1 seconds max (=default Maximum Allowed Timestep setting) each renderframe, regardless of how many outstanding cycles Unity manages to perform in this timeframe.
    https://docs.unity.cn/560/Documentation/Manual/TimeFrameManagement.html
    No Physics frames will be dropped or skipped, framerate will slow down to 10/s (=1/0.1s) at best, and physics time can slow down or speed up depending on how many outstanding frames can be fit into the 0.1s window (maybe you have seen physics speeding up after a huge lag in some cases, i have)

    I hope this makes sense, I think that's all there is to it
     
    Last edited: May 3, 2021
    NikoBay and april_4_short like this.
  5. wechat_os_Qy06eaOhICF9NcZoMWMLtv5cI

    wechat_os_Qy06eaOhICF9NcZoMWMLtv5cI

    Joined:
    Feb 1, 2018
    Posts:
    33
    so ,the input handle should be putting to another thread?and handle the buffer in Update()?
     
  6. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I feel like I have to repeat it;

    If you want to have a character that is moving smoothly; FixedUpdate is evil and will never allow it to be smooth.
    The only way for FixedUpdate to give a smooth result, is if it ALWAYS has the same number of physic update per visual frame - which the only way to do is to call it manually.

    If you suddenly have 1 more physic update, the player moves faster for 1 frame.
    If you suddenly have 1 less physic update, the player moves slower for 1 frame.
     
    Marrt likes this.
  7. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    I find that not to be the case with simple interpolation, providing the forces are applied in fixedupdate. One thing that can make interpolation appear as if it's stuttering is when people have bad input code that runs out of sync.
     
    ADNCG likes this.
  8. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    You would need to force that interpolation in LateUpdate to "fix" the stuttering done in FixedUpdate - and that also introduce the possibility of overlapping collision and bouncing back the next frame since you sort of discard the physic.

    It's simple really; you cannot have smooth motion if you let physic drive it from FixedUpdate.
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    I uhh... don't agree.

    You can have smooth motion from FixedUpdate.

    FixedUpdate ticks with the physics simulation step. The physics simulation is simulating at the same rate as FixedUpdate. And it's smooth.

    Also... this is completely outside of the context of the topic at this point, since the topic at this point is about input and fixedupdate and why you can't do things like any of the Input.*****Down or Input.*****Up methods since they rely on the framerate/update sequence and not physics/fixedupdate.
    (that is unless you're dragging the topic back a couple years to... which was your last comments about animation driven games and fixedupdate in... 2019?)
     
    ADNCG likes this.
  10. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    You are right, I have seen countless cameras tied to physic frames in so many games and prototypes it is a shame, since it isn't really much of a sidetrack to fix it. And although lot's of dev's claim that nobody notices it, you can definitely feel the difference on a side-by-side basis.

    No, it isn't "smooth": https://forum.unity.com/threads/the-truth-about-fixedupdate.231637/page-4#post-7101967
    Even if you set the the physics timestep to a third or a fifth of your frametime, you will always have cases where Unity will perform more or less physics frames in a render cycle.


    THE ONLY WAY:
    The only way to have a reliably smooth looking movement or camera-follow:
    - use Unity's interpolation | extrapolation *,**
    or
    - manually interpolate | extrapolate a detached visual transform
    here is an old pic, the character-visuals are smoothly interpolated, the capsule shows the driving rigidbody, the camera follows the visuals.


    *beware that doing anything to the rigidbodies transform without using ApplyForce or similar Unity functions will break the build-in interpolation.
    ** be mindful how and when you fetch rigidbody-positions to update your camera-position
     
  11. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    I've observed this... but with an odd caveat. The erratic render frame time is longer than you'd think it should be, about 3 out of every 4 times, or more.

    So it being right is the erratic part. It's consistently not correctly timed.

    Proof, for me, was putting a counter in for a fixedUpdate set to exactly 1/4 of the frame rate and fixed update. That fixedUpdate should get called 4 times (or less) per frame/update.

    But it gets called 5 times at least 4 times more often than it gets called 4 times, and it never gets called only 3 times.
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    yes, if your camera is tied to it the camera can behave unsmoothly.

    This is related to the timing difference between the camera and the thing you're following.

    This is why most camera scripts utilize interpolation and lateupdate. And the lateupdate example is a camera follow script:
    https://docs.unity3d.com/ScriptReference/MonoBehaviour.LateUpdate.html

    This doesn't mean the physics simulation isn't smooth. The physics always occurs at the same rate no matter what. You're not going to move faster because physics ticked N+1 times this frame. The physics updates 1/Nth of a second every tick. The entire reason that it may update more in a single frame is because the framerate isn't smooth and thusly the physics simulation needs to account for that.
     
    april_4_short likes this.
  13. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    Then the timing of calls to Update is rarely accurate to within 1/120th of a second if it's at a rate of 1/60th of a second.
     
  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    I think the main issue is what we're referring to by "smooth".

    The simulation itself is "smooth" in that the simulation doesn't speed up or change (unless you change directly change those settings of the simulation). It simulates exactly 1/Nth of a second per tick (where N is the number of ticks per second you've configured it to be).

    If your body is set to 3 units/second, then it'll move 3/N units per tick.

    That is smooth in my book. Every tick does the same exact amount of change.

    Y'all seem to be referring to stuttering caused by physics simulation and the update loop not being synchronized. This imo doesn't mean one is smooth and the other isn't... they're just not synchronized. And this is because physics simulation steps rely on update stepping them before update draws to the screen. But fair, you see stuttering, and you're referring to this as "not smooth".

    Personally I consider Update to be the "not smooth" aspect because the delta time between updates is variable. Even if you use something like v-sync or the newer sync technologies... there's still stutters in updating. It's primarily because not the same amount of calculations are being done each frame. As a result the synchronization of the physics update to the visual update desynchronize and needs to be accounted for.

    This lack of synchronization looks like stutter.

    And how you deal with stutter is you interpolate.

    And yes you have to be mindful of this fact. You have be mindful of when you access rigidbodies and apply forces.

    Unity tells you this in their documentation! Because physics is simulated at a constant simulation rate, unlike frame rate. Frame rate might can change wildly from 60 to 120 to 15 fps second to second. But your physics simulation will always be N ticks per second regardless (where N is the number you configured in the physics settings).
     
  15. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489

    So how do we, all of us, get the smoothest presentation of physics bodies moving?

    My approach, which most can't do because they probably have more going on, is to run fixedUpdate at 240Hz, and set flags for input and handle the physics of it in those fixedUpdates, too, and do that until the Update is called, which clears those flags.

    This way I get the smoothest movement i can, and the lowest latency responsiveness to the input as possible, too.

    But this is a dirty, dirty, hacky way to do this. And means I have to optimise a lot of things to make this possible.

    I have tried EVERY other combination of ways to get smooth movement.

    I really do mean this, every other way. And ways that make no sense at all.

    So I'm not looking for suggestions. I've tried it all.

    i'm asking if you've found a way that works for you, and what you do.

    My situation: Box2D, sonic type game, but also like pinball, and incredibly fast, targeting high frame rate phones, so I'll need that faster fixedUpdate for all those folks with 120Hz screens and 240Hz and above touch responsiveness.

    Yes, I'm aware of the studies of people showing that most can't see the difference in framerates, and a lot can't sense the difference between different latencies. But some can.

    I'm targeting them.
     
  16. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    There's no "all of us" solution since the implications of a simulated physics comes with caveats that effect different people in different ways.

    If your problem is camera stuttering... then interpolate your camera in LateUpdate.

    If your problem is erratic framerate... try toying with the frame rate settings.

    If it's something to do with simulation specifically and you don't like the simulation methods. You could toy around with stepping the physics yourself:
    https://docs.unity3d.com/ScriptReference/Physics.Simulate.html

    If it's something else... well it depends on what that something else is.
     
    april_4_short likes this.
  17. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    a million times this. There's a funkiness there, that's part host OS and hardware funkiness and part Unity.

    Not clever enough to proportion blame. So I blame Unity.
     
  18. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    But that is wrong. At least if you are talking "smooth compared to the real world time", but what else could you mean

    Example:
    -framerate of 1s/frame
    -physics framerate 1.2s/frame
    -max allowed timestep 0.1 seconds (lets imagine the physics updates take the processor about 0.03s, so 4 will fit but then we have passed 0.1s)

    frame 1: time = 0.000F; physicstime=0.0F: no phy-update pending
    frame 2: time = 1.001F; physicstime=0.0F: no phy-update pending
    frame 3: time = 2.010F; physicstime=0.0F: one phy-update pending -> performed and physicstime is set to exactly 1.2F
    frame 3: time = 3.011F; physicstime=1.2F: one phy-update pending -> performed and physicstime is set to exactly 2.4F
    frame 4: time = 4.021F; physicstime=2.4F: one phy-update pending -> performed and physicstime is set to exactly 3.6F
    frame 5: time = 5.020F; physicstime=3.6F: two phy-updates pending -> performed and physicstime is set to exactly 5.0F
    frame 6: time = 6.028F; physicstime=5.0F: no phy-update pending
    frame 7: time = 7.021F; physicstime=6.2F: one phy-update pending -> performed and physicstime is set to exactly 6.2F
    (huge lag)
    frame 8: time =12.028F; physicstime=6.2F: four (12F-6.2F/1.2F = 4.8F) updates pending -> 3 are performed but then we exceed the processingtime 0.1 seconds allowed timestep, physicstime is set to exactly 9.8F
    frame 9: time =13.030F; physicstime=9.8F: two phy-update pending -> performed and physicstime is set to exactly 12.2F
    Now, look at the relation between the physics time and the time. If a rigidbody would travel across the screen at 1unit/s it would not be smooth in any sense. This would be the visible total distance it traveled in each render frame: -> 0u,0u,1u,2u,3u,5u,5u,6u (lag) 10u,12u

    So the simulation is always speeding up and down compared to the real world time. Because of that you need to interpolate if you want smoothness. Here is some old code (but ignore the build in the top post, it is too old) https://forum.unity.com/threads/smo...iminating-hiccup-sources.162694/#post-1149041
     
  19. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    What issues are you having with rigid body interpolation?

    I'm working on a game where all movement is done in FixedUpdate using InControl. To get the lowest possible input latency, we have InControl set to update on FixedUpdate, making sure it's script execution order is lower than our character movement script.

    But if you really want, you can make your physics run in Update: disable "auto simulation" in the project settings, and have a singleton Monobehaviour call Physics.Update(Time.deltaTime) on its update. By adjusting script execution order, you can read input before simulating the physics for each frame, which should give you even lower latency.

    But be aware that using a varying time step for physics will cause things to behave slightly different depending on the target device framerate. Things like max jump height will end up different between 30fps and 120fps (this is actually an issue in UE4, which uses variable time step.)

    In our case, using fixedUpdate for everything and interpolating ensures the game behaves exactly the same no matter the device framerate. It also helps with online multiplayer since we implemented a custom client movement reconciliation: having a fixed time step allow us to more easily replay the client's stored moves when a misprediction occurs.

    Now, using fixed update has a caveat when you are moving things around without using rigid bodies. For example, we use character controller for players (because replaying moves on a rigid body was too convoluted). For those, we had to create a custom "transform interpolator" component which tries to mimic the RigidBody's native interpolation as well as adding "EarlyFixedUpdate" and "LateFixedUpdate" events.
     
    Last edited: Aug 25, 2021
  20. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Really? I have been looking for this :)
    How do i need to set it up to reliably get a callback just before physics? It needs to be every frame, even if no physics are performed in it:
    upload_2021-8-25_17-27-18.png
    Unity - Manual: Order of execution for event functions (unity3d.com)
     
  21. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    I defined what I meant by smooth.

    ...

    Also, is your example at 1 fps and physics a 0.83 ticks per second?

    Yeah... there's going to be all sorts of synchronization issues at that rate. As well as simulation issues because Unity specifically points out in the documentation that very large delta times for simulations result in non-deterministic behaviour.

    Basically... the simulation behaves in a manner that assumes it'll be running in the 50 and higher ticks per second. Not... < 1.

    Combined with the relatively small allowed timestep (allowed 0.1, to simulate 1.2, at a 1s interval) you're effectively forcing huge stutters. Hence your long lag. Your allowed time shouldn't be significantly smaller than your frame time. You have the entirity of frame time to process, why restrict it to 10% of that? Only reason is to exaggerate the syncrhonization stutter.

    ...

    In the end if you have your physics set to tick say 50 times per second.

    And you have an object moving at 5 units/second.

    After 1 second... it will be 5 units along.

    That's the point of the physics simulation. It attempts to simulate the physics deterministictly. This is what I personally was referring to as it being "smooth".

    I understand if you are using it to refer to synchronization stutter. But I don't put the blame on the physics update for that. I put the blame on the fact that framerates aren't smooth. The stutter comes from the fact that framerates vary, and therefore the physics simulation needs to be processed in a manner to account for that.

    Basically...

    It's a semantics argument.

    You're not wrong in that stutters DO exist (though your example is extremely exaggerated). I'm not saying they don't.

    I'm saying that the physics is simulated at a constant rate. Things don't speed up or slow down in the sense that the simulated velocity/speed does not actually change.

    The visualization stutters because the visual frame rate is out of sync with the simulation rate.

    But in the end if you track an object across the screen over time it will simulate deterministically at the speed/velocity you set it to.

    Basically I'm saying the simulation is smooth.

    We're both saying that the frame rate is not smooth.

    (and your example demonstrates this. The simulation time is exactly on point, where as the frame time is off by as much as 0.01 frame to frame)
     
    Last edited: Aug 25, 2021
  22. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    If you disable auto-simulation you can control when the simulation happens by calling it manually, so you can do things like reading input and applying forces before it.

    Also, if you create a Monobehaviour and set it's script execution order to be lower than everything else, it's FixedUpdate will execute before all other classes' FixedUpdates. You can effectively use that to fire an event you can call "EarlyFixedUpdate", which is guaranteed to be the first one before all other FixedUpdates. Likewise you can do the inverse: make another Monobehaviour with a very high script execution order and it's FixedUpdate will execute after all others, but before auto-simulated physics.
     
  23. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    This is only beneficial if all your problems can be solved at a physics update rate that's equal to (or less than) the Update rate.

    In my case, that's not the case, for all sorts of things. The faster the fixedUpdate rate, the better are joints, the accuracy and responsiveness of collisions, the nature of damping behaviour, the application of forces is more robust and interesting, and the ultimate frame rates of the target devices go up to 240 fps, the rate I'm using fixedUpdate at.

    At this rate, there are still some issues with the positional presentation of things being a bit stuttery. This is only noticeable by those with good reflexes and rates of response to visual stimuli, but that's the target player group. So it's a bit of a problem.

    There's something polyrhythmic about the way things are positioned and presented relative to their motion in a perfectly presented timeline.

    This is easiest to observe if you make a white stick the height of the screen and about a finger's width (on a normal monitor), on a black background, and spin it with physics. Our eyes and brains can best see jitter in this situation. But some cannot see it, at all. Blessed are they.

    This is harder to accurately detect with moving objects, because everyone has widely varying saccades and pursuits. But spinning objects, we're better at detecting their rates, and inconsistencies in those rates.

    I mention this because it will isolate jitter better than just about any other human testing can, not because it's particularly applicable to anything else.


    This is a whole other set of issues, when dealing with rapid collisions and varying rates of friction dependent behaviour, and joints. It's always a compromise between rates of interpolation, rates of physics updates and modifying behaviours with custom adjustments.

    This is one of the reasons I'm using Unity, Box2D and a fixedUpdate rate of 240Hz, these things combined mean I'm somewhat able to just worry about presentation rates changing and modding graphics loads to suit, and can leave physics at this 240Hz for all devices.

    I want to the read the post production book you write!!!
     
    Marrt likes this.
  24. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Yes, but only IF at least one fixed update is needed that frame. You can't use that for KeyUp/Down as we discussed. As of now, you cannot effectively have a hook before the FixedUpdate portion of the engine loop that is called in each frame... EXCEPT: if you create a component every frame at the EndOfFrame via a Coroutine and use its Start/Awake as hook. I do that, but it seems hacky, hence my question.

    Yeah, i just wanted to get your idea straight because i fear it might lead others back to the idea that the physics is perfectly tied to the real time and will reliably happen 50 times per second with GHz timer accuracy.
    If you say the requirement for something to be smooth is sufficiently met by having a virtual simulation timer that is increased by the same amount every tick, fine, it is smooth by that definition. I just think these words can easily mislead others that havent spent countless hours on thinking about this like we did. So i would refrain from calling it smooth.

    The physics System is basically a motorcycle trying to paint dashes on a street that is completely blocked by a slow unpassable renderframe bus that occasionally stops because someone needs to use the toilet.
    I hope that bad analogy is at least amusing :rolleyes:
     
  25. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Coincidently, I think my very old build shows exactly that, But i think you need to have a gsync or fressync monitor because something in that old code won't respect the buidl in vsync anymore
    http://marrtgames.com/index.html?buildName=SmoothMovement

    press fullscreen and set it up like this, then toggle Extra/Interpolation:
    upload_2021-8-26_13-48-40.png
    the spinning bar should very visible jitter if no inter or extrapolation is enabled, even if you increase the physic rate (top right)
    upload_2021-8-26_13-50-19.png

    How you would incorporate that into a high stakes, high reflexes game like shooter where the visible hitboxes really really matter is another topic :) (god i hate those emote designs, but i need some emotes in my dull posts)
     
  26. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    I just had to deal with GetKeyDown being completely broken when used inside FixedUpdate yesterday, lol. Resolved that by keeping track of key states in two dictionaries which are alternated every fixed update, then compare the current state with the previous one.

    I swear at least 50% of my work is writing workarounds to Unity's shortcomings, geez.
     
  27. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Neto_Kokku, that seems like an oddly complicated workaround : /

    If you are interested, i can share my current Tickmanager. It also achieves pre physics keydown readings
     
  28. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    The documentation for GetKeyDown tells you explicitly that it is only valid in Update().

    From: https://docs.unity3d.com/ScriptReference/Input.GetKeyDown.html
     
  29. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    It was simple, actually, about 30 or so lines of code. Using Time.fixedTime / Time.fixedDeltaTime you can get the fixed update "tick" number. With that it's simple to flip flop which dictionary gets written to and which gets read from: I write GetKey() into the "current" dictionary and know the previous state by reading from the "previous" dictionary.

    Since key Down/Up pooling code runs every frame, there's no need to actively keep reading all keys, I just write the current value when it's requested, since the next tick it will be available as the "previous" value. By making this a static class with the same methods as the Input class, I just had to add this at the top of the script to use it:

    Code (CSharp):
    1. using Input = FixedUpdateInput;
     
  30. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Exact, even if Unity's inter/extra-polation introduces its own set of issues.

    Physic may be "smooth", but it visually isn't and that's what matter. It will never be smooth if you visual is bound to a FixedUpdate physic, no matter how much you try to interpolate the camera or the character.

    The solution we picked is to not have any FixedUpdate ever. We call the physic ourselves in early in LateUpdate. This way, physic updates are always 1 per visual frame.
     
  31. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    You lost me, I suspect that you might be doing something counterproductive, but I might be wrong and mean no harm or insult.

    Valid approach if you can ensure framerate stability and have modes for 120+ Hz Monitors. Although I can't imagine how you would do that without actually changing the physics timestep which would somewhat change your physics behavior.
     
  32. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    What would you need exactly for 120Hz monitors?
     
  33. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    No problem, I think I wasn't clear enough. This is the script with the basics of it:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class FixedUpdateInputHelper
    6. {
    7.     private static Dictionary<KeyCode, bool> keyStates0 = new Dictionary<KeyCode, bool>();
    8.     private static Dictionary<KeyCode, bool> keyStates1 = new Dictionary<KeyCode, bool>();
    9.  
    10.     private static int FrameIndex
    11.     {
    12.         get
    13.         {
    14.             if (Time.inFixedTimeStep)
    15.                 return Mathf.RoundToInt(Time.fixedTime / Time.fixedDeltaTime);
    16.             else
    17.                 return Time.frameCount;
    18.         }
    19.     }
    20.  
    21.     private static Dictionary<KeyCode, bool> GetStates(int offset)
    22.     {
    23.         if ((FrameIndex + offset) % 2 == 0)
    24.             return keyStates0;
    25.         else
    26.             return keyStates1;
    27.     }
    28.  
    29.     private static Dictionary<KeyCode, bool> CurrentState => GetStates(1);
    30.     private static Dictionary<KeyCode, bool> PreviousState => GetStates(0);
    31.  
    32.     public static bool GetCurrentKey(KeyCode keyCode)
    33.     {
    34.         var value = Input.GetKey(keyCode);
    35.         CurrentState[keyCode] = value;
    36.         return value;
    37.     }
    38.  
    39.     public static bool GetPreviousKey(KeyCode keyCode)
    40.     {
    41.         if (PreviousState.TryGetValue(keyCode, out bool isDown))
    42.             return isDown;
    43.         return false;
    44.     }
    45.  
    46.     public static bool GetKeyDown(KeyCode keyCode)
    47.     {      
    48.         return GetCurrentKey(keyCode) && !GetPreviousKey(keyCode);
    49.     }
    50. }
    51.  
    The drawback of this approach is that you need to call GetKeyDown() for at least two consecutive ticks to get a valid result, but for our use case it was enough.
     
  34. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    So you use that to basically be able to write "FixedUpdateInputHelper.GetKeyDown()" within FixedUpdate? Seems a bit cumbersome. The only gain you get is that you should be able to use a KeyDown in Physics in the same frame it was pressed. Normally people set things like Jump-Flags in Update that will only get consumed in the FixedUpdate of the NEXT frame.

    If you care for it, here is my current Tickmanager. It does a lot of other things too, but if you want to be able to receive keydowns to set flags that get used in FixedUpdate, use "TickManager.FirstTickWithDeltaTime += MyInputFetcher;"
    Code (CSharp):
    1. //Version 2018-08-06
    2.  
    3.  
    4. using UnityEngine;
    5. using System;
    6. using System.Collections;
    7. using System.Collections.Generic;    //List of IEnumerators
    8.  
    9. //    Marrt's TickManager
    10. //        Better timing Management for Unity
    11. //        very enhanced version of what i found here
    12. //        http://answers.unity3d.com/questions/59876/how-to-achieve-a-latefixedupdate-effect.html
    13. //
    14. //    https://docs.unity3d.com/Manual/ExecutionOrder.html
    15.  
    16. //
    17. // usage: subscribe to event within OnDisable and OnEnable
    18. //        void OnEnable(){  
    19. //            TickManager.FirstTick1    += MyInputFetcher;
    20. //            TickManager.FixedTick1    += MyFixedUpdate;
    21. //            TickManager.FixedTick2    += MyLateFixedUpdate;
    22. //            TickManager.Tick1        += MyUpdate;
    23. //        }
    24. //        void OnDisable(){
    25. //            TickManager.FirstTick1    -= MyInputFetcher;
    26. //            TickManager.FixedTick1    -= MyFixedUpdate;
    27. //            TickManager.FixedTick2    -= MyLateFixedUpdate;
    28. //            TickManager.Tick1        -= MyUpdate;
    29. //        }
    30. //        private void MyInputFetcher(){
    31. //            //don't do physics stuff in here!
    32. //            //don't use deltaTime!
    33. //            //-> only prepare stuff that the next physics loop picks up,
    34. //            //        - like setting jump flag that resets after it is used in the actual physics step or
    35. //            //        - updating a moveInputVector that is used to change velocity
    36. //            //        - this is how you should do it anyway, even without this tickmanager
    37. //        }
    38. //        private void MyFixedUpdate(){ /*your previous FixedUpdate() code here*/ }
    39. //      
    40. // Order:
    41. //          1 firstTick1        // once per frame before the fixed ticks, can fetch Input before Fixed Update, happens even if no fixed Update happened this frame
    42. //          2 firstTick2
    43. //        
    44. //        Physics{    //may happen multiple times per game-loop or not at all
    45. //          3 fixedTick1;        // use this to replace FixedUpdate()
    46. //          4 fixedTick2;        // use this to replace FixedUpdate()
    47. //          5 every coroutine subscribed by AddPrePhysicsUpdateCoroutine()
    48. //          6 fixedTick3;        // use this to replace FixedUpdate()
    49. //          - INTERNAL PHYSICS UPDATE, this is where stuff is actually moved
    50. //          - OnTriggerXXX
    51. //          - OnCollisonXXX
    52. //          7 fixedCoTick1
    53. //          8 fixedCoTick2
    54. //        }
    55. //      
    56. //          9 tick1            // use this to replace Update() by subscribing a Custom function to tick1()
    57. //         10 tick2
    58. //         12 coTick1
    59. //         13 coTick2
    60. //         14 lateTick1
    61. //         15 lateTick2
    62. //         16 lateTick3
    63. //         17 lateTick4
    64. //         18 EndOfFrame
    65.  
    66. public    enum TickType{
    67.     None,
    68.     FirstTickWithDeltaTime,
    69.     FirstTick1,
    70.     FirstTick2,
    71.  
    72.     FixedTick1,
    73.     FixedTick2,
    74.  
    75.     PreInternalPhysicsRoutines,
    76.  
    77.     FixedTick3,
    78.  
    79.         //INTERNAL PHYSICS UPDATE
    80.         //OnTriggerXXX
    81.         //OnCollisonXXX
    82.  
    83.     FixedCoTick1,
    84.     FixedCoTick2,
    85.  
    86.     Tick1,
    87.     Tick2,
    88.  
    89.     CoTick1,
    90.     CoTick2,
    91.  
    92.     LateTick1,
    93.     LateTick2,
    94.     LateTick3,
    95.     LateTick4,
    96.     EndOfFrame
    97. }
    98.  
    99. /// <summary>TickManager for better control of when things happen, put this on a persistent GameObject in your startscene</summary>
    100. public class TickManager : MonoBehaviour {
    101.  
    102.     private    static    TickType    activeTickStore = TickType.None;
    103.     public    static    TickType    ActiveTick{
    104.         get            { return activeTickStore;  }
    105.         private set    { activeTickStore = value; }
    106.     }
    107.  
    108.     private    static bool firstTickSubmitted = false;
    109.     public static event Action FirstTickWithDeltaTime;    //firstTick that is created through additional Component and runs before any physics plus yielding the correct deltaTime value
    110.     public static event Action FirstTick1;        // dont use 'Time.deltaTime' here, you will get the fixed Timestep mostly! always the first event per frame, can be used to fetch input before Fixed Update in order to use it for physics without delay
    111.     public static event Action FirstTick2;        // dont use 'Time.deltaTime' here, you will get the fixed Timestep mostly! second tick for first tick, can be used if inputs at firstTick1 create changes that must still be dealt with in other scripts before physics
    112.  
    113.     public static event Action FixedTick1;        // use this to replace FixedUpdate()
    114.     public static event Action FixedTick2;        // use this to replace FixedUpdate()
    115.     //preInternalPhysicsRoutines
    116.     public static event Action FixedTick3;        // use this to replace FixedUpdate()
    117.     //INTERNAL PHYSICS UPDATE, this is where stuff is actually moved, check below WorkerTest_UpdateBEFOREPhysicsCoroutine to see how implement something that runs in coroutine before physics update unlike yield return new WaitForFixedUpdate()
    118.     //OnTriggerXXX
    119.     //OnCollisonXXX
    120.     public static event Action FixedCoTick1;    // yield return new WaitForFixedUpdate();
    121.     public static event Action FixedCoTick2;    // yield return new WaitForFixedUpdate();
    122.    
    123.     public static event Action Tick1;            // use this to replace Update()
    124.     public static event Action Tick2;
    125.     public static event Action CoTick1;
    126.     public static event Action CoTick2;
    127.     public static event Action LateTick1;
    128.     public static event Action LateTick2;
    129.     public static event Action LateTick3;//suggestion: camera stuff
    130.     public static event Action LateTick4;//suggestion: After Camera changes
    131.  
    132.  
    133.     public static event Action EndOfFrameTick;
    134.  
    135.     public    static    float    lastFrameStartRealtime = -1F;
    136.    
    137.     private    static    TickManager instance;
    138.  
    139.     void Awake () {
    140.  
    141.         if(instance != null){
    142.             Debug.LogError("█■■ TickManager ■■█: Singleton Warning:\n"+this.GetType().Name+" could not assign itself because "+instance.gameObject.name+" already did");
    143.         }else{
    144.             instance = this; //Debug.Log("█■■ TickManager ■■█.instance was claimed ");
    145.         }
    146.  
    147.         StartCoroutine( CoroutineTick());
    148.         StartCoroutine( FixedCoTick());
    149.         StartCoroutine( EndOfFrameCall() );
    150.  
    151.         if(debugTimingSubscriptionsByKeyH){ DebugSubscriptionsTimings(); }
    152.  
    153.         //Profiling test for AddPrePhysicsUpdateCoroutine
    154.         //for(int i = 0; i<100000; i++) {    AddPrePhysicsUpdateCoroutine( WorkerTest_UpdateBEFOREPhysicsCoroutine() );    }
    155.     }
    156.  
    157.  
    158.     private static List<IEnumerator>    preInternalPhysicsRoutines    = new List<IEnumerator>();
    159.     private static List<IEnumerator>    preInternalRemovalMarker    = new List<IEnumerator>();
    160.    
    161.     //How to get a Coroutine to run reliably BEFORE INTERNAL PHYSICS UPDATE?    
    162.     // Add your IEnumerator here 'AddPrePhysicsUpdateCoroutine()', yield by 'yield return null;'
    163.     // save a reference to your coroutine before adding, never call StartCoroutine on it:
    164.     //      
    165.     //        Start:
    166.     //        myCoroutineReference = MyCoroutine( myVar1, myVar2 );
    167.     //        AddPrePhysicsUpdateCoroutine( myCoroutineReference );
    168.     //
    169.     //        Removal:
    170.     //        After it has been finished it is automatically removed, you can also remove it manually
    171.     //        RemovePrePhysicsUpdateCoroutine( myCoroutineReference );
    172.     //        (is there a memory buildup problem i might not aware of?, do i have to end the IEnumertor explicitely somehow, profiler seems fine)
    173.     //
    174.     /// <summary> Subscribe an IEnumerator to be called BEFORE the internal physics Update. In the Coroutine, yield by 'yield return null;' </summary>
    175.     public    static    void AddPrePhysicsUpdateCoroutine    ( IEnumerator coroutine )    {    preInternalPhysicsRoutines.Add        (coroutine);    }
    176.     public    static    void RemovePrePhysicsUpdateCoroutine( IEnumerator coroutine )    {    preInternalPhysicsRoutines.Remove    (coroutine);    }
    177.    
    178.     IEnumerator WorkerTest_UpdateBEFOREPhysicsCoroutine() {
    179.         //TEST: dont start this via StartCoroutine, use AddPrePhysicsUpdateCoroutine( WorkerTest_UpdateBEFOREPhysicsCoroutine() );
    180.         int i = 0;
    181.         for(;;) {
    182.             i++; if(i == 100) { break; }
    183.             yield return null;
    184.         }
    185.     }
    186.  
    187.  
    188.     /// <summary>For Debuggung Print the amount of subscriptions that are hooked to the events</summary>
    189.     public string PrintSubCounts(){
    190.         string subscriptionInfo = "";
    191.        
    192.         subscriptionInfo +=    "firstTick\t\t"    +"["+    (FirstTick1        ==null?"000":FirstTick1.    GetInvocationList().Length.ToString("D3"))+"]\n";
    193.         subscriptionInfo +=    "firstTick\t\t"    +"["+    (FirstTick2        ==null?"000":FirstTick2.    GetInvocationList().Length.ToString("D3"))+"]\n";
    194.         subscriptionInfo +=    "fixedTick1\t\t"+"["+    (FixedTick1        ==null?"000":FixedTick1.    GetInvocationList().Length.ToString("D3"))+"]\n";
    195.         subscriptionInfo +=    "fixedTick2\t\t"+"["+    (FixedTick2        ==null?"000":FixedTick2.    GetInvocationList().Length.ToString("D3"))+"]\n";
    196.         subscriptionInfo +=    "prePhyCoro\t\t"+"["+    preInternalPhysicsRoutines.Count.ToString("D3")+"]\n";
    197.         subscriptionInfo +=    "fixedTick3\t\t"+"["+    (FixedTick3        ==null?"000":FixedTick3.    GetInvocationList().Length.ToString("D3"))+"]\n";
    198.         subscriptionInfo +=    "fixedCoTick1\t"+"["+    (FixedCoTick1    ==null?"000":FixedCoTick1.    GetInvocationList().Length.ToString("D3"))+"]\n";
    199.         subscriptionInfo +=    "fixedCoTick2\t"+"["+    (FixedCoTick2    ==null?"000":FixedCoTick2.    GetInvocationList().Length.ToString("D3"))+"]\n";
    200.         subscriptionInfo +=    "tick1\t\t"        +"["+    (Tick1            ==null?"000":Tick1.            GetInvocationList().Length.ToString("D3"))+"]\n";
    201.         subscriptionInfo +=    "tick2\t\t"        +"["+    (Tick2            ==null?"000":Tick2.            GetInvocationList().Length.ToString("D3"))+"]\n";
    202.         subscriptionInfo +=    "coTick1\t\t"    +"["+    (CoTick1        ==null?"000":CoTick1.        GetInvocationList().Length.ToString("D3"))+"]\n";
    203.         subscriptionInfo +=    "coTick2\t\t"    +"["+    (CoTick2        ==null?"000":CoTick2.        GetInvocationList().Length.ToString("D3"))+"]\n";
    204.         subscriptionInfo +=    "lateTick1\t\t"    +"["+    (LateTick1        ==null?"000":LateTick1.        GetInvocationList().Length.ToString("D3"))+"]\n";
    205.         subscriptionInfo +=    "lateTick2\t\t"    +"["+    (LateTick2        ==null?"000":LateTick2.        GetInvocationList().Length.ToString("D3"))+"]\n";
    206.         subscriptionInfo +=    "lateTick3\t\t"    +"["+    (LateTick3        ==null?"000":LateTick3.        GetInvocationList().Length.ToString("D3"))+"]\n";
    207.         subscriptionInfo +=    "lateTick4\t\t"    +"["+    (LateTick4        ==null?"000":LateTick4.        GetInvocationList().Length.ToString("D3"))+"]\n";
    208.        
    209.         print(subscriptionInfo);
    210.         return subscriptionInfo;
    211.     }
    212.  
    213.  
    214.     //CALLS:
    215.     public    static    TickHelper tickHelper = null;
    216.  
    217.     public static void TickHelperStart( int creationFrame )
    218.     {
    219.         if(debugTimingSubscriptionsByKeyH){ print("5. called TickHelperStart  (created:"+creationFrame +") in frame:"+Time.frameCount); }
    220.         ActiveTick = TickType.FirstTickWithDeltaTime;  
    221.         FirstTickWithDeltaTime?.Invoke();    //if(FirstTickWithDeltaTime != null)            { FirstTickWithDeltaTime(); }
    222.         CheckFirstTickSubmission();
    223.     }
    224.  
    225.     private    static void CheckFirstTickSubmission()
    226.     {
    227.         if(!firstTickSubmitted){          
    228.             ActiveTick = TickType.FirstTick1;        if(FirstTick1 != null)        { FirstTick1();    }
    229.             ActiveTick = TickType.FirstTick2;        if(FirstTick2 != null)        { FirstTick2();    }
    230.             lastFrameStartRealtime    = Time.realtimeSinceStartup;
    231.             firstTickSubmitted        = true;
    232.         }
    233.     }
    234.  
    235.     void FixedUpdate()
    236.     {
    237.         CheckFirstTickSubmission();
    238.  
    239.         ActiveTick = TickType.FixedTick1;        if(FixedTick1 != null)            { FixedTick1();    }
    240.         ActiveTick = TickType.FixedTick2;        if(FixedTick2 != null)            { FixedTick2();    }
    241.        
    242.  
    243.         ActiveTick = TickType.PreInternalPhysicsRoutines;
    244.         //manually continue all Coroutines added to preInternalPhysicsRoutines
    245.         preInternalRemovalMarker.Clear();
    246.         foreach(IEnumerator ien in preInternalPhysicsRoutines)    {    if( !ien.MoveNext()) { preInternalRemovalMarker.Add(ien);  }    }    //if(preInternalRemovalMarker.Count > 0) { print("ending something");}
    247.         foreach(IEnumerator ien in preInternalRemovalMarker)    {    preInternalPhysicsRoutines.Remove(ien);    }
    248.  
    249.         ActiveTick = TickType.FixedTick3;        if(FixedTick3 != null)            { FixedTick3();        }
    250.     }
    251.     IEnumerator FixedCoTick()
    252.     {
    253.         for(;;) {
    254.             ActiveTick = TickType.FixedCoTick1;        if(FixedCoTick1 != null)        {    FixedCoTick1();    }
    255.             ActiveTick = TickType.FixedCoTick2;        if(FixedCoTick2 != null)        {     FixedCoTick2();    }
    256.             yield return new WaitForFixedUpdate();
    257.         }
    258.     }
    259.    
    260.     void Update()
    261.     {
    262.         //if no physics Update happened and no subscriber uses FirstTickWithDeltaTime, this frame this is the first Tick
    263.         CheckFirstTickSubmission();
    264.  
    265.         ActiveTick = TickType.Tick1;        if(Tick1 != null) { Tick1(); }
    266.         ActiveTick = TickType.Tick2;        if(Tick2 != null) { Tick2(); }
    267.     }
    268.     IEnumerator CoroutineTick()
    269.     {
    270.         for(;;) {
    271.             ActiveTick = TickType.CoTick1;        if(CoTick1 != null) { CoTick1(); }
    272.             ActiveTick = TickType.CoTick2;        if(CoTick2 != null) { CoTick2(); }
    273.             yield return null;
    274.         }
    275.     }
    276.     void LateUpdate()
    277.     {
    278.         ActiveTick = TickType.LateTick1;        if(LateTick1 != null) { LateTick1();}
    279.         ActiveTick = TickType.LateTick2;        if(LateTick2 != null) { LateTick2();}
    280.         ActiveTick = TickType.LateTick3;        if(LateTick3 != null) { LateTick3();}
    281.         ActiveTick = TickType.LateTick4;        if(LateTick4 != null) { LateTick4();}
    282.     }
    283.  
    284.     private    IEnumerator EndOfFrameCall()
    285.     {
    286.         int lastCreationFrame = 0;
    287.  
    288.         while(true){
    289.  
    290.             ActiveTick = TickType.None;
    291.  
    292.             //Wait for the very end of the frame
    293.             yield return new WaitForEndOfFrame();
    294.  
    295.             ActiveTick = TickType.EndOfFrame;
    296.             if( EndOfFrameTick != null ) { EndOfFrameTick();}
    297.  
    298.             //catch double creations
    299.             if( lastCreationFrame == Time.frameCount ){    Debug.LogWarning("Helper called twice, may happen during Pause in Editor"); continue;    }
    300.  
    301.             //add new tickhelerp to get a new awake, NEEDS TO BE DONE IN 'EndOfFrame', otherwise Start() will be called immidiately
    302.             if( tickHelper != null ){    Destroy ( tickHelper );    }
    303.  
    304.             if( FirstTickWithDeltaTime != null ){
    305.                
    306.                 if(debugTimingSubscriptionsByKeyH){ print("1. before adding TickHelper "+Time.frameCount); }
    307.  
    308.                 //create new Component that calls its sertfasdgbvac at beginning of next frame
    309.                 tickHelper            = this.gameObject.AddComponent<TickHelper>();
    310.                 lastCreationFrame    = tickHelper.creationFrame;
    311.  
    312.                 if(debugTimingSubscriptionsByKeyH){ print("3. after adding TickHelper "+Time.frameCount); }
    313.  
    314.             }
    315.             //reset firstTick at the very end
    316.             firstTickSubmitted = false;
    317.         }
    318.     }
    319.    
    320.     public    static bool    debugTimingSubscriptionsByKeyH = false;
    321.     private    string    chronoString = "";
    322.     private    void    DebugSubscriptionsTimings(){
    323.  
    324.         KeyCode testKey = KeyCode.H;
    325.  
    326.         FirstTickWithDeltaTime    += () => {    chronoString  = "";                                                                            };
    327.         FirstTickWithDeltaTime    += () => {    chronoString += "FirstTickDeltaTime\t"    +Time.frameCount+","+Input.GetKeyDown(testKey)+", ("+Time.deltaTime +" != "+Time.fixedDeltaTime+")\n";    };
    328.         FirstTick1                += () => {    chronoString += "FirstTick1\t\t"        +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    329.         FirstTick2                += () => {    chronoString += "FirstTick2\t\t"        +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    330.         FixedTick1                += () => {    chronoString += "FixedTick1\t\t"        +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    331.         FixedTick2                += () => {    chronoString += "FixedTick2\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    332.         //                        +=                                    preInter    nalPhysicsRoutines.Count.ToString("D3")+"]\n";
    333.         FixedTick3                += () => {    chronoString += "FixedTick3\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    334.         FixedCoTick1            += () => {    chronoString += "FixedCoTick1\t"        +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    335.         FixedCoTick2            += () => {    chronoString += "FixedCoTick2\t"        +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    336.         Tick1                    += () => {    chronoString += "Tick1\t\t"                +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    337.         Tick2                    += () => {    chronoString += "Tick2\t\t"                +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    338.         CoTick1                    += () => {    chronoString += "CoTick1\t\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    339.         CoTick2                    += () => {    chronoString += "CoTick2\t\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    340.         LateTick1                += () => {    chronoString += "LateTick1\t\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    341.         LateTick2                += () => {    chronoString += "LateTick2\t\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    342.         LateTick3                += () => {    chronoString += "LateTick3\t\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    343.         LateTick4                += () => {    chronoString += "LateTick4\t\t"            +Time.frameCount+","+Input.GetKeyDown(testKey)+"\n";    };
    344.  
    345.  
    346.         LateTick4                += () => {    print(chronoString);                                        };
    347.         #if UNITY_EDITOR
    348.         LateTick4                += () => {    if(Input.GetKeyDown(testKey)){ UnityEditor.EditorApplication.isPaused = true;};                    };
    349.         #endif
    350.     }
    351.    
    352.     #region CopyPasta
    353.     //put relevant event subscriptions into the monobehaviour. unsubscribe in disable via '-=' referiung to the same Actions or Functions, caching them might be advisable
    354.    
    355.     private    System.Action    dF    = ()=>{};    //dummyFunction
    356.     private    IEnumerator        dC    = null;        //dummyCoroutine
    357.  
    358.     private void OnEnableSubscribe(){
    359.         TickManager.FirstTickWithDeltaTime    += dF;
    360.         TickManager.FirstTick1                += dF;
    361.         TickManager.FirstTick2                += dF;
    362.  
    363.         TickManager.FixedTick1                += dF;
    364.         TickManager.FixedTick2                += dF;
    365.         TickManager.AddPrePhysicsUpdateCoroutine(dC);
    366.         TickManager.FixedTick3                += dF;
    367.         //INTERNAL PHYSICS UPDATE
    368.         //OnTriggerXXX
    369.         //OnCollisonXXX
    370.         TickManager.FixedCoTick1            += dF;
    371.         TickManager.FixedCoTick2            += dF;
    372.    
    373.         TickManager.Tick1                    += dF;
    374.         TickManager.Tick2                    += dF;
    375.         TickManager.CoTick1                    += dF;
    376.         TickManager.CoTick2                    += dF;
    377.         TickManager.LateTick1                += dF;
    378.         TickManager.LateTick2                += dF;
    379.         TickManager.LateTick3                += dF;
    380.         TickManager.LateTick4                += dF;
    381.     }
    382.     private void OnDisableUnsubscribe(){
    383.         TickManager.FirstTickWithDeltaTime    -= dF;
    384.         TickManager.FirstTick1                -= dF;
    385.         TickManager.FirstTick2                -= dF;
    386.  
    387.         TickManager.FixedTick1                -= dF;
    388.         TickManager.FixedTick2                -= dF;
    389.         TickManager.AddPrePhysicsUpdateCoroutine(dC);
    390.         TickManager.FixedTick3                -= dF;
    391.         //INTERNAL PHYSICS UPDATE
    392.         //OnTriggerXXX
    393.         //OnCollisonXXX
    394.         TickManager.FixedCoTick1            -= dF;
    395.         TickManager.FixedCoTick2            -= dF;
    396.    
    397.         TickManager.Tick1                    -= dF;
    398.         TickManager.Tick2                    -= dF;
    399.         TickManager.CoTick1                    -= dF;
    400.         TickManager.CoTick2                    -= dF;
    401.         TickManager.LateTick1                -= dF;
    402.         TickManager.LateTick2                -= dF;
    403.         TickManager.LateTick3                -= dF;
    404.         TickManager.LateTick4                -= dF;
    405.     }
    406.     #endregion CopyPasta
    407. }
    This is the helper that creates a pre-physics hook each frame, don't add it to a gameobject, the tickmanager will do that.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. /// <summary>
    7. /// Component that is created and deleted every frame to give the possibility to have pre physics Input with correct 'Time.deltaTime',
    8. /// since 'Time.deltaTime' will not yield the wanted value during the FixedUpdate Cycle</summary>
    9. public class TickHelper : MonoBehaviour {
    10.     public int creationFrame = 0;
    11.     void Awake () {
    12.     //    No need to disable the component, only if it is created at the EndOfFrame, its Start will not be called that frame but next: this.enabled    = false;
    13.         creationFrame    = Time.frameCount;
    14.  
    15.         if( TickManager.debugTimingSubscriptionsByKeyH ){ print("2. created TickHelper: "+creationFrame); }
    16.     }
    17.     void Start () {
    18.  
    19.         if( TickManager.debugTimingSubscriptionsByKeyH ){ print("4. TickHelper (created:"+creationFrame +") calling TickHelperStart in frame:"+ Time.frameCount ); }
    20.  
    21.         TickManager.TickHelperStart(creationFrame);
    22.     }
    23. }
    24.  
     
  35. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Well, if you say that you tightly couple the renderframes to the physicframes, wouldn't your physics rund twice as fast on 120Hz Monitors compared to 60Hz?
    I would imagine that you would have to set your physics timestep to match your framerate(60Hz->0,0166666, 120Hz->0,0083333), even if you manually call your physics Update (You said "We call the physic ourselves in early in LateUpdate"). And by doing that, i am pretty confident that collisions and other things behave differently.

    If you don't match your Timestep at all and just leave it at 0.02, your velocities would not be correct. E.g. if you call your physics update 60 times per second but your timestep is 0.02, your rigidbodies would move 20% faster, 1unit/s would be visibly 1.2units/s or even 2.88 units/s for 144Hz.

    Or am i missing something here? :confused:
     
  36. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    Using KeyDown in fixed update was exactly the use case for this script, so there wasn't any need for much more.

    Setting a flag in update would cause repeated inputs if more than one fixed update fired between two updates (frame time larger than fixed time step).

    We also only needed to add a "using" at the top of the script to replace the call to Input to our custom class, since they have the same signatures.
     
  37. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    No, you set a jump flag, read it in FixedUpdate, and only ever set it to false at the moment it has been used (=consumed). Maybe "Jumptoken" would be a better name then. No double reads.
     
  38. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    Ah yes, that would work. But the code which we had to fix this for was... not the best, so we needed a solution which required less structural changes.
     
  39. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    There's no reason to use such flags for jumping. You can simply apply the jump impulse in Update when the jump key is pressed. Forces applied outside FixedUpdate will be delayed / accumulated until the nexts physics update anyways.

    Just for completeness, I made a simple simulation that visually shows what FixedUpdate actually does depending on that framerate and the fixed framerate. It's true that you can not get rid of the interference pattern you get between the two frequencies. When you mix 50Hz and 60Hz you get an "overtone" or more precise a difference tone of 10Hz. Since we talk about discrete simulation steps this interference will be +1 ot -1 frame jumps at that difference frequency. Of course the higher the frequencies, the lower the absolute error or jump, however you can't eliminate it completely.

    The simple answer is you always have one of the two issues. Either you run physics on the normal visual update (which is now possible thanks to Physics.Simulate) then you get rid of the interference, however your simulation is now frame dependent and can't really be made frame independent. When you use a fixed frame rate for physics you get a more or less reliable simulation that is independent of the visual frame rate, but you will get the interference pattern most people have complained about.
     
    Marrt and lordofduct like this.
  40. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    We pass Time.DeltaTime to Physics.Simulate... So the physic simulation is always synced to the framerate.

    Surprisingly, since we aren't doing a deterministic game, it works great.
     
    Bunny83 likes this.
  41. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Oh, i didn't know that. Most of the time you also have some physics-checks along those jumps and other impulses... but those should behave the same way during the preceding Update of the previous frame if i am not mistaken, since the Physics-Positions, velocities, etc. should stay the same. Well, if you rely on some Non-rigidbody positions to create AI-drive directions, but well, those differences will be miniscule at best and you shouldn't do that anyway.

    However, you will still have that "at least one frame delay" that way, since Update comes after any eventuall FixedUpdate. Hence any KeyUp/Down you fetch in any Update, would have also been available in FixedUpdate of that frame (if there was any), so you are basically saying: lets add about 7 to 16ms of input delay to my game. And if you are building a Physics based plattformer, jumps are especially timing critical i suppose.
    So for plattformers i would fetch Input before FixedUpdate (e.g. via my Tickmanager script) and decrease the physics timestep to something lower than the framedelay. That way i will almost always have one or more FixedUpdates per render frame, so jumps and other timing critical inputs will create reliably short visual input-feedback. But maybe i am just overengineering and sidetracking instead of getting things done, as usual...:(
     
  42. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Hmm, are you using continuous collisions on your rigidbodies? if not, check how some lag will affect your physics:
    Code (CSharp):
    1.  
    2. if(Input.GetKeyDown(KeyCode.Space)){ System.Threading.Thread.Sleep(2000); Debug.Log(Time.deltaTime);}
    3. if(Input.GetKeyUp  (KeyCode.Space)){ Debug.Log(Time.deltaTime);}
    4.  
    and check if anything passes through your ground or walls when moving.
    If yes, set your upload_2021-9-3_10-21-15.png to something reliable, since that setting will cap your deltaTime to its value.

    https://stackoverflow.com/a/5743843
     
  43. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    We wrote our own character controller that performs raycast and sphere cast every frame. Even with very low FPS (10-15), our character doesn't cross walls or floor.
     
    Marrt likes this.
  44. qwty1234

    qwty1234

    Joined:
    Apr 24, 2022
    Posts:
    1
    Why do we need to run those extra functions before update? Since update runs a lot more than fixed update and probably the other functions as well, why can't we simplify things and have just one function that handles everything?
     
  45. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    For starters Update doesn't necessarily run "a lot more than" fixedupdate. It's that fixedupdate runs a fixed number of times per second (of course there is some slight fuzziness to its overlap with said second due to how it's updated). Where as update fires a completely different number of times.

    By default physics/fixedupdate updates 50 times per second (this can be configured).

    Update on the other hand can fire anywhere from 0 to a whole bunch depending on the performance of your system. You can also add in refresh sync requirements. Basically though it's your "frame rate", if you're getting 30fps, it's updating LESS frequently than fixedupdate. And if you're running at 120fps, it's updating "a lot more than". If you're at 50-60fps though, a pretty standard fps on PC, and you end up roughly the same amount of updates.

    ...

    Now with that said... you say:
    What functions are you referring to?

    There are 4 pages of posts here, and the most recent posts don't have any extra functions that I can see.

    This is a problem with necroing a post, especially one with as much history as this thread... you can't refer to something completely out of context like this.

    Give us some context.
     
  46. LogicFlow

    LogicFlow

    Joined:
    Aug 18, 2018
    Posts:
    33
    In spite of it appearing that there aren't: Are there any solutions to this? Aside from leaving Unity behind that is.
     
  47. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613

    I have no solution, but if you are willing to work for it yourself, I have three probable paths to salvation:

    Path 1:
    - Check out the new Input system, there might be the possibility to fetching input outside of the main loop and process it accordingly https://docs.unity3d.com/2022.2/Documentation/Manual/com.unity.inputsystem.html

    Path 2:
    - Check if you can measure the current frame's time consumption during the rendering progress and if there is a way of aborting it mid-render ("Scene Rendering" https://docs.unity3d.com/Manual/ExecutionOrder.html)
    - if yes, abort the current frame and just display the old one. Your gamelogic is still polling and executing inputs at your desired rate albeit that your losing some frames.

    Path 3:
    Make sure your game is optimized enough or play around with "Maximum Allowed Timestep" so that your game slows down instead. That way, short inputs should not be able to cause movement for a long duration - which is your problem i suppose
     
    LogicFlow likes this.
  48. RetroMasters82

    RetroMasters82

    Joined:
    Nov 3, 2021
    Posts:
    28