1
\$\begingroup\$

I've made a system which uses the ideas from "Fix your Time Step" in order update physics. I'm having trouble finding standard methods for dealing with this when user input affects player movement outside of a time-step.

If we want to integrate player movement within the "fixed time step, variable fps" system, I guess I would have to queue up player actions somehow, when accumulation hasn't yet completed. But then how do I integrate several different player actions within said timestep? IE lets say within timestep of N milliseconds, the player executes a move forward, move right and move backwards, where there is an infinite collidable wall in front.

  • I could always calculate the last input, but in this case, it wouldn't be correct to do so. The player actually should be blocked by the wall, and then moving right and back would move them to the right, and back, to the right of where only processing the last input would lead.

  • Similarly, if all movement was accounted for together, it might result in a player having an impossible vector for the final position, but if all moves were taken independently, there might have been a valid path (ie around a corner).

  • If I process all inputs then the player made during the time-step, it could conceivably be costly if they made a million tiny movements (collision check at each movement), though with exact physics (as far as a fixed time step simulation is concerned).

  • Even if we thought the last bullet wasn't that bad, as soon as you add multiple players to the equation, it starts looking really bad, especially with a server.

Are my only options to process all actions or only process a subset of them?

\$\endgroup\$
  • 1
    \$\begingroup\$ That's not what the problem bein expressed is. If say the timestep is 30 hz and the player is using a joystick. Then if the user clicks the button between samples then it will be lost. The only form of mechanism to solve this is to buffer all input until next sample frame then either resolve sequentially or sum the inputs to determine correct state through some logic. \$\endgroup\$ – ErnieDingo Jul 31 '19 at 7:05
  • \$\begingroup\$ @ErnieDingo Reading my comment again, you're right, it was not conveying what I had in mind when I wrote it. \$\endgroup\$ – Vaillancourt Jul 31 '19 at 20:31
  • \$\begingroup\$ Have you had situations where you received more than one input per simulation step? \$\endgroup\$ – Vaillancourt Jul 31 '19 at 21:48
  • 1
    \$\begingroup\$ @AlexandreVaillancourt At 30 and 20 physics iterations per second, definitely, but it also blocks fixed step entirely until I figure out what to do. I got bugs with it dropping inputs initially, cause by only taking one input when multiple were submitted. This is most definitely a problem pretty much any fixed rate game has encountered, but how they actually deal with it so far has been hidden from me. I have a solution I may post, but god damn is it frustrating knowing someone else most definitely has solved this problem but hasn't spoken up even out side of SE. \$\endgroup\$ – whn Jul 31 '19 at 23:53
  • 2
    \$\begingroup\$ @alexandrevaillancourt yes. I have had similar scenario with time step. But as per the op, there are strategies to combat this but they do end in dinner compromise. Either you execute a history of input which is highly complex, combine input history into a discreet input or drop the input entirely is the other option. Combining i found was the best compromise but it also requires rules around precedence of input etc. \$\endgroup\$ – ErnieDingo Aug 1 '19 at 6:40
2
\$\begingroup\$

I most often encounter this in the context of Unity, which has the following attributes:

  1. Input is checked once per displayed frame, before any of that frame's fixed/variable timestep updates
  2. Input is interpreted as one flat state for the entirety of the current display frame, not a queue of events

    • Each logical button has three independent boolean states:

      • Is currently down as of this input check
      • Has transitioned from up to down since last input check
      • Has transitioned from down to up since last input check

      (These are independent because a button could have been pressed & released all in the span of a single long frame)

    • Each logical analog axis has a current value as of this input check

  3. Zero or more FixedUpdate & Physics steps run to account for the game time passed

    • Here we first compute new physics forces / velocities in FixedUpdate routines...

    • ...then ask the physics engine to tick the simulation forward one fixed time step, integrating accelerations & velocities into changes in position and resolving collisions

    • ...then handle any trigger/collision events that occurred as a result of that integration.

  4. A variable-length Update/LateUpdate runs after the fixed steps & before rendering

    • Here we do any last-minute/display-only updates before the frame is rendered - particularly VFX, animation, and UI changes to reflect the most recent simulation state.
  5. The frame is rendered and presented

