Authoritative tutorial not working for fixed movement distance

Options
I created the authoritative server controls like in the tutorial, and everything worked fine until I modified it to my game, which is grid based, so the movement in one frame is with a fixed distance to another tile.

Everything works well on the server, but on the client, the player moves many tiles too far and then gets reset to the correct position by server. It worked properly on continuous movement because it was hard to spot (the player moved a little faster than he should, that is it).

Upon further investigation the problem is with ExecuteCommand() run on the clients PlayerController.cs, it takes place 4-8 times in consecutive frames for a single run of SimulateController() with actual input.

So in PollKeys() I actually track only Input.GetKeyUp (so it is only true in one frame), PollKeys() in SimulateController() is run only once, SimulateController() is run only once, and I get 4 to 8 consecutive ExecuteCommand() calls on the client (so not in single frame, one frame after another).

What can I be doing wrong? Thank you in advance for any help.

Comments

  • stanchion
    Options
    How do commands work?


    First, let's break down how SimulateController/ExecuteCommand works. SimulateController is only invoked on the host which is in control of the entity, this can be both the owner (if it calls TakeControl() for itself) or a proxy (if the owner calls GiveControl(connection)).

    SimulateController is used for collecting input state from your game and putting it into a command, and other tasks specific to the controller. SimulateController executes one time per frame only.

    ExecuteCommand runs on both the owner and the controller of an entity, so if you have a player character which the server spawns and then gives control of it to a client, ExecuteCommand will run on both the server and the controlling client. It will not run on any other clients.

    The next question usually is: so how does the input/state work on the command, and what does resetState do?

    So, first of - input is very obvious, this is set on the command in SimulateController and polls the local state of whatever input scheme you are using for that specific frame. When you call QueueCommand(cmd); the command is scheduled both for local execute on the client and is sent to the server for remote execution. This is what lets Bolt do client side prediction: the command will execute on both the server and client.

    The state on the command, is the resulting state movement state of executing the input, this is why you copy the state of whatever character motor you are using to the command.

    When the server executes a command, it will send the State of the command back to the client which created the command, and override the state of that specific command on the client with its own correct state.

    So how does resetState fit into all of this? resetState asks you to reset the state of the character motor to the state of the command passed in when resetState is true. This will only happen on remote controlling clients, never on the server. This happens once at the beginning of every frame, and the command which is passed in is the command which has received its correct state from the server.

    After the command with resetState has execute, Bolt will execute *all* other commands again on the client, to "catch up" to the current state. This happens every frame.
  • Thank you for an extensive explanation on the flow of things, but that still doesn't explain what is happening. Let me break it down:

    - On the client I press a button and then I release it (let's say it's in frame number 200 and the button is 'w' responsible for forward movement)
    - On the client SimulateController() is called in frame number 200 with the information about the released button
    - ExecuteCommand() is called on the Client in the frame 200 with the information that a button has been released

    - In frames 201, 202, 203, 205 the same single button release is being received in ExecuteCommand(), so I have like 5 movements forward instead of one. the server resets the position to the proper one, but I still see jittering on the camera (every frame moving forward and then reset for like 5 frames).

    I double checked, and I'm sure it's not a problem of multiple enqueued inputs, the positive button release is enqueued only once in SimulateController(), I have no idea why it comes 5-8 times to ExecuteCommand()
  • stanchion
    Options
    That is how commands work. If you want it to be executed once then check Command.IsFirstExecution
  • Could you please elaborate? I mean I want to understand WHY does ExecuteCommand is runned couple of times with the same information on the client?

    Either way thank you for the information, it's good to know that it works as expected.
  • DirtyHippy
    Options
    There is either one of two things happening. Make sure you are not queuing the same input over multiple frames in SimulateController. I would put a log for each simulate controller call and see what you are actually queueing for that frame. This is a common error that almost everyone does because they do not understand how input works in unity and how fixedupdate/update work.

    The other thing to note is explained in detail in stanchions post. Each tick on the client, the client resets his state to the server state and then replays all locally cached inputs from that point forward. If the client's predicted state doesn't match the server's state for that frame, the server's state that is return will reset you to a different position and then your end position will be different, resulting in a "hitch" as you are essentially corrected. In an ideal world your client and server simulations are exactly the same, and corrections are rare and generally introduced by something not predictable on the client.

    For example, if on a client you delete a wall collider, but on the server it is still there, the client will predict he can move forward. His resulting state on that frame will be past the point of the wall collision. However, the server will return a state for that frame that is right before the wall collision because on the server you stopped when you hit the wall. What you will see is your predicted client moving forward and then reset each frame, so it will look like your character is vibrating back and forth. This is expected behaviour in this case. This is why mucking with the client on a server auth environment isn't useful - because the final simulation is really running on the server, and your client is simply predicting to give you a clean user experience.