Movement in ExecuteCommand is jittery / jerky

Options
Hello.

I have an object which I move using the .Move() function on an attached character controller.

I have an authoritative setup with client-side prediction so I am using the SimulateController() and ExecuteCommand() methods.

Alright, so the problem is I dont see how I am able to get any smooth movements when moving the object inside a function which is not called in a framerate-dependent manner.

I could turn the simulation-rate up from 60 to 120, but that wouldnt eliminate the problem, only makes it less noticeable at an unnecessary high cost.

It is the same as trying to move an object inside FixedUpdate() with transform.translate() - Its never going to be as smooth as inside the regular Update() with the help of Time.deltatime.

Am I missing some basic understanding? Or how can I acheive smooth, framerate-dependent movement inside ExecuteCommand?

Comments

  • T_Schm
    Options
    Hey Sympton,

    the approach of moving an entity inside the FixedUpdate() or with a fixed timestep of 60 fps is pretty common and gives you in general smooth movement. Note that the SimulateController() is called inside the FixedUpdate() and therefore at a rate of 60 calls/sec.

    To replicate this movement to another client is a different thing though and has to do with the network rate. By default you send every 3 frames a package meaning you will send 20 packets/sec. This results in stuttering movement on the client side. For this, there is interpolation.

    So in order to have a smooth movement you simulate the movement between the packetupdates. This option is avaiable inside the bolt state under "Smoothing Algorithm". When using the option "Interpolation" Bolt will automatically interpolate the position between the package updates.

    So every 3 frames there is a new valid update of the position. During the other 2 frames where there is no new information the entity will still move due to interpolation.

    This is the basic approach of doing network replication and how BOLT works. You can read further information about this in the tutorial section: http://doc.photonengine.com/en/bolt/current/getting-started/bolt-101-getting-started

    Note: You will not have to manage the interpolation yourself, but BOLT does. So do not try to interpolate inside the ExecuteCommand(), just handle this data as standard input data. Again, the tutorial explains how to do it.

    HTH,
    T_Schm
  • Symptom
    Options
    Hey T_Schm and thanks for the answer.

    The problem is actually not really network-related. The problem (also) occurs on the server and I get the same problems when trying to translate a transform inside FixedUpdate.

    You are right about getting generally getting smooth movement at 60 calls per sec, but this is assuming the fps is also steady at around 60.

    If vSync is enabled or the target frame rate is set to 60, the problem is miniscule and is probably why I havent had a problem before.

    Turning vSync off and having, lets say, 120 fps while simulating 60 times/sec will leave you with a character that only moves every second frame, which unfortunately is quite noticeable.

    Anyway, a solution is to force vSync on, which is fine I guess. I was just wondering if there was a common solution, like interpolating between the fixed calls each frame or something else.
  • T_Schm
    Options
    Symptom said:

    You are right about getting generally getting smooth movement at 60 calls per sec, but this is assuming the fps is also steady at around 60.

    This is not how the FixedUpdate() is supposed to work. Are you doing a time-based movement or a frame-based movement? Meaning are your movement values multiplied by "Time.deltaTime" or do you just do sth like "Move(transform.forward)" in each call of FixedUpdate()? I hope and think you use time-based movement.
    Symptom said:

    Turning vSync off and having, lets say, 120 fps while simulating 60 times/sec will leave you with a character that only moves every second frame, which unfortunately is quite noticeable.

    If the entity does still move with 60fps you should in nearly no case see a stuttering movement? How can you tell it moves every 2 frames. I mean 120 fps is really fast, so I do not think a human eye can detect the difference between a position update every 0.01667 sec (60 fps) or every 0.00833 sec (120 fps).

    In fact, the unit moves the same way (and as often as) it does when having a steady 60 fps. Just other parts might get a quicker refresh in action.

    I think you either have some issues with frame-based movement or pretty sharp eyes. ;)
  • Symptom
    Options
    Multiplying with Time.deltaTime inside FixedUpdate() or ExecuteCommand() at 60 calls/sec would be exactly the same as just multiplying with 0.0166...7

    See this web-build as an example
    http://s3.amazonaws.com/picobots/assets/unity/jerky-motion/JerkyMotion.html

    You can clearly see jerky movement.

    Or see this simple project
    http://s3.amazonaws.com/picobots/assets/unity/jerky-motion/JerkyMotion.zip

    Changing the script from using Update() to FixedUpdate() will cause jerky movement.
    You will unavoidably create stutter when doing transformations in FixedUpdate(): FixedUpdate is framerate independent, and therefore completely unrelated to your framerate. The stutter will be most visible the closer your framerate comes to the actual physics/fixed time step, because it will create an "interference pattern" between the different call intervals - the one being a fixed time interval, the other being a not-so-fixed number-of-frames interval, which might well mean that there are two or three consecutive frames rendered without any FixedUpdate() called inbetween, even with VSync enabled, or sometimes two consecutive FixedUpdate() calls without a call to Update(), if there was some reason that rendering got delayed.
    Which is taken from here
    http://answers.unity3d.com/questions/275016/updatefixedupdate-motion-stutter-not-another-novic.html
  • Symptom
    Symptom
    edited July 2015
    Options
    I made a desktop-build which shows the difference between movement Update() and FixedUpdate()

    here

    http://www.filedropper.com/jerkymovement

    or

    https://drive.google.com/file/d/0B9yeX0ntDdcobnJCYUV1VVFWQXM/view?usp=sharing

    and/or watch it here

    https://www.youtube.com/watch?v=7Dpd1r7Y7r0&feature=youtu.be

    The only difference between is that the top row is using FixedUpdate() and bottom row is using Update()
  • ashoulson
    ashoulson
    edited July 2015
    Options
    You need to perform your Move() on the FixedUpdate() timeframe (regular ticks at 50Hz), and then smooth the position of the visible body on the Update() timeframe (framerate-dependent). This is usually accomplished with two gameObjects. One is invisible and contains your controller, and the other is visible (contains your model) but doesn't have any colliders or physics logic.

    On your invisible "pawn" object:
    - Every FixedUpdate, move the Pawn with the Move() command using Time.fixedDeltaTime (or the Bolt equivalent).
    - Store your last position, and the new position you just moved to.

    On your visible object (I call these "fixtures", but this is a bad name):
    - Every Update, fetch the Pawn's current position (P_c) and last position (P_l).
    - Compute the following: t = Time.time - Time.fixedTime (i.e. how long it has been since your last movement update).
    - Set the position of your fixture to Vector3.Lerp(P_l, P_c, t) -- that is, interpolate between the last fixedUpdate position and the current fixedUpdate position according to where you are in the middle of the two fixedUpdate ticks. Use Slerp for Quaternions.

    That way you have reliable physics and smooth visible movement. In Bolt, this is handled for you on proxies. If your state contains a Transform, when you call SetTransform, you have the option for a "simulate" and "render" transform. Set the first to your pawn and the second to your fixture.
  • Symptom
    Symptom
    edited July 2015
    Options
    This is what I have been looking for. Thank you so much! Cant wait to implement this.
    This should be implemented in the example-project :)
  • Leoz
    Options
    I experienced a similar problem and solved it by adjusting the fixed update rate to the monitor's refresh rate. I did this manually until now, but I'm going to write a short script using Time.fixedDeltaTime and Screen.currentResolution.refreshRate.

    Implementing interpolation in combination with this seems like the best solution.
    This should be implemented in the example-project
    This should be implemented in all of Unity's examples... :/
  • Symptom
    Options
    @Leoz Well, its not really an issue with Unity, as you can just use Update() instead of FixedUpdate(), if you arent using Bolt. I actually ment that this should be implemented in an example-project in Bolt :smile:
  • Leoz
    Options
    @Symptom All controllers (at least the ones using rigidbodies) in the unity examples suffer from this issue. You probably won't notice if you haven't used 120/144Hz monitors for a bit.
    As a competitive CSGO player I'm probably a little bit spoiled, but there is no reason not to fix the issue if it's quite easy to do so.
  • Symptom
    Options
    @Leoz Well rigidbodies can already be interpolated by default in unity, with no need to do it manually.

    http://docs.unity3d.com/ScriptReference/Rigidbody-interpolation.html