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. Join us on March 30, 2023, between 5 am & 1 pm EST, in the Performance Profiling Dev Blitz Day 2023 - Q&A forum and Discord where you can connect with our teams behind the Memory and CPU Profilers.
    Dismiss Notice

Unity Multiplayer How to Sync Network Time?

Discussion in 'Multiplayer' started by zee_ola05, Apr 5, 2016.

  1. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    I want to have my Clients and Server to have their clocks as close as possible. What is the best way to do it? I wanna sync my client's clock with the server as soon as it connects.

    I know that this is a popular question. But I can't find a UNet-specific solution for this. The closest solution I got is the script in this post. But I still can't get mine to work. I get this error when calling GetRemoteDelayTimeMS.
    Code (CSharp):
    1. host id out of bound id {-1} max id should be greater 0 and less than {1}
     
  2. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    678
    I would say that more information is needed.

    1. What is the purpose of this clock?
    2. Does it represent real time?
    3. Does this clock's time pass as normal time does?

    For example, if it represents real time, then just have a method where the game detects the current time in the appropriate time-zone...and then have time elapse on that click via Time.time. So as I said, we need to know what you're trying to achieve with this clock.
     
  3. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    835
    I had trouble with this as well so instead of fighting unity and trying to guess how they wanted me to do it I just wrote my own. It seems to be working but I'm sure it's not the 100% best solution.

    On the host:
    Code (CSharp):
    1. NetworkServer.RegisterHandler(MsgType.RoundTripTimePing, roundTripTimePing;
    And on the clients (including the local client on the host):
    Code (CSharp):
    1. client.RegisterHandler(MsgType.TimeSync, syncNetworkTime);
    2. client.RegisterHandler(MsgType.RoundTripTimePong, roundTripTimePong);
    And then the actual methods:
    Code (CSharp):
    1.     public void roundTripTimePong(NetworkMessage msg)
    2.     {
    3.         if (clientRTTToServer == 0)
    4.         {
    5.             clientRTTToServer = Time.time - lastTimeClientReceivedTimeUpdateFromServer;
    6.         }
    7.         else
    8.         {
    9.             clientRTTToServer = (clientRTTToServer + Time.time - lastTimeClientReceivedTimeUpdateFromServer) / 2.0f;
    10.         }
    11.     }
    12.  
    13.     // Called on the server to calculate the round trip time from the client
    14.     public void roundTripTimePing(NetworkMessage msg)
    15.     {
    16.         if (NetworkServer.active)
    17.         {
    18.             if (serverRTTToClients.ContainsKey(msg.conn))
    19.             {
    20.                 // If we already have a rtt recorded for this connection the new rount trip time is the old time
    21.                 // averaged with the new time that was just calculated.
    22.                 serverRTTToClients[msg.conn] = (serverRTTToClients[msg.conn] + (Time.time - lastTimeServerSentTimeSyncMessageToClient[msg.conn])) / 2.0f;
    23.             }
    24.             else
    25.             {
    26.                 // If there is no rtt recorded yet for this connection then we use the time we just calculated
    27.                 serverRTTToClients[msg.conn] = (Time.time - lastTimeServerSentTimeSyncMessageToClient[msg.conn]);
    28.             }
    29.             lastTimeServerReceivedRTTResponseFromClient[msg.conn] = Time.time;
    30.  
    31.             // Server sends RTT message back to client so that the client can calculate rtt from server
    32.             msg.conn.Send(MsgType.RoundTripTimePong, new EmptyMessage());
    33.         }
    34.     }
    35.  
    36.     public void syncNetworkTime(NetworkMessage msg)
    37.     {
    38.         // Clients send a message back to the server so that it can calculate round trip time.
    39.         msg.conn.Send(MsgType.RoundTripTimePing, new EmptyMessage());
    40.  
    41.         // Store the time received from the server so that we can use it, along with the rtt, to estimate the server time
    42.         lastTimeClientReceivedTimeUpdateFromServer = Time.time;
    43.         lastTimeReceivedFromServer = msg.ReadMessage<FloatMessage>().value;
    44.     }
    45.  
    46.     float oldNetworkTime;
    47.     public float networkTime
    48.     {
    49.         get
    50.         {
    51.             if (NetworkServer.active)
    52.             {
    53.                 return Time.time;
    54.             }
    55.             else
    56.             {
    57.                 float timeSinceLastTimeSync = Time.time - lastTimeClientReceivedTimeUpdateFromServer;
    58.                 float aproximateNetworkTimeAtLastSync = lastTimeReceivedFromServer + clientRTTToServer / 2.0f;
    59.                 float newNetworkTime = aproximateNetworkTimeAtLastSync + timeSinceLastTimeSync;
    60.  
    61.                 //return newNetworkTime;
    62.  
    63.                 if (oldNetworkTime == 0)
    64.                 {
    65.                     oldNetworkTime = newNetworkTime;
    66.                     return newNetworkTime;
    67.                 }
    68.                 else
    69.                 {
    70.                     float dif = Mathf.Clamp(newNetworkTime - oldNetworkTime, 0, Time.deltaTime);
    71.  
    72.                     oldNetworkTime += dif;
    73.  
    74.                     return oldNetworkTime;
    75.                 }
    76.             }
    77.         }
    78.     }
    On the server you need to start this coroutine running for each incoming connection:
    Code (CSharp):
    1.     public IEnumerator syncNetworkTimeToClient(NetworkConnection conn)
    2.     {
    3.         while (NetworkServer.connections.Contains(conn))
    4.         {
    5.             float lastTimeServerSyncedWithClient = 0;
    6.             float lastTimeClientResponded = 0;
    7.             lastTimeServerSentTimeSyncMessageToClient.TryGetValue(conn, out lastTimeServerSyncedWithClient);
    8.             lastTimeServerReceivedRTTResponseFromClient.TryGetValue(conn, out lastTimeClientResponded);
    9.             if (lastTimeClientResponded >= lastTimeServerSyncedWithClient && Time.time - lastTimeServerSyncedWithClient > .03333f)
    10.             {
    11.                 NetworkServer.SendToClient(conn.connectionId, MsgType.TimeSync, new FloatMessage(Time.time));
    12.                 lastTimeServerSentTimeSyncMessageToClient[conn] = Time.time;
    13.             }
    14.             yield return new WaitForEndOfFrame();
    15.         }
    16.     }
    You should then be able to use the networkTime property and get something reasonably close to the same time on all clients including the host.
     
    Last edited: Apr 5, 2016
  4. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    The purpose of this clock is for synchronization. So my gameobject's time variable would reference the same clock. I need to get timestamp from the server and have it be reflected in each client.
     
  5. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    This what I have so far. Will do further tests.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.Networking;
    4.  
    5. public class NetworkPlayer : NetworkBehaviour
    6. {
    7.     public bool isNetworkTimeSynced = false;
    8.  
    9.     // timestamp received from server
    10.     private int networkTimestamp;
    11.  
    12.     // server to client delay
    13.     private int networkTimestampDelayMS;
    14.  
    15.     // when did we receive timestamp from server
    16.     private float timeReceived;
    17.  
    18.     protected virtual void Start()
    19.     {
    20.         if (isLocalPlayer)
    21.         {
    22.             CmdRequestTime();
    23.         }
    24.     }
    25.  
    26.     [Command]
    27.     private void CmdRequestTime()
    28.     {
    29.         int timestamp = NetworkTransport.GetNetworkTimestamp();
    30.         RpcNetworkTimestamp(timestamp);
    31.     }
    32.  
    33.     [ClientRpc]
    34.     private void RpcNetworkTimestamp(int timestamp)
    35.     {
    36.         isNetworkTimeSynced = true;
    37.         networkTimestamp = timestamp;
    38.         timeReceived = Time.time;
    39.  
    40.         // if client is a host, assume that there is 0 delay
    41.         if (isServer)
    42.         {
    43.             networkTimestampDelayMS = 0;
    44.         }
    45.         else
    46.         {
    47.             byte error;
    48.             networkTimestampDelayMS = NetworkTransport.GetRemoteDelayTimeMS(
    49.                 NetworkManager.singleton.client.connection.hostId,
    50.                 NetworkManager.singleton.client.connection.connectionId,
    51.                 timestamp,
    52.                 out error);
    53.         }
    54.     }
    55.  
    56.     public float GetServerTime()
    57.     {
    58.         return networkTimestamp + (networkTimestampDelayMS / 1000f) + (Time.time - timeReceived);
    59.     }
    60. }
    61.  
     
    dnnkeeper, nxrighthere and shoo like this.
  6. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    678
    You still haven't answered the question. "1. What is the purpose of this clock?" ... "The purpose of this clock is for synchronization." ...synchronization of what? Basically, after you GET the time, what're you going to do with it?
     
  7. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    @DRRosen3 I wanted to have a Mage cast a spell. It would have a 1 second casting time. If I have the network time, I could send the start cast time to the other client. And when the other client receives this info, it can perform the spell without the lag.
     
  8. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    @DRRosen3 Another instance where I could use this network/server time is correcting the position of an object. If I have an object who started moving at time T with speed S, I can compute its current position on the other clients if I know the network/server time.
     
  9. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,824
    Necro'd your own 2 year old thread. I like it :)
     
    TwoTen likes this.
  10. Driiades

    Driiades

    Joined:
    Oct 27, 2015
    Posts:
    151
    Maybe it's a bear, and was sleeping during 2 years and came back to finish his mage spell.

    hummmmm.



    (just a joke, don't beat me :'( )
     
    Hukha and unlikelysurvival like this.
  11. Moe_Baker

    Moe_Baker

    Joined:
    Oct 22, 2017
    Posts:
    34
    It's been two years, time to necro this thread again I guess
     
    Joe-Censored likes this.
  12. qbvbsite

    qbvbsite

    Joined:
    Feb 19, 2013
    Posts:
    61
    Lol, I actually have an answer for this haha.
     
  13. Moe_Baker

    Moe_Baker

    Joined:
    Oct 22, 2017
    Posts:
    34
    Nice, I got a good solution going on with .net DateTime timestamps, how are you doing it ?
     
  14. qbvbsite

    qbvbsite

    Joined:
    Feb 19, 2013
    Posts:
    61
    I use a rolling counter with each input frame which saves on bandwidth and doesn't need to be the same on the server. Works perfect for when the client needs to replay the event to reconcile the inputs. I do store the server timestamp for when I receive the inputs from the clients which can be used to roll back events server-side to verify hits and such.
     
  15. Moe_Baker

    Moe_Baker

    Joined:
    Oct 22, 2017
    Posts:
    34
    Sounds like a good solution, for me, I'm storing a DateTime timestamp of when the client received the server's time, the server's time at that point in 'time' & the round trip time.
    Then in an Update method, I subtract DateTime.Now from the timestamp and add the server's time + half of round trip time.
    Probably not the most performant approach, but, this way I can be sure that the client's time will never drift from the server's time.