In a situation like this, my usual solution is this:

  1. Introduce an EarlyUpdate method that runs once for each display frame, after the input check and before any of the fixed steps (if any) and any of the variable-length updates. (In Unity when not using the scriptable game loop, I emulate this behaviour)

    Here, we process any instantaneous input events like button pressed / released - applying an upward impulse the moment the jump button is pressed is one common example. These are inputs that happen only for a moment: the button transitions from up to down in one specific frame. It doesn't keep happening on future frames, unless I release the button and press it again, in which case it's a new press.

    This way we ensure these events are processed exactly once - so we don't miss inputs on frames that get no fixed updates, or double-process inputs on frames that get multiple fixed updates.

    By handling this before the fixed steps, we minimize the perceived latency. In the example of a jump input, the outcome might be setting an upward velocity on the player. So doing this before the fixed steps gives the physics engine a chance to integrate this velocity change into a position change the player can observe when we render the frame. Even on frames where we get no fixed step, we can render a particle puff or change in the character's animation frame to give the player immediate feedback that their input was heard.

    By processing this at the very beginning of the frame, rather than interleaved between fixed updates according to the timestamp on each input event, we're being maximally charitable to the player, potentially shaving fractions of a frame off their reaction time, for the sake of making the controls feel as tight & responsive as we can.

  2. Process any continuous inputs in each FixedUpdate step. These are things like button holds (including holding a jump for extra upward force after the initial launch impulse), or holding a trigger / analog stick at a particular angle.

    Here the input state is comparatively long-lived (the player will often hold it at a similar value for several display frames), so we don't miss out if we skip it on short frames where we have no fixed steps: we can safely resume handling the hold on the next frame's fixed step. And we can safely handle the same continuous value multiple times on long frames where we have multiple fixed steps - it's no worse than the player holding the trigger one fixed step longer.

    By handling these in our fixed step, we ensure the simulation behaviour does not depend on the player's display framerate. I'll move at the same speed, and fall in the same arc, at 30, 60, or 120 fps. The display framerate just determines the sampling frequency of new hold values. As long as we're rendering at 30 fps+, this will be enough temporal resolution to pull off even fairly intricate analog control inputs sequences. (Keeping in mind that human reaction time is ~5x longer than a display frame even at a low of 30 fps)

This is an approximation, of course. Arguably, we could be more "accurate" by repeatedly checking for input, time-stamping each input event, and processing the inputs in a queue within the fixed step to which each one logically belongs. The upshot of doing that though is most often going to be delaying a user's input further (since it may well have arrived while we were doing rendering work for this frame, after our next fixed step logically ended, and will now be delayed until our second or third fixed step of the next frame instead of the first) and complexifying our code, for little to no benefit to the perception of accurate sequencing of events. So I think it's a simplification worth making.

