Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Fully authoritative physics based movement controller. Client is having trouble moving.

Discussion in 'Netcode for GameObjects' started by ireakhavok, Mar 1, 2023.

  1. ireakhavok


    Feb 9, 2023
    So I have a player network object and camera that get spawned independently and linked inside my start function after spawning.

    on update ( I also tried Fixed update) the client gets all inputs and sends them through an RPC call.

    Originally In the RPC call, the server finds out which client sent the call, grabs the network objects related to that call, and assigns them to method variables of the types rigidbody(player), animator, and transforms (a virtual camera location, and the actual camera). I tried to use network object references and pass them through to the RPC call. This worked the exact same way, but was smoother but this is bad for network security.

    The virtual camera is a location the container of the camera is offset from for different viewing perspectives. An offset is calculated based upon which perspective is selected, and offsets the actual camera from the virtual location, so that when you toggle between the perspectives, the virtual location is used in the calculations for the new offset.

    It works perfectly in every regard to the host. But, when I try to add the client (without client prediction at this point) some weird stuff happens:

    1) There is a really fast rubberbanding effect that happens, that actually keeps the player's location from updating like it should? It's almost as if the player is sending updates too soon, but when I moved into the fixed update method, the issue got even worse and the player couldn't move barely at all.

    2) when I remove the add force step that limits the velocity vector on the rigidbody, the movement is basically equal to what the server does (albeit still kinda rubber bandy), but obviously, this is terrible considering the movement force just increases like a bat outa hell.

    I was at one point trying to not act upon the rigid bodies via the network and just send back the positions, but am 100% just utilizing the network transform, network rigidbody, and network animator field.

    One thing that might be going on is that the serverRPC is being called at a rate such that the player's position in the network doesn't update fast enough, and does the same calculation over and over again.

    Another concern is that I don't like having to grab the references of the rigidbodies, etc from the networkmanager.

    I did try to pass the positions of the player through, have the server run the code on the positions and pass the changes back and then move. (obviously didn't work authoritative, duh)

    Still the same issue with player movement via physics. I know the concept is mostly correct, because I do the same sort of operations with the camera object with it's transform, so that leads me to believe that there is something going on under the hood with the physics that I am not accounting for properly. Also, my code could use with some refactoring as the code is really complex, but because the mixing of different perspectives in the same area of code is really different. I could refactor it, but I don't plan on doing so until the movement issue is fixed.

    Just to clarify: the issue isn't tied to visual jitteriness , it's with actual movement on both the server and client side. If I don't account for visual perception, the player simply jitters from where they should be back and then forward again incredibly fast, with an extremely small positive displacement no matter the direction.

    Any suggestions on what I should do, look at, or try?
    Last edited: Mar 2, 2023
  2. CodeSmile


    Apr 10, 2014
    Didn‘t read all of it but from what I grasped you want each player to move via physics. In that case. make sure to use NetworkRigidbody or at a minimum make each player kinematic.I think the issue you see is when a player tries to move into another rigidbody that should „give way“ this info first travels to the server, where the server simulates physics, which makes the client rigidbodies update their positions, which makes the moving client rubberband because movement and actual collisions are out of sync from the client‘s perspective.

    I believe Fish-Net could fix that as it supports client-side physics prediction, a feature NGO currently does not have.
  3. ireakhavok


    Feb 9, 2023
    Just to clarify: I am doing fully authoritative without prediction until the server business is figured out, so there should be no collisions or movement client side that is causing issues. This is 100% that the physics calculations aren't reaching the client, specifically. I believe the network transform is working, but each frame the velocity is back at 0, as if there's no velocity.

    Doing some more looking, it appears that my rigidbody physics calculations are just not happening for the client. when I step through and look at the rigidbody attached to my client through the inspector window velocity is always 0. No Velocity is visible on the rigidbody from the client side, at all, even though it is "moving" forward. Yes, I do have it as kinematic. It is only interacting with a terrain collider. nothing else. The server performs the movement flawlessly via the exact same methods, and the camera translates perfectly for both client and host. The issue is with the rigidbody. In note to your point of collisions: the collisions may be out of sync on both sides of the connection. Host and client. it's not so much rubberbanding (but it might as well be rubberbanding in the opposite direction). The character trying to move is flickered back into spot of the client. Also, there is no movement calculations client side as I have not turned on any kind of prediction, but do have the code for it.
  4. CodeSmile


    Apr 10, 2014
    There is no physics simulation on the client. Therefore velocity being zero sounds reasonable. The client is just a dumb kinematic body, waiting for the next position update from the server.

    Are you sure you are not just experiencing the round-trip-time lag? Like a client moves forward one step at "global time" 0 ms and the server does the calculations and the client receives those updates maybe 100 ms later - that means one tenth of a second has passed before the client performs the action, which will be noticable.
  5. ireakhavok


    Feb 9, 2023
    I have the server set to 100-144 tick rate. so I don't think it's purely a visual lag do to round trip, also being that the ping is prolly 10ms or less. the max speed of the client char is far less than the server. If I hold down the force, it should eventually reach max speed, but it doesn't. it's like the collisions are causing it to jitter. I've been adjusting it to see what's going on. I might go to like 10 to see if I can see what is going on a little better. I think when I'm in the air (run back on the side of a mountain), it works a little, so it could be an issue with collisions in relation to the rigidbody? Maybe I should try a flat plane and see what happens? Will post again in a few hours.
  6. CodeSmile


    Apr 10, 2014

    You should tone that down to 60 or less. Leave the >60 fps scenarios to interpolation only. You don't want the server to send out this many updates or even calculate this many physics updates per second simply for bandwidth and computational reasons, and because it's just not necessary. Keep in mind that even action games like Quake 3 Arena ran at a 20 Hz server tick rate and modern games will probably do 30-50 ticks, hardly more.

    I wouldn't even be surprised that those high tick rate is what's causing your issues to begin with. o_O
  7. ireakhavok


    Feb 9, 2023
    reducing it to 60 actually made the issue more apparent. Truly, competitive games should start to increase their tick rate because tickrate should follow re-fresh rates of monitors, IMO. if I need to break it up into separate threads for making it separate calls for sake of processing time, I will, but ultimately, 100-140 hz for tick rate on a 4.8 GHZ processor with maybe a cycle load of 200,000 (I should figure this out for sure, lol) is not the issue here- although it could be an issue with the life cycle of the separate stages of calculating and relaying information, but I digress. Changing the tick rate had no positive improvement. Thanks for the suggestion.

    So looking at the network item references... Shouldn't the network object reference from the client be the exact same item as seen from the server's perspective? I know that the client doesn't calculate the values for say velocity, etc, by why wouldn't the client be able to see it's velocity, being that it's fully authoritative. Do I need to revert the sending of the networkobjectreference from the client to the server back to the method where I made the server find the client's objects manually on the server? From what I can see the exact same thing happened where the server calculated everything correctly from what it was given, including what the velocity should be... 6 m/s max when holding forward, but this is not what happens on the client. I don't mind posting some code, I just don't know how to make it look pretty on here
  8. ireakhavok


    Feb 9, 2023
    Code (CSharp):
    1.     void Update()
    2.     {
    3.         if (!IsSpawned) return;
    4.         if (IsClient && IsOwner)
    5.         {
    6.             getInputs();
    7.             if (CameraClient == null)
    8.                 return;
    9.             sendToServerRPC(
    10.                 mouseScrollValueClient,
    11.                 leftAltPressedClient,
    12.                 isRightSideShoulderClient,
    13.                 horizontalInputClient,
    14.                 verticalInputClient,
    15.                 xClient,
    16.                 yClient,
    17.                 playerPerspectiveClient,
    18.                 speedClient,
    19.                 distanceToCameraHolderClient,
    20.                 lastDirectionFacedClient,
    21.                 CameraHolderClientRef,
    22.                 CameraClientRef,
    23.                 transformRef);
    24.             //predictMovement();
    25.         }
    26.     }
    27. //Then the code for the RPC call....
    28. [ServerRpc(RequireOwnership =true)]
    29.     private void sendToServerRPC(
    30.      float mouseScrollValueClientRPC,
    31.      bool leftAltPressedClientRPC,
    32.      bool isRightSideShoulderClientRPC,
    33.      float horizontalInputClientRPC,
    34.      float verticalInputClientRPC,
    35.      float xClientRPC,
    36.      float yClientRPC,
    37.     perspective playerPerspectiveClientRPC,
    38.      float speedClientRPC,
    39.      float distanceToCameraHolderClientRPC,
    40.      Vector3 lastDirectionFacedRPC,
    41.     NetworkObjectReference cameraHolderRef,
    42.     NetworkObjectReference cameraRef,
    43.     NetworkObjectReference transformRef,
    45.     ServerRpcParams serverRpcParams = default)
    46.     {
    47.         //var clientIdRPC = serverRpcParams.Receive.SenderClientId;
    48.         Transform CameraHolderClientRPC;
    49.         Transform CameraClientRPC;
    50.         Animator playerAnimatorClientRPC;
    51.         Rigidbody ClientPlayerRigidbodyRPC;
    53.         NetworkObject CameraHolderObj;
    54.         NetworkObject CameraObj;
    55.         NetworkObject TransformObj;
    57.         cameraHolderRef.TryGet(out CameraHolderObj, NetworkManager.Singleton);
    58.         cameraRef.TryGet(out CameraObj, NetworkManager.Singleton);
    59.         transformRef.TryGet(out TransformObj, NetworkManager.Singleton);
    61.         CameraHolderClientRPC = CameraHolderObj.GetComponent<Transform>();
    62.         CameraClientRPC = CameraObj.GetComponent<Transform>();
    63.         ClientPlayerRigidbodyRPC = TransformObj.GetComponent<Rigidbody>();
    64.         playerAnimatorClientRPC = TransformObj.GetComponent<Animator>();
    66.         //var PlayerObject = NetworkManager.ConnectedClients[clientIdRPC].PlayerObject;
    67.         //var go = PlayerObject.gameObject;
    68.         ////playerTransformRPC = go.transform;
    69.         //var ownedObjects = NetworkManager.ConnectedClients[clientIdRPC].OwnedObjects;
    70.         //foreach (var obj in ownedObjects)
    71.         //{
    72.         //    if (obj.GetComponentInChildren<Camera>() != null)
    73.         //    {
    74.         //        CameraHolderClientRPC = obj.transform;
    75.         //        CameraClientRPC = obj.GetComponentInChildren<Camera>().transform;
    76.         //    }
    77.         //    //else if (obj.GetComponent<Rigidbody>() != null
    78.         //    //    && obj.GetComponent<Animator>() != null)
    79.         //    //{
    80.         //    //    playerTransformRPC = obj.transform;
    81.         //    //}
    82.         //}
    83.         //playerAnimatorClientRPC = go.GetComponent<Animator>();
    84.         //ClientPlayerRigidbodyRPC = go.GetComponent<Rigidbody>();
    86.         Vector3 downVector = new Vector3(ClientPlayerRigidbodyRPC.position.x, ClientPlayerRigidbodyRPC.position.y + playerHeight, ClientPlayerRigidbodyRPC.position.z);
    88.         bool isGroundedRPC = Physics.Raycast(downVector, Vector3.down, playerHeight + .4f, whatIsGround);
    89.         //checkForIsGrounded(playerTransformRPC);
    91.         if (speedClientRPC > maxMovementSpeed)
    92.         {
    93.             speedClientRPC = maxMovementSpeed;
    94.         }
    95.         else if (speedClientRPC < minMovementSpeed)
    96.         {
    97.             speedClientRPC = minMovementSpeed;
    98.         }
    100.         switch (playerPerspectiveClientRPC)
    101.         {
    102.             case perspective.overTheShoulder:
    103.                 overTheShoulder(ClientPlayerRigidbodyRPC,
    104.                              mouseScrollValueClientRPC,
    105.                              leftAltPressedClientRPC,
    106.                              isRightSideShoulderClientRPC,
    107.                              horizontalInputClientRPC,
    108.                              verticalInputClientRPC,
    109.                              xClientRPC,
    110.                              yClientRPC,
    111.                              playerPerspectiveClientRPC,
    112.                              speedClientRPC,
    113.                              distanceToCameraHolderClientRPC,
    114.                              CameraHolderClientRPC,
    115.                              CameraClientRPC,
    116.                              playerAnimatorClientRPC,
    117.                                 isGroundedRPC);
    118.                 break;
    119.         }
    121.     }
    122. // Then the code for one of these methods, over the shoulder, currently.
    123. private void overTheShoulder(Rigidbody ClientPlayerRigidbodyRPC,
    124.          float mouseScrollValueClientRPC,
    125.          bool leftAltPressedClientRPC,
    126.          bool isRightSideShoulderClientRPC,
    127.          float horizontalInputClientRPC,
    128.          float verticalInputClientRPC,
    129.          float xClientRPC,
    130.          float yClientRPC,
    131.          perspective playerPerspectiveClientRPC,
    132.          float speedClientRPC,
    133.          float distanceToCameraHolderClientRPC,
    134.          Transform CameraHolderClientRPC,
    135.          Transform CameraClientRPC,
    136.          Animator playerAnimatorClientRPC,
    137.          bool isGroundedRPC)
    138.     {
    139.         CameraHolderClientRPC.position = ClientPlayerRigidbodyRPC.position - (distanceToCameraHolderClientRPC * ClientPlayerRigidbodyRPC.transform.forward);
    140.         Vector3 VectorFromPlayerToTrueCameraLocation = CameraHolderClientRPC.position - ClientPlayerRigidbodyRPC.position;
    142.         Quaternion CurrentRotationQuaternion = Quaternion.Euler(0, xClientRPC, 0);
    143.         Vector3 movement = (ClientPlayerRigidbodyRPC.transform.forward * verticalInputClientRPC + ClientPlayerRigidbodyRPC.transform.right * horizontalInputClientRPC);
    144.         movement.y = 0;
    146.         if (isGroundedRPC)
    147.         {
    148.             ClientPlayerRigidbodyRPC.AddForce(10f * ClientPlayerRigidbodyRPC.mass * speedClientRPC * movement.normalized , ForceMode.Force);
    150.         }
    151.         else
    152.         {
    153.             ClientPlayerRigidbodyRPC.AddForce(.2f * ClientPlayerRigidbodyRPC.mass * speedClientRPC * movement.normalized, ForceMode.Force);
    155.         }
    156.         ClientPlayerRigidbodyRPC.transform.rotation = CurrentRotationQuaternion;
    157.         playerAnimatorClientRPC.SetFloat("MovementSpeed", ClientPlayerRigidbodyRPC.velocity.magnitude * 10);
    158.         Quaternion targetRotation = Quaternion.Euler(0, 0, 0);
    160.         if (movement.magnitude > 0)
    161.         {
    162.             targetRotation = Quaternion.LookRotation(movement.normalized);
    163.         }
    165.         float angleDifferenceFromCharacterToCamera = 0f;
    166.         angleDifferenceFromCharacterToCamera = GetSignedAngle(targetRotation, CurrentRotationQuaternion, new Vector3(0, 1, 0));
    168.         //angleDifferenceFromCharacterToCamera = Quaternion.Angle(targetRotation, rotation);
    169.         if ((angleDifferenceFromCharacterToCamera != 0) & (movement.magnitude != 0))
    170.             playerAnimatorClientRPC.SetFloat("AngleDifference", angleDifferenceFromCharacterToCamera);
    171.         else
    172.             playerAnimatorClientRPC.SetFloat("AngleDifference", 0);
    174.         // Calculate the camera offset based on the current distance
    175.         float xDistance = CameraHolderClientRPC.position.x - CameraHolderClientRPC.position.x;
    176.         float yDistance = CameraHolderClientRPC.position.y - CameraHolderClientRPC.position.y;
    177.         float currentDistance = Mathf.Sqrt(xDistance * xDistance + yDistance * yDistance);
    178.         float tValue = Mathf.InverseLerp(distanceMin, distanceMax, currentDistance);
    179.         float distanceFactor = Mathf.Lerp(1.0f, 0.5f, tValue);
    180.         float clampAngle = Mathf.Lerp(85.0f, 45f, tValue) * distanceFactor;
    181.         // Limit the y-angle to the clamp angle
    182.         yClientRPC = Mathf.Clamp(yClientRPC, -clampAngle, clampAngle);
    183.         CameraHolderClientRPC.position = ClientPlayerRigidbodyRPC.position + VectorFromPlayerToTrueCameraLocation;
    184.         Quaternion cameraRotation = CurrentRotationQuaternion;
    185.         CameraHolderClientRPC.rotation = cameraRotation * Quaternion.Euler(-yClientRPC, 0, 0);
    187.         //Quaternion upDownAngle = cameraRotation * Quaternion.Euler(-yServer, 0, 0);
    188.         //camController.rotationOffset = cameraRotation * Quaternion.Euler(-yClientRPC, 0, 0);
    189.         CameraClientRPC.rotation = cameraRotation * Quaternion.Euler(-yClientRPC, 0, 0);
    191.         float rightShift = isRightSideShoulderClientRPC ? shoulderShiftAmount : -shoulderShiftAmount;
    193.         float tiltAngle = Mathf.Deg2Rad * (yClientRPC);
    195.         float zDistance = CameraClientRPC.position.z - ClientPlayerRigidbodyRPC.position.z;
    196.         float flyingDistance = Mathf.Sqrt(xDistance * xDistance + yDistance * yDistance + zDistance * zDistance);
    197.         float extra_height = -((float)(flyingDistance * Mathf.Tan(tiltAngle)));
    199.         //float closer = -((float)(Mathf.Cos(tiltAngle) * flyingDistance));
    201.         //// Project the closer value onto the game's x-y plane
    202.         //Vector3 forward = CameraClientRPC.transform.forward;
    203.         //forward.y = 0;
    204.         //Vector3 right = CameraClientRPC.transform.right;
    206.         //if (!isRightSideShoulderClientRPC)
    207.         //{
    208.         //    right = -CameraClientRPC.right;
    209.         //}
    211.         //right.y = 0;
    212.         //Vector3 projection = closer * forward + 1 * right;
    214.         //// Split the projection into x and y components
    215.         //float xProjection = projection.x;
    216.         //float yProjection = projection.z;
    218.         Vector3 rightVector = ClientPlayerRigidbodyRPC.transform.right * rightShift;
    219.         var totalChange = new Vector3(CameraHolderClientRPC.position.x,// + xProjection,
    220.             CameraHolderClientRPC.position.y + (float)(playerHeight + extra_height),
    221.             CameraHolderClientRPC.position.z);// + yProjection);
    222.         CameraClientRPC.position = totalChange + rightVector;
    223.         //if (isGroundedRPC)
    224.         //{
    225.         //    if (ClientPlayerRigidbodyRPC.velocity.magnitude > maxMovementSpeed)
    226.         //    {
    227.         //        ClientPlayerRigidbodyRPC.AddForce((speedClientRPC * ClientPlayerRigidbodyRPC.velocity.normalized)
    228.         //            - ClientPlayerRigidbodyRPC.velocity
    229.         //            , ForceMode.VelocityChange);
    230.         //    }
    231.         //}
    233.         Vector3 flatVel = new(ClientPlayerRigidbodyRPC.velocity.x, 0f, ClientPlayerRigidbodyRPC.velocity.z);
    234.         if (isGroundedRPC)
    235.         {
    236.             if (flatVel.magnitude > speedClientRPC)
    237.             {
    238.                 Vector3 limitedVel = flatVel.normalized * speedClientRPC;
    239.                 ClientPlayerRigidbodyRPC.velocity = limitedVel + new Vector3(0,ClientPlayerRigidbodyRPC.velocity.y,0);
    240.             }
    241.         }
    243.             //receiveClientRPC(
    244.             //    ClientPlayerRigidbodyRPC.velocity,
    245.             //    ClientPlayerRigidbodyRPC.position,
    246.             //     mouseScrollValueClientRPC,
    247.             //     leftAltPressedClientRPC,
    248.             //     isRightSideShoulderClientRPC,
    249.             //     horizontalInputClientRPC,
    250.             //     verticalInputClientRPC,
    251.             //     xClientRPC,
    252.             //     yClientRPC,
    253.             //     playerPerspectiveClientRPC,
    254.             //     speedClientRPC,
    255.             //     distanceToCameraHolderClientRPC,
    256.             //     CameraHolderClientRPC.position,
    257.             //     CameraHolderClientRPC.rotation,
    258.             //     CameraClientRPC.position,
    259.             //     CameraClientRPC.rotation,
    260.             //     playerTransformRPC.position,
    261.             //     playerTransformRPC.rotation,
    262.             //    playerTransformRPC.forward);
    264.         }
  9. ireakhavok


    Feb 9, 2023
    I will probably be reverting some of the commented out code and make the server grab the network objects based on the sender, because this method could allow for cheaters to change the position of other rigidbodies, etc.
  10. CodeSmile


    Apr 10, 2014
    Have you read this article?

    Specifically check the last paragraph.

    And this is also instructive:

    Nearly the only popular game that features >60 Hz tick rates is CSGO and they have about 20 years worth of experience and very simplistic gameplay (from a network traffic perspective and when it comes to networked physics).

    I believe the velocity of the client body is 0 because it is not transferred from the server because it is not needed. The server needs the velocity to calculate the next physics frame, the client does not since it does not perform physics simulation. That means everything that moves gets its position sent from the server, and the more physics bodies you have in the game times tick rate will double the network bandwidth if you double the tick rate. Network congestion is a real issue, eventually you‘ll hit a limit. Did you monitor in and outgoing traffic? You can quickly go above a reasonable upstream limit, especially if you plan for players to be able to host an internet game through their own upstream-limited Internet connection.
  11. CodeSmile


    Apr 10, 2014
    You could post a video with both host and client screens visible at the same time. It may help to narrow down reasons for a particular rubberbanding just by looking at it.
  12. ireakhavok


    Feb 9, 2023
    Just so you know I am using 30 as the ticks, with no change. I will post a video at 30hz and at 100hz, later. I hear you about needing to look at latency, but I'm sure that it's not the issue. I'm sure that I'm just doing something wrong either by using the wrong method, or just not understanding something in the life cycle of the game.

    Just to check that the processing time is short - Inside the server - Using stopwatch.diagnostics the elapsed time is 0ms. looking further - to be more specific 400 stopwatch ticks, so .00004 s (9/10 of the time) and one was .0005 s : so I am not holding up because of processing time on the server.

    looking at my networking, It wouldn't even register on network diagnostics. Just sits at 0 Mbps. There is literally only two rigidbodies - the two characters.

    I feel like there is an issue with handling the rigidbody as a collider to the client and back to the server. If I remove all collisions, say like, by move backwards from the terrain (I stop colliding) the character movement is smooth. Will post a video of this later.

    Network Rigidbody, Network Transform, Rigidbody, Transform, Animator, network animator, network object, capsule collider, and my movement script are on the character. Will post later, in about 7 hours. I have work to go to. Thanks for helping me, hopefully we can figure it out. been stuck for a week on this.
  13. ireakhavok


    Feb 9, 2023
    video of the issue:

    NetSettings.png playerImg.png
  14. ireakhavok


    Feb 9, 2023
    Just noticed there was two network rigidbodies. removed the second. It still functioned the same.
  15. ireakhavok


    Feb 9, 2023
    BTW while I'm debugging and in the middle of the code that determines the positions, and forces, etc. the isKinematic flag is false. I have no idea why this would be or if that is expected. I have so many questions...
  16. ireakhavok


    Feb 9, 2023
    yeah so I also found that even with client prediction and server reconciliation the collisions cause the interactions to become non-deterministic. It seems that every one of my problems, from the issue I'm describing above, and the client-server reconciliation has to do with collisions.

    this is going to be an issue with me trying to create a game in unity that is physics / force based and multiplayer.
  17. ireakhavok


    Feb 9, 2023
    I can't move correctly with server authoritative movement and collisions (without client prediction or client movement.)

    Also when I do implement client side prediction and server reconciliation its just getting far too bogged down because it's having to reconcile every frame after a collision.

    So in order to fix the first issue which may be plaguing the first, I removed all the client side prediction stuff and am just focusing on server controlling every aspect of movement, with just commands being sent to the server. This is where I'm stuck and don't know how to proceed.
  18. ireakhavok


    Feb 9, 2023
    So the client is still having moving troubles, but I improved the visual stutters by moving to a queueing system where the client sends the data to a queue on the server, then during the fixed update, it removes each from the queue, performs the operation from the queue. I may try to make it faster by sorting by the client, combining the stack from that client, etc.

    I have to look up how vsync is handled, now, too. Weird stuff happening on my alt monitors
    Last edited: Mar 4, 2023