Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Extrapolation of rigidbody movement at high speeds

Discussion in 'Multiplayer' started by Karlthegus, May 2, 2016.

  1. Karlthegus

    Karlthegus

    Joined:
    Feb 25, 2014
    Posts:
    13
    Hi everyone,

    We're making a multiplayer (2-4 players) car game where the goal of the game is to crash into the player with the money, so that you get the money, and then you try to escape from the other players.

    Here's an image to give you an idea



    It's a game where speed is pretty high, and precision has to be as good as possible. So so far we've been doing movement on the client, and then doing reconciliation and extrapolation on the other clients, we've tried a few methods of doing this extrapolation, and it seems no matter what we do either the precision is way off or it looks like it's lagging constantly because of the frequency of network updates.

    This is the code we're using right now, I've added two versions of how we've tried to interpolate to the target position, none of them work as well as we'd hope. The network tick has been tried at both 0.333 seconds, 0.1 seconds and 0 seconds. We also do extrapolation and interpolation of rotation, but I thought I'd leave that out for now as it seems to be working fine.

    This method is called every fixedUpdate, the results are sent from the remote client as a command to the server, and then as a rpc to all the clients.

    Code (CSharp):
    1. public virtual void MoveRemote()
    2.     {
    3.         //Set initial vars
    4.         var targetPos = carNetworkManager.results.position;
    5.         var targetRot = carNetworkManager.results.rotation;
    6.         var targetVelocity = carNetworkManager.results.velocity;
    7.         var targetAngularVelocity = carNetworkManager.results.angularVelocity;
    8.  
    9.         // We send the commands at a set rate, so we assume we receive them at a set rate, instead of measuring the delay
    10.         tickRate = GlobalVars.networkTickRate;
    11.  
    12.         //If we don't have a new set of results in time, we start extrapolating from our current position
    13.         if (elapsedTime >= tickRate && carNetworkManager.resultsUsed)
    14.         {
    15.             carNetworkManager.results.position = rigidB.position;
    16.             carNetworkManager.results.rotation = rigidB.rotation;
    17.             carNetworkManager.results.velocity = rigidB.velocity;
    18.             carNetworkManager.results.angularVelocity = rigidB.angularVelocity;
    19.             carNetworkManager.resultsUsed = false;
    20.         }
    21.         //If we have a new set of results, reset the extrapolation
    22.         if (!carNetworkManager.resultsUsed)
    23.         {
    24.             initialPos = rigidB.position;
    25.             initialVelocity = rigidB.velocity;
    26.             elapsedTime = 0;
    27.             carNetworkManager.resultsUsed = true;
    28.         }
    29.  
    30.         elapsedTime += Time.fixedDeltaTime;
    31.        
    32.  
    33.         targetVelocity = Vector3.Lerp(initialVelocity, carNetworkManager.results.velocity, elapsedTime / tickRate);
    34.         var projectedPos = initialPos + targetVelocity * elapsedTime + 0.5f * carNetworkManager.results.acceleration * (float)Math.Pow(elapsedTime, 2);
    35.         targetPos = carNetworkManager.results.position + carNetworkManager.results.velocity * elapsedTime + 0.5f * carNetworkManager.results.acceleration * (float)Math.Pow(elapsedTime, 2);
    36.         rigidB.velocity = targetVelocity;
    37.  
    38.  
    39.         var PosDistance = Vector3.Distance(rigidB.position, targetPos);
    40.         var RotDistance = Quaternion.Angle(rigidB.rotation, targetRot);
    41.  
    42.         //Version A:
    43.         //Lerp between projected position of the local car, and the remote car based on the elapsed time between (assumed) updates
    44.         rigidB.position = Vector3.Lerp(projectedPos, targetPos, elapsedTime / tickRate);
    45.  
    46.         //Version B:
    47.         //Move towards the target position based on distanca and a set speed
    48.         rigidB.position = Vector3.MoveTowards(rigidB.position, carNetworkManager.results.position, Mathf.Exp(PosDistance * updateMoveSpeed));
    49.  
    50.         //Add the inputs
    51.         Move(carNetworkManager.inputs);
    52.         /*
    53.         The above method does this:
    54.         foreach (int i in carInfo.drivingWheelIndices)
    55.         {
    56.             carInfo.wheelColliders[i].motorTorque = input.rightTriggerValue - input.leftTriggerValue * carInfo.motorMultiplier;
    57.         }
    58.         */
    59. }
    Here is an example of the Version B with the updateMoveSpeed set relatively low (1). The red car represents the extrapolated position, and the blue is the results position received from the network. The movement is smooth and nice, but the position is way off. Increase the updateMoveSpeed and you get the opposite problem, especially at high speeds.



    Is there anything you can see immediately that we're doing all out of whack? Any other methods you could recommend us to try? Should we try other network frameworks like Photon? Any and all answers are appreciated! :)
     
    Chom1czek likes this.
  2. ristophonics

    ristophonics

    Joined:
    May 23, 2014
    Posts:
    32
    Nothing yet? Having similar issues with UNET and HLAPI here https://answers.unity.com/questions/1533782/using-unet-and-the-high-level-api-what-is-the-best.html

    In one of the links Anton describes his extrapolation method for Warthunder but specifically mentions how tricky it is to calculate player collisions. Check it out here : https://warthunder.com/en/devblog/current/898

    Love to know if you make any progress and would be happy to collaborate on a warthunder style networked collision detection.
     
  3. Karlthegus

    Karlthegus

    Joined:
    Feb 25, 2014
    Posts:
    13
    So it's been a while since we've worked on this, but a trick we ended up using (And that they mention in the article you mentioned) is to introduce a 100ms input lag. That way we can compensate for up to 100ms delay between the clients. Obviously not a perfect solution for all kinds of games, but in our example it isn't very noticeable because you kind of just feel like the car is a little heavier.
    A bigger problem for us is that the physics is non-determinative (which I believe I read that Unity is working on? But I'm not sure) so explosions that throw the cars around can be especially jarring.

    We're in a place where what we have is acceptable right now, but it's something we're going to come back to in the future.
     
  4. Jos-Yule

    Jos-Yule

    Joined:
    Sep 17, 2012
    Posts:
    292
    There is a really good GDC talk by the RocketLeague folks about how they implemented their networking stuff, worth watching if you have access. For our multiplayer online car game (AutoAge, on Steam), we decided that, for collisions specifically, we would just "detach" all the involved non-local cars from their in-coming network position updates, and let the local physics system deal with the collision. So the local player gets to see a really good looking collision. We would then LERP over a short period post-collision back to the incoming network-ed positions for the non-local cars. Since the collision would probably be behind the local player by this point, they would not see the LERP, and so it would still look good. We actually have a mode in the game very similar to what you are talking about, where everyone chases one play that has the TOKEN, and however can carry the TOKEN for X time wins the round. You pickup the TOKEN by destroying whoever currently is carrying it.
    Good luck with your game!!