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.
host id out of bound id {-1} max id should be greater 0 and less than {1}
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.
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.
public void roundTripTimePong(NetworkMessage msg)
{
if (clientRTTToServer == 0)
{
clientRTTToServer = Time.time - lastTimeClientReceivedTimeUpdateFromServer;
}
else
{
clientRTTToServer = (clientRTTToServer + Time.time - lastTimeClientReceivedTimeUpdateFromServer) / 2.0f;
}
}
// Called on the server to calculate the round trip time from the client
public void roundTripTimePing(NetworkMessage msg)
{
if (NetworkServer.active)
{
if (serverRTTToClients.ContainsKey(msg.conn))
{
// If we already have a rtt recorded for this connection the new rount trip time is the old time
// averaged with the new time that was just calculated.
serverRTTToClients[msg.conn] = (serverRTTToClients[msg.conn] + (Time.time - lastTimeServerSentTimeSyncMessageToClient[msg.conn])) / 2.0f;
}
else
{
// If there is no rtt recorded yet for this connection then we use the time we just calculated
serverRTTToClients[msg.conn] = (Time.time - lastTimeServerSentTimeSyncMessageToClient[msg.conn]);
}
lastTimeServerReceivedRTTResponseFromClient[msg.conn] = Time.time;
// Server sends RTT message back to client so that the client can calculate rtt from server
msg.conn.Send(MsgType.RoundTripTimePong, new EmptyMessage());
}
}
public void syncNetworkTime(NetworkMessage msg)
{
// Clients send a message back to the server so that it can calculate round trip time.
msg.conn.Send(MsgType.RoundTripTimePing, new EmptyMessage());
// Store the time received from the server so that we can use it, along with the rtt, to estimate the server time
lastTimeClientReceivedTimeUpdateFromServer = Time.time;
lastTimeReceivedFromServer = msg.ReadMessage<FloatMessage>().value;
}
float oldNetworkTime;
public float networkTime
{
get
{
if (NetworkServer.active)
{
return Time.time;
}
else
{
float timeSinceLastTimeSync = Time.time - lastTimeClientReceivedTimeUpdateFromServer;
float aproximateNetworkTimeAtLastSync = lastTimeReceivedFromServer + clientRTTToServer / 2.0f;
float newNetworkTime = aproximateNetworkTimeAtLastSync + timeSinceLastTimeSync;
//return newNetworkTime;
if (oldNetworkTime == 0)
{
oldNetworkTime = newNetworkTime;
return newNetworkTime;
}
else
{
float dif = Mathf.Clamp(newNetworkTime - oldNetworkTime, 0, Time.deltaTime);
oldNetworkTime += dif;
return oldNetworkTime;
}
}
}
}
On the server you need to start this coroutine running for each incoming connection:
public IEnumerator syncNetworkTimeToClient(NetworkConnection conn)
{
while (NetworkServer.connections.Contains(conn))
{
float lastTimeServerSyncedWithClient = 0;
float lastTimeClientResponded = 0;
lastTimeServerSentTimeSyncMessageToClient.TryGetValue(conn, out lastTimeServerSyncedWithClient);
lastTimeServerReceivedRTTResponseFromClient.TryGetValue(conn, out lastTimeClientResponded);
if (lastTimeClientResponded >= lastTimeServerSyncedWithClient && Time.time - lastTimeServerSyncedWithClient > .03333f)
{
NetworkServer.SendToClient(conn.connectionId, MsgType.TimeSync, new FloatMessage(Time.time));
lastTimeServerSentTimeSyncMessageToClient[conn] = Time.time;
}
yield return new WaitForEndOfFrame();
}
}
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.
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.
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
public class NetworkPlayer : NetworkBehaviour
{
public bool isNetworkTimeSynced = false;
// timestamp received from server
private int networkTimestamp;
// server to client delay
private int networkTimestampDelayMS;
// when did we receive timestamp from server
private float timeReceived;
protected virtual void Start()
{
if (isLocalPlayer)
{
CmdRequestTime();
}
}
[Command]
private void CmdRequestTime()
{
int timestamp = NetworkTransport.GetNetworkTimestamp();
RpcNetworkTimestamp(timestamp);
}
[ClientRpc]
private void RpcNetworkTimestamp(int timestamp)
{
isNetworkTimeSynced = true;
networkTimestamp = timestamp;
timeReceived = Time.time;
// if client is a host, assume that there is 0 delay
if (isServer)
{
networkTimestampDelayMS = 0;
}
else
{
byte error;
networkTimestampDelayMS = NetworkTransport.GetRemoteDelayTimeMS(
NetworkManager.singleton.client.connection.hostId,
NetworkManager.singleton.client.connection.connectionId,
timestamp,
out error);
}
}
public float GetServerTime()
{
return networkTimestamp + (networkTimestampDelayMS / 1000f) + (Time.time - timeReceived);
}
}
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?
@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.
@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.
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.
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.