\$\endgroup\$
  • \$\begingroup\$ Wow, this is exactly the kind of answer I was looking for. I'll hold off accepting for a bit to give other people a chance. I'm confused by what you mean by instantaneous input events. Is the differentiation between instant vs continuous inputs if the state changed to and from in the same frame vs only constant/toggled once? If some one holds down the jump button through out a frame, and your game makes it so if some one holds it down they continually jump, would that be instantaneous or continuous? \$\endgroup\$ – whn Aug 1 '19 at 1:20
  • \$\begingroup\$ Also do you separate player movement from collision checks? It sounds like you do (which makes sense to lower perceived input delay), via handling instantaneous events outside of the fixed physics update. So you would have the player view update instantaneously, but not obey physics until the physics update, which would look at the last position, and the current position, calculate a velocity impulse, then calculate collision based upon that? \$\endgroup\$ – whn Aug 1 '19 at 1:26
  • \$\begingroup\$ I've edited the answer to clarify. In the jump example, the moment the jump button goes from up to down is an instantaneous input. We react to that by applying an upward impulse. The duration the jump button remains held is a continuous input. We might apply a lower gravity to the character in each fixed step that the button is held after the jump, up to a maximum hold duration. This way, the possible jump arcs are quantized by the fixed time step, and I can't get a more precise jump by running at a higher display framerate, keeping the game fair across devices. \$\endgroup\$ – DMGregory Aug 1 '19 at 2:40
  • \$\begingroup\$ With regard to physics, the model I'm outlining works a bit like this: (Early Update: apply jump impulse)--[fixed loop start]-->(Fixed Update: apply reduced gravity acceleration)->(Physics Step: integrate accelerations & velocities into position changes)->(Collision Resolution: resolve intersections & fire collision messages)->(Collision Handlers: handle collision events)->[go to fixed loop start until caught up to current time]-->(Update / Late Update: tick animations, effects, and UI to reflect the current moment)-->(Render interpolated state) \$\endgroup\$ – DMGregory Aug 1 '19 at 2:45
  • \$\begingroup\$ I have another question. The only instantaneous event you mention is jumping, which I totally understand. As soon as that button goes down, it makes sense you should get an impulse. But what about the player just pressing forward? There isn't an impulse there. Are these actions categorically considered continuous? Despite the fact that you could press and release in consecutive frames? \$\endgroup\$ – whn Oct 10 '19 at 0:19
-2
\$\begingroup\$

While Fixing your time step, you should Free your draw step so that positions are interpolated between logic frames. Input runs at the logic rate, and drawing runs at the refresh rate. Separate drawing and logic. You can have an event queue for two timers that can be set to the refresh and logic rate then monitor it for events. If logic or rendering takes too long, skip visual frames as necessary, or force game time to slow down.

Edit3 What I was getting at was that there's nothing special you need to do to handle this other than handle events in the order they occur.

Run your logic timer at whatever rate you want within reason. This is when you update the game state based on dV or dA or however you want to integrate. With overlap based collision this is also when you run collision detection. Now the difficulty I believe you were having was with "when to apply input based state changes". I believe you were questioning whether to aggregate input per logic update. Logic usually runs fast enough that you don't get more than one input event but in the case of mouse input you might get several move events within a relatively slow logic update. It seems the real question is what to do with the input events in between logic updates. Do you feel it is necessary to apply the elapsed time to them? I have never in practice needed such precision that input events can't just be applied directly and consecutively and take effect on the next logic frame.

Are your logic updates so infrequent or so expensive that this actually caused problems in real life?

\$\endgroup\$
  • \$\begingroup\$ This answer does not appear to have anything to do with my question. \$\endgroup\$ – whn Jul 31 '19 at 20:21
  • \$\begingroup\$ you should be able to comment on your own answer btw. commenting on my post won't work. \$\endgroup\$ – whn Jul 31 '19 at 20:28
  • \$\begingroup\$ Is your question collision resolution or independent logic and frame rates? \$\endgroup\$ – BugSquasher Jul 31 '19 at 20:29
  • \$\begingroup\$ the question is how to correlate player input commands with fixed step physics. Ignore frame rate entirely. I've outlined above the issues with processing them individually (negates the purpose of fixed time-step, not scalable with multiple players), and combining them (if the player provided inputs to navigate a corner, it is possible that they wouldn't actually be able to navigate it) and only taking a single input. There's also the issue of what the player sees, when combining you get a perception of delayed input, and if you combine, how shall interaction be ordered? (like clicking). \$\endgroup\$ – whn Jul 31 '19 at 20:33
  • \$\begingroup\$ The player should 1) not move forward 2) move right and 3) move back \$\endgroup\$ – BugSquasher Jul 31 '19 at 21:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.