C# (Unity) Create Synced Network Time

1

I have been going round in circles of how to ultimately synchronize the movement of 20 objects through 60 chosen points each. I had several options the main were

  • Using sync vars to synchronize their progress between points (1 to 0), but that is open to lag and fairly intensive
  • Using unity's built in network transform, but that would seem costly in terms of networking ability as the other device only need know the points and when to start
  • First, sending the list of points, which i have successfully done and then making the devices start movement at a certain time at a fixed rate

So this is how i came to the conclusion that i needed to have a networked universal time accurate to around 0.1 seconds so that the users would see the same movement. Please if you see this as the wrong approach to this networking please let me know as I very much a beginner to networking.

So far I have tried three methods to get this synced time.

  1. I used System.DateTime believing that it was an accurate and universal time but found variation beyond a second between devices

  2. I tried to calculate the latency between the two devices so i could calulate and remove the device time variation, by

    • (A) Using the in built method of GetAveragePing(NetworkPlayer player); but this was old and depreciated as the NetworkPlayer class seemed to be no longer functional and compatible
    • (B) Using another in built method of GetCurrentRtt(int hostId, int connectionId, out byte error); which returned 0 and again i believe may be depreciated
    • (C) By sending a message from the Server to the Client and back then dividing the time taken by two but this seemed inaccurate as I was trying to calculate latency between the server and client which was not the same as between the client and server so not exactly half
  3. I tried to access a form of synced network time from a server, by

    • (A) Using code from here to get a network time then worked out the System.DateTime difference between the network date time at a certain point so that i could synchronize them. The scripts which I used to do this are below.
    • (B) Getting network time stamps from GetNetworkTimestamp(); which I thought may be what i wanted

So for all of these methods have failed so today I am asking you;

  • Is there another method for syncing time?

  • Should one of these methods work, so have I gone wrong, in which case i can give further details on my issues and code used?

  • Is my approach to networking totally wrong, or at least mostly wrong and how do you suggest i achieve my desired goal?

Thank you very much for reading this I hope it is detailed and still clear and if i can help you advance your understanding of my question i will be very happy to assist. Thanks for any answers / enlightening comments.

Code for Networked Time

Script A (Gets Network Time)

using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using UnityEngine.UI;
public class GetNetworkTime : MonoBehaviour {

    public static System.DateTime NetworkTime()
    {
        //default Windows time server
        const string ntpServer = "time.windows.com";

        // NTP message size - 16 bytes of the digest (RFC 2030)
        var ntpData = new byte[48];

        //Setting the Leap Indicator, Version Number and Mode values
        ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)

        var addresses = Dns.GetHostEntry(ntpServer).AddressList;

        //The UDP port number assigned to NTP is 123
        var ipEndPoint = new IPEndPoint(addresses[0], 123);
        //NTP uses UDP
        var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        socket.Connect(ipEndPoint);

        //Stops code hang if NTP is blocked
        socket.ReceiveTimeout = 3000;

        socket.Send(ntpData);
        socket.Receive(ntpData);
        socket.Close();

        //Offset to get to the "Transmit Timestamp" field (time at which the reply 
        //departed the server for the client, in 64-bit timestamp format."
        const byte serverReplyTime = 40;

        //Get the seconds part
        ulong intPart = System.BitConverter.ToUInt32(ntpData, serverReplyTime);

        //Get the seconds fraction
        ulong fractPart = System.BitConverter.ToUInt32(ntpData, serverReplyTime + 4);

        //Convert From big-endian to little-endian
        intPart = SwapEndianness(intPart);
        fractPart = SwapEndianness(fractPart);

        var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);

        //**UTC** time
        var networkDateTime = (new System.DateTime(1900, 1, 1, 0, 0, 0, System.DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);

        //return networkDateTime.ToLocalTime();
        return networkDateTime;
    }

    // stackoverflow.com/a/3294698/162671
    static uint SwapEndianness(ulong x)
    {
        return (uint)(((x & 0x000000ff) << 24) +
                       ((x & 0x0000ff00) << 8) +
                       ((x & 0x00ff0000) >> 8) +
                       ((x & 0xff000000) >> 24));
    }
}

Script B (Difference Calculator and Time Logger)

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;

    public class SyncTime2 : NetworkBehaviour {

        public float serverTimeDif;
        public float clientTimeDif;

        GetNetworkTime GetNetworkTime;

        GameObject time;
        Text TimeLog;

        // Use this for initialization
        void Start () {
            GetNetworkTime = GetComponent<GetNetworkTime>();
            time = GameObject.Find("time");
            TimeLog = time.GetComponent<Text>();
            if (isServer)
            {
                serverTimeDif = (float)System.DateTime.UtcNow.TimeOfDay.TotalSeconds - (float) GetNetworkTime.NetworkTime().TimeOfDay.TotalSeconds;
                StartCoroutine("DisplayTime", serverTimeDif);
            }
            else
            {
                clientTimeDif = (float)System.DateTime.UtcNow.TimeOfDay.TotalSeconds - (float)GetNetworkTime.NetworkTime().TimeOfDay.TotalSeconds;
                StartCoroutine("DisplayTime", clientTimeDif);
            }
        }

        IEnumerator DisplayTime (float TimeDif)
        {
            for (;;)
            {
                TimeLog.text = "" + ((float)System.DateTime.UtcNow.TimeOfDay.TotalSeconds + TimeDif);
                // Log time so i can see if it is different on different devices
                yield return new WaitForSeconds(0.01f);
            }
        }
    }
c#
networking
unity3d
time
synchronization
asked on Stack Overflow Oct 20, 2016 by g_l • edited May 23, 2017 by Community

1 Answer

0

Hmmmff... Seems simply adding the time difference rather than minus-ing it works and synchronizes the time, so rather than

TimeLog.text = "" + ((float)System.DateTime.UtcNow.TimeOfDay.TotalSeconds - TimeDif);

I should use

TimeLog.text = "" - ((float)System.DateTime.UtcNow.TimeOfDay.TotalSeconds + TimeDif);

If anyone could confirm that this is/isn't the correct method of networking I would deem that an answer

answered on Stack Overflow Oct 20, 2016 by g_l

User contributions licensed under CC BY-SA 3.0