I was under the impression from reading the documentation that Network.time was kept the same for all players so that you can easily synchronise, interpolate, etc.
However this is obviously* not how it works, I’m getting vastly different Network.time values (e.g. 40174.452 on one machine and 201552.805 on another).
Is there a way to synchronise them so they stay the same at all times on all machines connected?
*You can easily reproduce this, just follow these steps on two different computers.
Download the Networking Example from the Unity site and open the project in Unity
Open Chat.js and add the following three lines:
function Update()
{
Debug.Log(Network.time);
}
Save it and run the game.
Host a game on one computer and join it on another computer (not in another window on the same machine)
(EDIT) UPDATE: I discovered a better solution than this. I’ll leave this post here in case you want to use it for some reason but see my update post a few posts down for the better solution.
I’m still interested to hear what the official solution to the problem is, but here’s mine: I wrote a script to make Network.time behave the way I want, using the synchronisation method described in this article.
Unfortunately you can’t write to Network.time, so I’ve made another static variable (NetworkTimeSync.time) which I use instead.
The script is below, if you want to use it just save it as NetworkTimeSync.js and attach it to an object that has a NetworkView.
I’ve tested it over LAN and over internet to someone in the same city, it seems to work fine - I haven’t tested it across a high latency connection though.
//Synchronised network time (NetworkTimeSync.time)
//Based on method described here: [url]http://www.mine-control.com/zack/timesync/timesync.html[/url]
var timeSendDelay : float = 2; //how often to send sync requests
var timeSinceLast : float = timeSendDelay;
var halfLatencies = Array();
static var time : double = 0;
function Start()
{
//initialize array
halfLatencies = new Array();
}
function OnConnectedToServer()
{
//clear latencies list
halfLatencies = new Array();
timeSinceLast = timeSendDelay;
}
function Update()
{
if (Network.isClient)
{
time += Time.deltaTime;
timeSinceLast += Time.deltaTime;
if (timeSinceLast > timeSendDelay)
{
networkView.RPC("SyncNetClock", RPCMode.Server, time, 0.0);
timeSinceLast = 0;
}
}
else time = Network.time; //this is the only place is your program where Network.time should appear - use NetworkTimeSync.time instead
}
var dataLength : int = 5; //whatever amount of data you want to find the median from
function recalculateAverage()
{
var avgHalfLat : float = 0;
var hLength = halfLatencies.length;
if (hLength > dataLength)
{
//find the median and discard anomalous samples
halfLatencies.Sort();
var midpoint : int = hLength * 0.5;
var median : float = halfLatencies[midpoint];
var stddev : float = 0;
for (var hL in halfLatencies)
{
stddev += (hL-median)*(hL-median);
}
stddev /= hLength;
stddev = Mathf.Sqrt(stddev);
//remove all entries above 1 stddev
var stdmin = median-stddev;
var stdmax = median+stddev;
for (var i : int = hLength-1; i>=0; i--)
{
var testval : float = halfLatencies[i];
if ((testval < stdmin)||(testval > stdmax))
{
halfLatencies.RemoveAt(i);
}
}
}
if (hLength > 0)
{
//calculate arithmetic mean
for (var hL in halfLatencies)
{
avgHalfLat += hL;
}
avgHalfLat /= hLength;
}
while (halfLatencies.length > dataLength)
{
//make sure array doesn't grow out of control
halfLatencies.Pop();
}
//Debug.Log("Average latency: "+Mathf.RoundToInt(avgHalfLat*1000)+" ms");
return avgHalfLat;
}
@RPC
function SyncNetClock(ctime : float, stime : float, info : NetworkMessageInfo)
{
if (Network.isServer)
{
//reply
networkView.RPC("SyncNetClock", info.sender, ctime, time);
}
else //if (Network.isClient)
{
var halfLatency : float = (time - ctime)/2.0;
//add to latencies list
halfLatencies.Add(halfLatency);
//compute likely average half-latency
halfLatency = recalculateAverage();
var timeDelta : float = stime - time;
time += timeDelta + halfLatency;
}
}
So after a bit of fiddling I discovered that the Network.time values are in sync, they’re just not equal. I’m unsure why this is, but it allowed me to solve the problem in a much neater way than I did above. I’ve since removed all that code I wrote to custom-synchronise.
The solution to the problem is to send relative times rather than absolute times. On the sender side, you use your Network.time to convert the time into a “time from now”. On the receiving end, use the timestamp in the NetworkMessageInfo to convert it back into absolute time again.
This also explains why my timestamp values weren’t matching on two different computers - they are given in terms of local Network.time, rather than a shared synchronised time.
should probably provide an example. I think this works, I haven’t actually tested it though.
//attach to a gameSetup object with a networkview
static var time : double;
//this time is the same on EVERYONE. Network.time is relative to the player. Network.time - info.timestamp WILL work correctly, because the timestamps are adjusted
private var netView : NetworkView;
//fixes for network.time not being same on all teh clients. Syncs itself to server's network.time
function Awake(){
netView = networkView;
if (!Network.isServer){
netView.RPC("SyncDeltaTime", RPCMode.Server);
}
else{
time = Network.time;
}
}
//keep time updated
function Update(){
if (Network.isServer) time = Network.time; //truthfully the server can always reference network.time while clients can reference netx.time, but this makes code a bit easier
else time = Network.time + deltaTime; //cause network.time is synched, just starts at different points, so just add the difference
}
//server function
@RPC
function SyncDeltaTime(info : NetworkMessageInfo){
var foo : float = Network.time; //can't rpc doubles
netView.RPC("SetDeltaSync", info.sender, foo);
}
var deltaTime : float;
//get the difference in time from local network.time to the server's time.
function SetDeltaSync(newTime : float, info : NetworkMessageInfo){
deltaTime = (newTime + (Network.time - info.timestamp)) - Network.time; //make sure we compensate for lag.
Debug.Log("Delta from network.time to server's network.time is " + deltaTime);
}
edit: facepalm. I was calculating deltaTime wrong. It’s correct now, so I believe the script works for real now.
keep in mind, equal means that the client has the same “simulated network time” as the server uses for timestamps etc so you can do any kind of interpolation etc.
it will NEVER be equal between client and server if you mean equal = the same value at the same point in time. But that wouldn’t make any sense at all as the timestamps are generated based on network.time not time.time. Also network.time automatically includes the impacts of latency etc. Time.time is fine for a lot but not for anything related to networking.
I’m very confused. I thought that was the point of synchronisation? So the server could say to all the clients “this is going to happen at time X” and then time X occurs simultaneously for players?
Thanks for your example cerebrate - is this the proper way to synchronize time over the network? I was naively thinking that I could use Network.time on all clients and the server, but it is different on every client. I modified your code slightly to provide time starting from 0 at the Awake of the server:
then NetworkStartTime.instance.time can be used anywhere for synchronized network time starting at 0 from the servers Awake().
Will I correctly calculate deltaTimes when there is a large lag? It seems to me that you had (Network.time - Network.time) in your example. The timestamp matches the time the message was sent, in the local machines network time, right?
By the way, I am using Unity 3 Beta 7, I’m under the impression that there was a bug in an earlier beta, and it was fixed. Just to clarify, is the expected behavior for Network.time to be unique on each machine?
Yeah, the network.time seems to be actually the system’s uptime of the computer it’s running on.
Anyway, Network.time - NetworkMessageInfo.timestamp is the time it took for the packet to transit, so yes, networkmessageinfo timestamps are indeed made to match the local network.time, not whoever sent it.