Search Unity

AnySync - Transform sync for any networking.

Discussion in 'Assets and Asset Store' started by forcepusher, Mar 28, 2018.

  1. MustangLM

    MustangLM

    Joined:
    Feb 6, 2015
    Posts:
    34
    @forcepusher Moving The RigiBody.MovePosition to Fixed Update, using rigibody interpolation, and increasing send rate to 15 msg/s fixed the problem, but the object seems to be correcting a lot, move back and forth while moving(not jittery) , What can I do about this ?
     
  2. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    While figuring out the source of issues I suggest to set TargetLatency to something very high like 0.3 seconds to exclude the possibility that extrapolation is creating any artifacts. I'm not sure what the issue is without a reproduction project.
     
  3. joshua-jebadurai

    joshua-jebadurai

    Joined:
    Jul 3, 2009
    Posts:
    13
    I'm working on fast paced Server based Authoritative Air Hockey game, modded to work with Forge Remastered. The puck without anything hitting it, seems to be in sync properly across all clients. The problem is obvious, when the paddle hits the puck in the client, it needs to be updated immediately on the client, but since it goes across the server and comes back, the puck goes inside the paddle then responds later on the client.

    I give a RPC call about the hit position and velocity of the puck from the client to server. And in the server, I use the teleport method to notify the sudden change in position and velocity. But the server round-about still causes the puck to go inside the paddle, is there a way the client playbacks in a delay, so if I hit the paddle on the client, the round about would match the timing of the delay?

    Code (CSharp):
    1. // Paddle code snippet
    2. private void Update() {
    3.         isOwner = networkObject.IsOwner;
    4.         networkId = networkObject.playerIndex;
    5.  
    6.         if (networkObject.IsServer) {
    7.             // Disabled on the client for now, because too many hits are getting called.
    8.             _collider2D.enabled = false;
    9.  
    10.         } else if (networkObject.IsOwner && !networkObject.IsServer) {
    11.  
    12.             if (Input.GetMouseButton(0)) {
    13.                 moveToPosition = Table.ClosestPoint(0, Camera.main.ScreenToWorldPoint(Input.mousePosition));
    14.                 _rigidbody.MovePosition(moveToPosition);
    15.             }
    16.  
    17.         } else {
    18.             // Other clients don't need collision detection.
    19.             _collider2D.enabled = false;
    20.         }
    21.     }
    22.  
    23.     private void OnCollisionExit2D(Collision2D collision) {
    24.         if (networkObject.IsOwner) {
    25.             Puck puck = collision.gameObject.GetComponent<Puck>();
    26.             if (puck != null) {
    27.                 networkObject.SendRpc(RPC_PUCK_HIT, Receivers.Server, (Vector2)puck.transform.position, puck.Velocity);
    28.              
    29.                 StopAllCoroutines();
    30.                 StartCoroutine(EnableCollider());
    31.             }
    32.         }
    33.     }
    34.  
    35.     // This happens on the server
    36.     public override void PuckHit(RpcArgs args) {
    37.         var hitPosition = args.GetNext<Vector2>();
    38.         var hitVelocity = args.GetNext<Vector2>();
    39.         Puck.TakeHit(hitPosition, hitVelocity);
    40.      
    41.     }
    Puck Code:
    Code (CSharp):
    1. private void Update() {
    2.         if (networkObject.IsServer) {
    3.            
    4.             // send sync messages
    5.             _timeSinceLastSync += Time.deltaTime;
    6.             if (_timeSinceLastSync >= MinimumSendInterval) {
    7.                 if (_lastSentPosition != transform.position)
    8.                     _idle = false;
    9.  
    10.                 if (!_idle) {
    11.                    
    12.                     syncBuffer.AddKeyframe(_timeSinceLastSync, transform.position);
    13.                     networkObject.SendRpc(RPC_SYNC, Receivers.Others, _timeSinceLastSync, transform.position, _rigidbody.velocity);
    14.                     // go idle only after sending a keyframe with the same position to prevent drifting while idle
    15.                     if (_lastSentPosition == transform.position)
    16.                         _idle = true;
    17.  
    18.                     _lastSentPosition = transform.position;
    19.                     _timeSinceLastSync = 0f;
    20.                 }
    21.             }
    22.         }
    23.         else {
    24.  
    25.             // avoid warnings if syncBuffer is empty
    26.             if (syncBuffer.HasKeyframes) {
    27.                 // update playback and apply new position
    28.                 syncBuffer.UpdatePlayback(Time.deltaTime);
    29.  
    30.                 transform.position = syncBuffer.Position;
    31.                 _rigidbody.velocity = syncBuffer.Velocity;
    32.  
    33.             }
    34.         }
    35.     }
    36.  
    37.     public override void Sync(RpcArgs args) {
    38.         syncBuffer.AddKeyframe(
    39.             interpolationTime: args.GetNext<float>(),
    40.             position: args.GetNext<Vector3>()
    41.             );
    42.     }
    43.  
    44.     // Server running a hit call from the Client
    45.     public static void TakeHit (Vector2 position, Vector2 hitVelocity) {
    46.         _instance._TakeHit(position, hitVelocity);
    47.     }
    48.  
    49.     // Server calculates the hit that happened on the client and then notifies the others the sudden change
    50.     public void _TakeHit(Vector2 position, Vector2 hitVelocity) {
    51.         syncBuffer.AddKeyframe(_timeSinceLastSync, transform.position, velocity: hitVelocity);
    52.         networkObject.SendRpc(RPC_SYNC, Receivers.Others, _timeSinceLastSync, transform.position, _rigidbody.velocity);
    53.  
    54.         transform.position = position;
    55.         _rigidbody.velocity = hitVelocity;
    56.  
    57.         _timeSinceLastSync = 0;
    58.         syncBuffer.AddKeyframe(0, transform.position, velocity: hitVelocity);
    59.         networkObject.SendRpc(RPC_SYNC, Receivers.Others, _timeSinceLastSync, transform.position, _rigidbody.velocity);
    60.  
    61.     }
     
  4. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    Hello. I don't think it's possible to properly prevent that in this setup. One solution I can suggest is to override the puck positioning locally for a moment after hitting it.
    If I was going to make a game like this myself, I would have used Physics.Simulate to predict puck position in the future. Take a look at this page http://www.codersblock.org/blog/client-side-prediction-in-unity-2018
     
  5. joshua-jebadurai

    joshua-jebadurai

    Joined:
    Jul 3, 2009
    Posts:
    13
    Thank you, I did override the position for a moment after the hit but when I enable syncbuffer back, it goes back in time. I'll try the Physics.Simulate method, I think I read that blog a time ago. Thanks for the quick response, I love AnySync by the way.
     
    forcepusher likes this.
  6. HallMaruShiranui

    HallMaruShiranui

    Joined:
    Aug 10, 2018
    Posts:
    3
    Hi, this asset is easy to integrate to a custom network serializer and sender/receiver? I'm using TcpListener and TcpClient on my game.
     
  7. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    Hello. Yeah, you can do that. AnySync is not tied to any specific network architecture.
     
  8. HallMaruShiranui

    HallMaruShiranui

    Joined:
    Aug 10, 2018
    Posts:
    3
    Cool, i'm reading your documentation, i can sync animators too using SyncBuffer? There is some example syncing animation?
     
  9. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    There's no examples for that unfortunately. It's designed to sync position and rotation values (and their velocities) only.
     
  10. HallMaruShiranui

    HallMaruShiranui

    Joined:
    Aug 10, 2018
    Posts:
    3
    Cool, i'll try to sync all animations using parameters on animators and events on custom packets, thanks for your reply i'll give a try on AnySync.
     
  11. Zincord

    Zincord

    Joined:
    Dec 21, 2013
    Posts:
    14
    Some news about the "Delayed Action Execution"?
     
  12. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    It's still there in a high priority feature list along with decoupling sync logic from MonoBehaviour, and looking into ECS compatibility.
    Currently I'm working with some company on releasing a battle royale game that uses AnySync, so I can't afford to work on new features until it's released because that stuff requires a fair bit of research. Definitely not in February or March.
     
    Last edited: May 7, 2019
  13. yuliyF

    yuliyF

    Joined:
    Nov 15, 2012
    Posts:
    145
    I get problem when my user change a move speed, as example: going - 3, get in a car - 10. In the moment when person transit on, looks some shake. Have any advice?
     
  14. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    That's not supposed to happen. If you can share a reproduction project, I can point out the issue with the netcode.
    You can email me at bananapartydev@gmail.com or start a personal conversation to share the project and get into details.
     
  15. Tony-Lovell

    Tony-Lovell

    Joined:
    Jul 14, 2014
    Posts:
    93
    Is it easily possible to swap out networking sublayers such as PUN/UNET/etc for my own? I have my own networking in place now which can write/read rotations and Vector3s which I'd like to keep. I only really need network smoothing, not network transport.

    Thanks in advance!

    tone
     
  16. yuliyF

    yuliyF

    Joined:
    Nov 15, 2012
    Posts:
    145
    forcepusher, thanks. Problem was in my part(moving twice the same object), so I'm testing demo to change speed dynamical - works fine
     
    Last edited: Mar 22, 2019
    forcepusher likes this.
  17. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    Hello. This asset is not tied to any specific network transport nor networking solution, you can swap things as you see fit.
     
  18. Velcer

    Velcer

    Joined:
    Mar 16, 2017
    Posts:
    19
    Hey, any update on getting this to not require mono behavior?
     
  19. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    I'm finally able to work on the new features full time.
    An early version of new AnySync update is ready for preview. If anyone wants to try it and maybe give me some feedback, send me a personal message with the invoice number of your purchase.

    New features:
    • No longer inherits from MonoBehaviour (it's no longer a component).
    • Invocation synchronization. Now you can execute RPCs in perfect sync with the movement ("Delayed Event Execution" feature I was talking about earlier).
    • ECS compatibility. It's not exactly designed for pure ECS since data is not separated from the behavior code, but it should work fine regardless if you put MotionGenerator into a component struct.
    • Lots of cosmetic changes that solve some naming issues and make coding less daunting.
    I'm still working on rewriting the examples, documentation and rolling a new video. I can't publish it yet on the Asset Store.
     
    Last edited: May 14, 2019
    yuliyF likes this.
  20. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    2.0 Update mentioned above is up in the Asset Store.
    Remove the old AnySync folder before importing it and please let me know if there are any issues.

    It's a major update that includes some code-breaking changes. Backup your project and prepare to change your game code in order to upgrade.
     
  21. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    2.01 Update brought significant improvement to the Photon sample code. It was unnecessarily complex and not well thought when used together with other networked components. I've decided to calculate keyframe interpolation time from PhotonMessageInfo and that allowed us to use any PhotonView observe option, not just Unreliable.
    So now we have a choice to use ReliableDeltaCompressed or UnreliableOnChange to save some bandwidth without a hassle.
    See new Photon sample code here https://pastebin.com/T57V07E9
     
    Last edited: Jul 1, 2019
  22. Ruslank100

    Ruslank100

    Joined:
    Apr 11, 2018
    Posts:
    13
    Hello! I am using this asset to smooth tanks movement(I am using Dark Rift 2), but have a small problem, that causes tanks to move a bit jittery(it looks like micro - freezes, but the game is running at 60 fps smoothly!). How do I fix this?
    The code works like this : position message received from the server. A keyframe is added using the data received, and the interpolation time is time since tank spawned. In Update() I am progressing the playback using Time.deltaTime, and then setting the position from buffer. Thanks in advance!
     
  23. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    Hello. Thanks for using AnySync.
    Interpolation time should not be calculated from spawn time. It should be transferred over the network explicitly (See lines 40-45) or calculated from timestamp sent with network message (See lines 90-99).
    In case if you can't figure it out, send me a reproduction project so I can fix it for you and point out what went wrong.
     
    Last edited: Jul 15, 2019
  24. Ruslank100

    Ruslank100

    Joined:
    Apr 11, 2018
    Posts:
    13
    Thanks for your reply, I have tried to do this, but it looks that I am doing something wrong. The code :
    This is called when a client receives a message from the server, all the simulations are happening on it(on the server)
    Code (CSharp):
    1.  if(lastReceivedKeyframeTime < 0f)
    2.                           {
    3.                              lastReceivedKeyframeTime = timeS;
    4.                           }
    5.                               var interpolationTime = (float)(timeS - lastReceivedKeyframeTime);
    6.                               lastReceivedKeyframeTime = timeS;
    7.  
    8. then...
    9. buffers[i].AddKeyframe(interpolationTime , new Vector3(pos[num * 3 + 0], pos[num * 3 + 1], pos[num * 3 + 2]),Quaternion.Euler(rots[num * 3 + 0], rots[num * 3 + 1], rots[num * 3 + 2]));
    Then in Update() I am doing this :
    Code (CSharp):
    1. buffers[i].UpdatePlayback(Time.deltaTime);
    2.                     tank.position = buffers[i].Position;
    3.                     tank.rotation = buffers[i].Rotation;
    Now jitter happens stable once in 3 - 4 seconds, for a second. Also do you have the older version of AnySync? My game breaks when .net 4.x used for some reason.
     
  25. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    Sent an old version in a private conversation.
    I didn't find any issues in the code chunks you posted. If you are unable to provide a reproduction project, then I will offer you a refund. There's not much I can do without a repro, my help is limited to guessing.

    UPDATE: After providing more support we figured there was no issues since it ran fine in builds.
    Slight hitch was an editor lag caused by things unrelated to this asset.
     
    Last edited: Aug 9, 2019
  26. Velcer

    Velcer

    Joined:
    Mar 16, 2017
    Posts:
    19
    Using my own networking solution, but that's kind of irrelevant. I need to sync a 2D rigidbody for my project to work correctly. Problem is your system doesn't go far for 2D, so this is my work around.
    Code (CSharp):
    1.     public void SyncMovement(float interpolationTime, Vector2 position, float rotation, Vector2 velocity, float angularVelocity)
    2.     {
    3.         if (IsMe)
    4.             return;
    5.  
    6.         _motionGenerator.AddKeyframe(interpolationTime, position, velocity, new Quaternion (0,0,rotation, transform.rotation.w), new Vector3(0,0, angularVelocity));
    7.     }
    This does not get an accurate rotation, and while position and velocity works smoothly, rotation appears do be desync'd, and a little jittery even.

    Edit: I considered just hacking together a Rigidbody as opposed to the Rigidbody2D, however I depend also on the 2D colliders for my objects.
     
    Last edited: Dec 20, 2019
  27. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    Hello. There's nothing hacky nor unsupported about using 2D with AnySync. Your code is correct with a tiny exception where you shouldn't specify quaternion values directly, but use Quaternion.Euler method instead.
    Here's what MotionGenerator.AddKeyframe call should look like, assuming that you need to sync the Z angle:
    _motionGenerator.AddKeyframe(interpolationTime, position, velocity, Quaternion.Euler(0,0,rotation), Vector3(0,0, angularVelocity));
     
    Velcer likes this.
  28. Velcer

    Velcer

    Joined:
    Mar 16, 2017
    Posts:
    19
    I'm using this correctly I think, but I have a problem where rotating is very sluggish, it looks as if your skipping frames, because of the way it turns.

    The other issue I noticed is that once I release my inputs, the player character for another network will drift forwards at a set speed, which it shouldn't.

    EDIT: Thanks, I was able to resolve this on my own. Silly me forgot to assign the data from the motion generator.

    Question: should I be getting my latency, and then assigning that to the motion generator?

    EDIT2: Encountered a problem.

    Code (CSharp):
    1.             if (_motionGenerator.HasKeyframes)
    2.             {
    3.                 // Update playback and apply new position.
    4.                 _motionGenerator.UpdatePlayback(Time.deltaTime);
    5.                 transform.position = _motionGenerator.Position;
    6.                 Debug.LogError(_motionGenerator.HasKeyframes);
    7.                 _rigidbody2D.rotation = _motionGenerator.Rotation.eulerAngles.z;
    8.                 _rigidbody2D.velocity = _motionGenerator.Velocity;
    9.                 _rigidbody2D.angularVelocity = _motionGenerator.AngularVelocity.z;
    10.  
    11.             }
    This code is executing constantly, even when there haven't been more frames sent over the network. I'm worried that the playback never gets to 0 frames, and continues to assign old data. This doesn't allow me to do minor client side smoothing adjustments, as this data is continually set.

    I want to simulate the movement in the same way my player does, but use your system to periodically correct it, to save on bandwidth.

    Edit3:
    This is how my movement looks, setting minimum send time to .1f and just letting motiongenerator do all the work.

    The first ship is local, and the second is not. It looks really jumpy, you'll see what I mean.



    This is my code:
    Code (CSharp):
    1. public void HandleNetworkedMovement()
    2.     {
    3.         if (IsMe)
    4.         {
    5.             _timeSinceLastSync += Time.deltaTime;
    6.             if (_timeSinceLastSync >= _minimumSendInterval)
    7.             {
    8.                 if (_rigidbody2D.position != _lastSentPosition || _rigidbody2D.velocity != _lastSentVelocity || _rigidbody2D.rotation != _lastSentRotation || _rigidbody2D.angularVelocity != _lastSentAngularVelocity)
    9.                     SendSyncMessage();
    10.             }
    11.         }
    12.         else
    13.         {
    14.             if (_motionGenerator.HasKeyframes)
    15.             {
    16.                 // Update playback and apply new position.
    17.                 _motionGenerator.UpdatePlayback(Time.deltaTime);
    18.                 _rigidbody2D.position = _motionGenerator.Position;
    19.                 _rigidbody2D.rotation = _motionGenerator.Rotation.eulerAngles.z;
    20.                 _rigidbody2D.velocity = _motionGenerator.Velocity;
    21.                 _rigidbody2D.angularVelocity = _motionGenerator.AngularVelocity.z;
    22.             }
    23.         }
    24.     }
    25.  
    26.     public void SendSyncMessage()
    27.     {
    28.         ClientManager.Instance?.Send("z", 1, _timeSinceLastSync, _rigidbody2D.position.x, _rigidbody2D.position.y, _rigidbody2D.rotation, _rigidbody2D.velocity.x, _rigidbody2D.velocity.y, _rigidbody2D.angularVelocity);
    29.         _lastSentPosition = _rigidbody2D.position;
    30.         _lastSentVelocity = _rigidbody2D.velocity;
    31.         _lastSentRotation = _rigidbody2D.rotation;
    32.         _lastSentAngularVelocity = _rigidbody2D.angularVelocity;
    33.         _timeSinceLastSync = 0f;
    34.     }
    35.  
    36.     public void SyncMovement(float interpolationTime, Vector2 position, float rotation, Vector2 velocity, float angularVelocity)
    37.     {
    38.         if (IsMe)
    39.             return;
    40.  
    41.         _motionGenerator.AddKeyframe(interpolationTime, position, velocity, Quaternion.Euler(0, 0, rotation), new Vector3(0, 0, angularVelocity));
    42.     }
     
    Last edited: Dec 21, 2019
  29. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    Nah, MotionGenerator is constantly adopting to your network latency and packet jitter automatically.
    You can reduce interpolation latency the higher your ping time is (to use extrapolation for network latency compensation), but I'd rather avoid trying it while learning how to use AnySync. It's very tricky.
    When MotionGenerator is reaching the end of keyframe data, it continues to extrapolate according to velocities in the last sent frame.
    This practice is kinda discouraged. I'd roll my own deterministic simulation that simulates movement using the input (and refund AnySync in the process), or stick to using MotionGenerator. Mixing those two might be very painful.
    I've looked at the video and code. The thing is that Physics and Transform updates are executed on separate threads, so this might cause all sorts of jitter issues. You should assign MotionGenerator data to the transform, instead of the rigidbody. Like this:
    Code (CSharp):
    1. if (_motionGenerator.HasKeyframes)
    2. {
    3.     // Update playback and apply new position.
    4.     _motionGenerator.UpdatePlayback(Time.deltaTime);
    5.     transform.position = _motionGenerator.Position;
    6.     transform.rotation = _motionGenerator.Rotation;
    7. }
    Refer to the basic 2D example for a jitter-free solution. Besides that, make sure you use interpolation in rigidbody settings.
    If those changes still didn't solve your problem, try not using Rigidbody2D.position, Ridibody2D.rotation and other physics-related stuff at all when it comes to netcode.
     
    Last edited: Dec 21, 2019
  30. Velcer

    Velcer

    Joined:
    Mar 16, 2017
    Posts:
    19
    Thanks!! I just needed to set rotation and position. I didn't understand fully how to use this, I think I have a better idea now.
     
  31. Velcer

    Velcer

    Joined:
    Mar 16, 2017
    Posts:
    19
    This is very bandwidth heavy. How difficult would a deterministic solution be for this?

    Edit: Also is it possible to add a way to, in the motion generator sync other values, e.g health, shield? I've been using an additional motion generator to do this.
     
  32. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    AnySync is an interpolation library and has nothing to do with bandwidth. You can improve your netcode with tricks like network culling, packet compression and delta encoding to achieve desired results.
    Deterministic solutions are great, except they consume a lot of time and effort. Most indie startups attempt it, but couldn't handle it and rewrite everything from scratch at some point. I say if you've got what it takes - go for it.

    I would recommend checking out Photon Quantum. If you look at their pricing page, you'll get what I'm talking about. Usually new customers desperately need a lot of support and this is where their subscription money goes.
    There is InvocationSync script included with the asset. It's designed exactly for things like that. See UNetRigidbodyColorChanger.cs in the Expert example.
     
    Last edited: Jan 7, 2020
    Velcer likes this.
  33. Velcer

    Velcer

    Joined:
    Mar 16, 2017
    Posts:
    19
    Is there a way to use this asset for server side authority, but still allow the player to have instant response time? For instance is there anyway I can make the player simulate himself, but the server issues corrections?
     
  34. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    202
    I think you can, but I'm not sure if it'll provide the best user experience. You will run into nasty rubber-banding issues, and this is why not a single AAA studio uses fully authoritative movement - including Valve, Blizzard and Dice. The fact that cheaters are able to use speedhacks in their games proves it.
    All they do is check if client's player position has gone way too far from server simulation and teleport them back into correct position. You can definitely do that with AnySync.
     
    Last edited: Jan 16, 2020
    Velcer likes this.
unityunity