Search Unity

Synchronized game start with UNET

Discussion in 'UNet' started by Hacky, Jun 18, 2015.

  1. Hacky

    Hacky

    Joined:
    Mar 22, 2013
    Posts:
    28
    Hi,

    in legacy network there was a Network.time property. I used this property for e.g. synchronized game starts or for calculating delays between sending an rpc and receiving it. Is there any similarly in UNET?

    Thanks for your replies.

    Best regards,
    David
     
  2. aabramychev

    aabramychev

    Unity Technologies

    Joined:
    Jul 17, 2012
    Posts:
    574
    Yes :) and No :(
    Yes: there are two functions: GetNetworkTimestamp - it uses to receive time label which should send to other peer, then peer can call GetRemoteDelayTimeMS provides received time label and this function return your delay in ms.

    The problem is - tight now something broken inside and i'm looking for proper fix :(
     
  3. any_user

    any_user

    Joined:
    Oct 19, 2008
    Posts:
    374
    is the timestamp returned by GetNetworkTimestamp supposed to be synchronized between the client and the server? I'm trying to create my own interpolation code, but I have problems getting the synchronized time.
     
  4. any_user

    any_user

    Joined:
    Oct 19, 2008
    Posts:
    374
    Would this the right way to do it?

    Code (CSharp):
    1.  byte error;
    2. int delayMS = NetworkTransport.GetRemoteDelayTimeMS (
    3. NetworkClient.allClients [0].client.connection.hostId,
    4. NetworkClient.allClients [0].client.connection.connectionId,
    5. NetworkTransport.GetNetworkTimestamp (),
    6. out error);
    7. int networkTime = (NetworkTransport.GetNetworkTimestamp () + delayMS);
    It seems to work, but only after being connected for around 15-20s. At the beginning the values are completely wrong.
     
    Last edited: Jul 13, 2015
  5. Dkury

    Dkury

    Joined:
    Jul 14, 2012
    Posts:
    46
    Bump. So, what would be prefferable for storing state time in Unity 5.2.3: Network.time or NetworkTransport.GetNetworkTimestamp()? I also get totally different values when using NetworkTransport.GetNetworkTimestamp() on the server and clients. Network.time seems working OK, but it's legacy, isn't it?
     
    Last edited: Dec 5, 2015
  6. Dkury

    Dkury

    Joined:
    Jul 14, 2012
    Posts:
    46
    any_user, wow, this is a bit off topic, but very peculiar. I was just looking at Drei game and was thinking about what a neat and seamless multiplayer those Etter guys made. Drei is an inspiration for me, currently I'm working on a social experience for VR, want to make matchmaking and interaction between players to work as seamless as it was in Drei. I decided to look at who was a developer and found a guy responsible for programming that neat multiplayer—Mario von Rickenbach. I was creeping at his old tweets and videos, meanwhile decided to look at this forum post in case if somebody responded. Then, I payed attention to your avatar and found it similar to Mario's avatar. And BAM, turned out that you're the Mario von Rickenbach guy. What a nice coincidence :)

    Btw, have you found an answer to your question?
     
  7. any_user

    any_user

    Joined:
    Oct 19, 2008
    Posts:
    374
    Hi Dima, nice to meet you here! :)
    For our next update of Drei, which will be released for PS4, PS Vita, Wii U, Steam and Android (in addition to the already released iOS version) we moved to UNET. That's because the builtin networking has no support for Wii U. The new version will be fully crossplatform multiplayer, so you should be able to seamlessly play together between mobile, consoles and PC. Only Microsoft doesn't allow that, that's why we haven't planned to make an XBOX version.

    Anyway, sorry for off topic, now about the actual question:
    I'm not 100% sure, but I think Network.time is not really supported in UNET. Maybe it is not completely wrong, but I wouldn't rely on it.

    Synchronizing the time could be done by sending the server-value of NetworkTransport.GetNetworkTimestamp() to the client, and there check the delay with NetworkTransport.GetRemoteDelayTimeMS() and subtract it from the local Time.time. The resulting value is the synchronized time.

    Here's an example how it could look. Completely untested, but you get the idea.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Networking;
    3. using System.Collections;
    4. public class NetworkTime : NetworkBehaviour
    5. {
    6.  
    7.     float lastReceivedDelayMS;
    8.     float syncInterval = .5f; // decrease to get more precise values
    9.  
    10.     void OnStartServer()
    11.     {
    12.         StartCoroutine(SendTimestampLoop());
    13.     }
    14.  
    15.     IEnumerator SyncTimestampLoop()
    16.     {
    17.         while(true)
    18.         {
    19.             RpcSyncTimestamp(NetworkTransport.GetNetworkTimestamp());
    20.             yield return new WaitForSeconds(syncInterval);
    21.         }
    22.     }
    23.  
    24.     [ClientRPC]
    25.     void RpcSyncTimestamp(int timestamp)
    26.     {
    27.         byte error;
    28.  
    29.         var serverConnection = NetworkManager.singleton.client.connection;
    30.         lastReceivedDelayMS = NetworkTransport.GetRemoteDelayTimeMS (
    31.             serverConnection.hostId,
    32.             serverConnection.connectionId,
    33.             timestamp,
    34.             out error);
    35.     }
    36.  
    37.     public float GetServerTime()
    38.     {
    39.         return Time.time - (lastReceivedDelayMS / 1000f);
    40.     }
    41.  
    42. }
    But at the end you problably anyway don't need just a general synchronized time, but the actual delay of specific messages. To get this, you can just send the timestamp with the message, and then calculate the time of the server for that message. I used it for the NetworkRigidbody script of Drei, which plays the old states of the object with a delay. This is the script for reference:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Networking;
    3. using System.Collections;
    4. public class NetworkRigidbody : NetworkBehaviour
    5. {
    6.  
    7.     Rigidbody2D _rigidbody2D;
    8.     public new Rigidbody2D rigidbody2D {
    9.         get {
    10.             if (!this)
    11.                 return null;
    12.          
    13.             if (!_rigidbody2D)
    14.                 _rigidbody2D = GetComponent<Rigidbody2D> ();
    15.             return _rigidbody2D;
    16.         }
    17.     }
    18.  
    19.  
    20.     internal float renderDelay = .15f;
    21.     //    int m_ExtrapolationLimitMS = 200;
    22.     public bool syncRotation = true;
    23.     public bool syncVelocity = true;
    24.     public bool syncAngularVelocity = true;
    25.  
    26.     [System.Serializable]
    27.     internal struct  State
    28.     {
    29.         internal int timestamp;
    30.         internal Vector2 position;
    31.         internal Quaternion rotation;
    32.         internal Vector2 velocity;
    33.         internal float angularVelocity;
    34.  
    35.         public static void Serialize(State state, NetworkWriter writer, NetworkRigidbody source)
    36.         {
    37.             writer.Write (state.timestamp);
    38.             writer.Write (state.position);
    39.             if(source.syncRotation)
    40.                 writer.Write (state.rotation.eulerAngles);          
    41.             if(source.syncVelocity)
    42.                 writer.Write (state.velocity);  
    43.             if(source.syncAngularVelocity)
    44.                 writer.Write (state.angularVelocity);
    45.         }
    46.  
    47.         public static State Deserialize(NetworkReader reader, NetworkRigidbody source)
    48.         {
    49.             State state = new State ();
    50.             state.timestamp = reader.ReadInt32();
    51.             state.position = reader.ReadVector2();
    52.             if(source.syncRotation)
    53.                 state.rotation = Quaternion.Euler( reader.ReadVector3 ());  
    54.             if(source.syncVelocity)
    55.                 state.velocity = reader.ReadVector2 ();
    56.             if(source.syncAngularVelocity)
    57.                 state.angularVelocity = reader.ReadSingle ();
    58.             return state;
    59.         }
    60.     }
    61.  
    62.     // We store twenty states with "playback" information
    63.     State lastSentState;
    64.     State[] m_BufferedState = new State[20];
    65.     // Keep track of what slots are used
    66.     int m_TimestampCount;
    67.  
    68.     public static float networkInterval = 1 / 15f;
    69.  
    70.     public override float GetNetworkSendInterval ()
    71.     {
    72.         return networkInterval;
    73.     }
    74.  
    75.     public override int GetNetworkChannel ()
    76.     {
    77.         return 1; // unreliable
    78.     }
    79.  
    80.     public override bool OnSerialize (NetworkWriter writer, bool initialState)
    81.     {
    82.         if (rigidbody2D)
    83.             lastSentState = GetCurrentState ();
    84.  
    85.         State.Serialize (lastSentState, writer,this);
    86.  
    87.         //base.OnSerialize (writer, initialState);
    88.  
    89.         return true;
    90.     }
    91.  
    92.     public override void OnDeserialize (NetworkReader reader, bool initialState)
    93.     {
    94.         State state = State.Deserialize (reader,this);
    95.  
    96.         ReceiveNewState (state);
    97.         //base.OnDeserialize (reader, initialState);
    98.     }
    99.  
    100.     void FixedUpdate()
    101.     {
    102.         if (!rigidbody2D)
    103.             return;
    104.  
    105.         if (isServer)
    106.             FixedUpdateServer ();
    107.     }
    108.  
    109.     void FixedUpdateServer()
    110.     {
    111.         SetDirtyBit (1);
    112.     }
    113.  
    114.     // We have a window of interpolationBackTime where we basically play
    115.     // By having interpolationBackTime the average ping, you will usually use interpolation.
    116.     // And only if no more data arrives we will use extra polation
    117.     void Update ()
    118.     {
    119.         if (!rigidbody2D)
    120.             return;
    121.      
    122.         if (isClient && !isServer)
    123.             UpdateClient ();
    124.     }
    125.  
    126.     void UpdateClient()
    127.     {
    128.         if (m_TimestampCount == 0)
    129.             return;
    130.  
    131.         byte error;
    132.  
    133.         int firstStateDelay = NetworkTransport.GetRemoteDelayTimeMS (
    134.             Game.networkManager.client.connection.hostId,
    135.             Game.networkManager.client.connection.connectionId,
    136.             m_BufferedState[0].timestamp,
    137.             out error);
    138.  
    139.         // Use interpolation if the rigidbody playback time is present in the buffer
    140.         if (firstStateDelay < Game.networkManager.clientRenderDelayMS)
    141.         {
    142.             // Go through buffer and find correct state to play back
    143.             for (int i=0;i<m_TimestampCount;i++)
    144.             {
    145.                 int lhsDelayMS = NetworkTransport.GetRemoteDelayTimeMS (
    146.                         Game.networkManager.client.connection.hostId,
    147.                         Game.networkManager.client.connection.connectionId,
    148.                         m_BufferedState[i].timestamp,
    149.                         out error);
    150.              
    151.                 if (lhsDelayMS >= Game.networkManager.clientRenderDelayMS || i == m_TimestampCount-1)
    152.                 {
    153.                     // The state one slot newer (<100ms) than the best playback state
    154.                     State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
    155.                     int rhsDelayMS = NetworkTransport.GetRemoteDelayTimeMS (
    156.                         Game.networkManager.client.connection.hostId,
    157.                         Game.networkManager.client.connection.connectionId,
    158.                         rhs.timestamp,
    159.                         out error);
    160.                     // The best playback state (closest to 100 ms old (default time))
    161.                     State lhs = m_BufferedState[i];
    162.  
    163.                     float t = Mathf.InverseLerp(lhsDelayMS, rhsDelayMS, Game.networkManager.clientRenderDelayMS);
    164.  
    165.                     transform.position = Vector2.Lerp(lhs.position, rhs.position, t);
    166.                     transform.rotation = Quaternion.Slerp(lhs.rotation, rhs.rotation, t);
    167.                     //rigidbody2D.rotation = Mathf.LerpAngle(lhs.rotation, rhs.rotation, t);
    168.                     rigidbody2D.velocity = Vector2.Lerp(lhs.velocity,rhs.velocity, t);
    169.                     rigidbody2D.angularVelocity = Mathf.Lerp(lhs.angularVelocity, rhs.angularVelocity, t);
    170.                 //    Debug.Log("Found packet for: " + gameObject.name);
    171.                     //DebugExtension.DebugPoint (rigidbody2D.position,Color.green,1,2);
    172.                     return;
    173.                 }
    174.             }
    175.          
    176.          
    177.             Debug.Log("Did not find packet " + m_TimestampCount + " for: " + gameObject.name);
    178.  
    179.          
    180.         }
    181.         // Use extrapolation
    182.         else
    183.         {
    184. //            print ("EXTRAPOLATE");
    185.             State latest = m_BufferedState[0];
    186.  
    187.             //float extrapolationLengthMS = (float)(interpolationTime - latest.timestamp);
    188.  
    189.             // Don't use extrapolation for more than xx ms, you would need to do that carefully
    190.             //if (extrapolationLengthMS < m_ExtrapolationLimitMS)
    191.             //{
    192.             //    rigidbody2D.position = latest.position + latest.velocity * extrapolationLengthMS / 1000;
    193.             //    rigidbody2D.rotation = latest.rotation + latest.angularVelocity * extrapolationLengthMS / 1000;
    194.             //}
    195.             //else
    196.             //{
    197.                 transform.position = latest.position;
    198.                 transform.rotation = latest.rotation;
    199.                 rigidbody2D.velocity = latest.velocity;
    200.                 rigidbody2D.angularVelocity = latest.angularVelocity;
    201.             //}
    202.  
    203.             //DebugExtension.DebugPoint (rigidbody2D.position,Color.blue,1,2);
    204.         }
    205.     }
    206.  
    207.     State GetCurrentState()
    208.     {
    209.         State s = new State ();
    210.         if (!rigidbody2D)
    211.             return s;
    212.         s.timestamp = NetworkTransport.GetNetworkTimestamp ();
    213.         s.position = rigidbody2D.position;
    214.         s.rotation = transform.rotation;
    215.         s.velocity = rigidbody2D.velocity;
    216.         s.angularVelocity = rigidbody2D.angularVelocity;
    217.         return s;
    218.     }
    219.  
    220.     //This Function inserts the new State in the proper slot.
    221.     //This is the Insertion-Sort
    222.     private void ReceiveNewState( State newState )
    223.     {
    224.         //If no States are present, put in first slot.
    225.         if( m_TimestampCount == 0 )
    226.         {
    227.             m_BufferedState[0] = newState;
    228.         }
    229.         else
    230.         {
    231.             //First find proper place in buffer. If no place is found, state can be dropped (newState is too old)
    232.             for( int i = 0; i < m_TimestampCount; i ++ )
    233.             {
    234.                 //If the state in slot i is older than our new state, we found our slot.  
    235.                 if( m_BufferedState[i].timestamp < newState.timestamp )
    236.                 {
    237.                     // Shift the buffer sideways, to make room in slot i. possibly deleting state 20
    238.                     for (int k=m_BufferedState.Length-1;k>i;k--)
    239.                     {
    240.                         m_BufferedState[k] = m_BufferedState[k-1];
    241.                     }
    242.  
    243.                     //insert state
    244.                     m_BufferedState[i] = newState;
    245.  
    246.                     //We are done, exit loop
    247.                     break;
    248.                 }
    249.             }
    250.         }
    251.  
    252.         //DebugExtension.DebugPoint (newState.position,Color.red,.5f,2);
    253.  
    254.         //Update TimestampCount
    255.         m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);
    256.  
    257.     }
    258. }