Search Unity

Success! Smooth Physics Rigidbody Transform Syncing Using UNET

Discussion in 'UNet' started by Zullar, Feb 24, 2017.

  1. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    So after a lot of work I got a smooth physics based network transform working using UNET and I thought I'd share.



    What I did
    -Used MessageBase to send all information (No NetworkTransform or SyncVars)
    -All physics calcs are performed locally on each client in-between network updates.
    -The client "owns" it's own players motion, and the server owns everything else. It's not cheat proof.
    -If owned by server the data is sent server --> all clients
    -If owned by Client2 the data is sent Client2 --> Server (Client1) --> Other Clients (Client3 & Client4 etc.)
    -Anything that applies a persistent force is applied locally on all clients. Like the chain harpoons in the videos. Force information is synced by not syncing the actual force, but things like spring rate or spring free length so force can be calculated locally & target information are synced via ID's.
    -Anything that applies an instant (impulse) force is only applied on the owners client only. For example if there is an explosion the server applies the explosion impulse to all server owned objects, but sends a ClientRpc to the owner with impulse info. Although this creates a delay for client-owned objects it prevents an object from being double/triple impulse (i.e. if the explosion impulse was instantly applied on the server, and then also on the client owner later, it would "reset" and snap back momentarily due to latency)
    -All physics object is broken up into 2 objects. I instantly snap the rigidbody & collider to the position sent by the owner. This would look jittery, so I LERP the visual aspects of the object. I do this because you can't LERP a rigidbody otherwise it wouldn't be physics based.
    -For a player object the information that is sent is
    ---Position
    ---Rotation (only Yrot is sent for the player since X and Z are locked)
    ---Rigidbody velocity
    ---Commanded angle/velocity (this is important... and allows physics to be performed locally in-between network updates)
    -The network send interval gets reduced for off-screen objects (it looks for the closest player).
    -If values have not changed then information is not sent (i.e. a wooden crate at rest)
    -For objects like arrows (or harpoons in the video) only speed, angle, and initial position is sent once. Since there is no way it can change trajectory there is no need to send periodic updates.
    -Data is compressed. i.e. angles use 12bits which gives 0.09deg resolution.
    -Another layer was added on top of the UNET HLAPI. This allows me to send data in both directions. For example position information for the player is controlled by the player Client2 --> Server (Client1) --> Client3&4 but animation data is sent from Server --> Client. The default UNET HLAPI does not allow data to travel both directions.

    Anyhow it took a long time but it finally seems to be working well. Any questions just let me know.
     
  2. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    244
    hey, nice work!

    so i assume you're sending snapshots frequently and each client is simulating physics between snapshots then? have you ever seen it do weird stuff, or appear jumpy? or is it fairly smooth because you're splitting things up into 2 separate objects? generally how smooth/accurate would you say your approach is?

    for my game, players can throw & catch a ball to each other in VR. it's very important that the ball's traversal look as realistic as possible. NetworkTransform wasn't cutting it, and I had quite a few problems with doing this prediction kind of stuff. having a non-rigidbody visual version of the object lerping around can produce some unrealistic results if you're staring at a ball flying at you that you're trying to catch. if it even lerps unrealistically for a quarter second, it's enough to throw off your timing and miss an otherwise easy catch.

    instead, i now have the client collect a buffer of snapshots that i lerp through. this seems to look basically perfect, but i can run into trouble if the buffer runs out (i.e. if there's a big 200ms+ hiccup in updates). also catching ends up looking a little laggy to the thrower, because the catcher is catching the buffered ball, so he might be 100-200ms behind the thrower. i'm still always looking for ways to improve this.

    thanks for sharing :D
     
  3. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    if you're doing throw and catch ball, it should probably just be deterministic, that is, you only need to send the network time it was thrown + angle + force and every client, even with a second or so of lag, should be able to reliably construct where the ball is for each client, without lag. This is also only sending data once, which is nice.
     
  4. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    244
    That would be great if my world is static. And if there were only one ball to worry about.
     
  5. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Yeah in a completely deterministic world I'd send the data (position, velocity, etc.) + timestamp. But if it's not deterministic then it gets really tricky to avoid the appearance of a lerping delay.

    Keeping a buffer of past data seems like a really good spot to start if motion performance is critical and you want to make a clever algorithm (like predictive or feed-forward). I do not buffer any past received data packs and apply any received data instantly which is simpler to code, but more limited.
     
  6. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    I don't know if this would get you what you want, but Photon has their new TrueSync product.
    It's a lockstep multiplayer framework with its own 2D/3D deterministic physics.
     
    Zullar likes this.
  7. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Wow that's crazy. They made their own physics engine to sync position?! Does that mean it's not compatible with Unity's built in physics?
     
  8. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    From what I recall, and you should check their website to verify it, the two physics worlds do not interact.
    I think you can use both at the same time, but please check, my memory is hazy.
     
  9. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    I also _just_ finally got syncing working well and the steps I followed are almost spot on exactly as you described, and every single one of them is important. The real key for me was applying constant forces on all clients.

    Rather than separating the visual elements as you've done I've got some fancy lerping towards a target state (which is itself extrapolated from the network state + physics via math). It seems to work pretty well but I definitely considered separating the visual elements. I'm pretty sure it's the more correct solution but I'll take "Good enough."

    A couple more random tips:

    If you want to reduce bandwidth you can combine messages from multiple related objects. For instance if you have a player made of 4 parts that all sync, send all of their data in one message instead of 4 different messages and you'll cut your overhead down by 75%.

    If you want to reduce bandwidth even more find a good implementation of the Half data type and send that over the network instead of floats. Boom, bandwidth cut in half.
     
    Last edited: Feb 28, 2017
    MichaelJT and Zullar like this.
  10. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    C# .NET and Unity reader/writers only write full bytes (8 bits). For just about everything this is fine, but as far as network bandwidth is concerned it's bad design IMO. Especially if a lot of bool flags are being networked (sends 8 bits instead of 1 bit!).

    I ended up making a Bit Reader/Writer instead of a Byte Reader/Writer. This allows to send bools at 1/8th the memory usage along with dirty masks. The way it works is it writes bits, then rounds up to the nearest byte and sends the byteArray using normal methods. Format is something like this.

    Bool: 1 bit (no dirty bit is needed since it wouldn't save any bandwidth)
    Byte: 1 bit dirty + 8bit (if dirty)
    short: 1 bit dirty + 16bit (if dirty)
    int32: 1 bit dirty + 32bit (if dirty)
    float: 1 bit dirty + 32bit (if dirty)
    floatCompressed: 1 bit dirty + X bit (if dirty)

    floatCompressed is a new variable type I made with these properties.
    float min
    float max
    byte numBits

    So for a floatCompressed angle (for example) I do compression options of
    min = 0deg
    max = 360deg
    numBits = 12 (1.5 bytes) ...I found this works well for angles

    Additionally I pulled in the uint compression that UNET uses (for small numbers an UInt32 only uses 8bits instead of 32 bits). For any array indexes and such this saves bandwidth.
     
    Carterryan1990 likes this.
  11. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    244
    @Zullar i ended up writing a very similar bit reader/writer last month. it's very cool to be able to set the float precision where appropriate. i believe i'm able to get away with 17 bits for each position axis, and 9 bits for each rotation axis (511 degrees of precision). considering i smoothly interpolate between snapshots, i'm tempted to crunch things down even further :D

    i am a little surprised this type of stuff is not built into the NetworkWriter/NetworkReader classes. it's such a simple way to cut your bandwidth down 40-50% once it's all set up.
     
    Zullar likes this.
  12. Driiades

    Driiades

    Joined:
    Oct 27, 2015
    Posts:
    151
    What do you think about the "Play in the past" method ?
     
  13. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    rempelj and Zullar like this.
  14. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
  15. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    Is this effectively a drop in replacement for NetworkTransform and NetworkTransformChild? I use both in my project and would love to free up the couple weeks I had planned to dedicate to improving or replacing them.
     
  16. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    Yep, that is exactly what it is. We've aimed to improve NetworkTransform on all fronts. Smooth Sync is easier to use, more configurable, uses less bandwidth, and gives you smoother and more accurate syncing of your objects.
     
  17. RoiDanton

    RoiDanton

    Joined:
    Apr 14, 2014
    Posts:
    4
    Thanks for sharing your ideas! @thegreatzebadiah Does Smooth Sync support physical direct player to player interaction? E.g. player 1 moves the character of player 2 around only by collision detection of their pawns?
     
  18. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    @RoiDanton Sort of, but not really. We have made some efforts to get it working as well as possible. We just added a mechanism recently that allows you to force an object's state to be sent immediately, instead of waiting for the next network update. This allows you to keep your send rate reasonably low, but force an immediate reaction when it is needed (such as on collision).

    Your best bet if you want networked objects interacting directly is to have them all owned by the server. That's going to introduce some network lag between client input and clients seeing a response, but it's one of those situations where you just can't have it all.

    P.S. We do this in our own game by basically faking it as much as possible. We do all sorts of crazy things like having a non-owned object appear to react to a local player's interaction immediately, but then smoothly correct to the actual position (from the owner) after a short amount of time. If not done carefully though this can cause all sorts of crazy behaviour when clients start disagreeing about where an object should be. Use with caution.
     
    Last edited: Feb 9, 2018
    RoiDanton likes this.
  19. MichaelJT

    MichaelJT

    Joined:
    Jan 28, 2015
    Posts:
    14
    Unity's multiplayer services are charged by the gigabyte - so not surprising they haven't optimised bandwidth.
     
    IdiotsCode and chadfranklin47 like this.
  20. chadfranklin47

    chadfranklin47

    Joined:
    Aug 11, 2015
    Posts:
    229
    Lol, that's one way to think about it.
     
  21. kabuto178

    kabuto178

    Joined:
    Mar 21, 2017
    Posts:
    59
    @Zullar How did you implement server objects only sending updates to the nearest player? Or even players only getting notified of updates within a certain range of themselves? I have been stuck on that for a bit now.
     
  22. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    I send all updates to all players (it's only a 3 player game). For a larger game with many players this would not work.
     
  23. kabuto178

    kabuto178

    Joined:
    Mar 21, 2017
    Posts:
    59
    i understand