无后端高效获取网络时间的组件
- 废话不多说,直接上源码
- m_NowSerivceTime 一个基于你发行游戏地区的时间偏移, 比如北京时区就是 8, 巴西就是-3,美国就是-5
- using Newtonsoft.Json; 如果这里报错, 就说明项目没有 NewtonsoftJson插件, 没关系,这里你改成Unity内置的就行
- 总之就一句, 没有必要就直接使用NTPComponent.m_NowUtc, 有时区要求就用 NTPComponent.m_NowSerivceTime, 总之,优势在我!
- 原理, 这块没必要看,如果有同学有兴趣,可以继续看看
废话不多说,直接上源码
直接新建一个脚本 NTPComponent.cs
将脚本Copy到你的项目,拖入场景节点上
获取UTC时间 NTPComponent.m_NowUtc
using UnityEngine;
using System;
using System.Collections;
using UnityEngine.Networking;
using Newtonsoft.Json;
using System.Globalization;namespace GameContent
{/// <summary>/// 启动游戏后,将所有地址列表遍历/// </summary>[DisallowMultipleComponent]public class NTPComponent : MonoBehaviour{/// <summary>/// 网络时间是否生效中/// </summary>public static bool m_IsValid { get; private set; } = true;/// <summary>/// 当前Utc时间/// </summary>public static DateTime m_NowUtc{get{return m_NowUtcServerDate.AddSeconds( ( int ) ( Time.unscaledTime - m_ServerTimePoint ) );}private set{m_NowUtcServerDate = value;}}/// <summary>/// 当前服务器时间/// </summary>public static DateTime m_NowSerivceTime{get{return m_NowUtc.AddHours( GlobalConfig.TIME_ZONE_OFFSET );}}/// <summary>/// 服务器标记时间对象/// </summary>private static DateTime m_NowUtcServerDate;/// <summary>/// 拉取服务器的标记时间尺/// </summary>private static float m_ServerTimePoint = 0f;/// <summary>/// 是否已经成功拉取到服务器时间/// </summary>private bool m_Inited = false;private void Awake( ){m_NowUtc = DateTime.UtcNow;m_ServerTimePoint = Time.unscaledTime;DontDestroyOnLoad( gameObject );}private void Start( ){
#if !SANDBOX_MODEStartCoroutine( GetNetTimeFromWorldTimeApi() );StartCoroutine( GetNetTimeFromTimeIOApi() );StartCoroutine( GetNetTimeFromGoogleApi() );#elseLog.Green( $"当前是沙盒环境,你可以更改时间 {DateTime.Now}" );
#endif}#region WebApiIEnumerator GetApi( string api, Action<string> callback ){using ( var request = UnityWebRequest.Get( "https://worldtimeapi.org/api/timezone/Etc/UTC" ) ){yield return request.SendWebRequest();try{if ( request.result == UnityWebRequest.Result.Success ){callback( request.downloadHandler.text );}//else//{// Debug.LogError( $"Failed to fetch server time: {request.error}" );//}}catch ( Exception e ){//不处理}}}struct WorldTimeData{public string datetime;public string timezone;public string utc_offset;}struct TimeIOData{public int year;public int month;public int day;public int hour;public int minute;public int seconds;public string dateTime;public string timeZone;public string dayOfWeek;public bool dstActive;}IEnumerator GetNetTimeFromWorldTimeApi( ){string result = string.Empty;yield return GetApi( "https://worldtimeapi.org/api/timezone/Etc/UTC", _ => result = _ );if ( !string.IsNullOrEmpty( result ) ){var data = JsonConvert.DeserializeObject<WorldTimeData>( result );Debug.Log( $"World Time: {data.datetime}" );if ( TryParseUTCString( data.datetime, out var utcNow ) && utcNow != DateTime.MinValue ){OnPullServerTimeOK( utcNow );}}}IEnumerator GetNetTimeFromTimeIOApi( ){string result = string.Empty;yield return GetApi( "https://timeapi.io/api/Time/current/zone?timeZone=UTC", _ => result = _ );if ( !string.IsNullOrEmpty( result ) ){var data = JsonConvert.DeserializeObject<TimeIOData>( result );Debug.Log( $"Time IO API: {data.dateTime}" );if ( TryParseUTCString( data.dateTime, out var utcNow ) && utcNow != DateTime.MinValue ){OnPullServerTimeOK( utcNow );}}}IEnumerator GetNetTimeFromGoogleApi( ){using ( var request = UnityWebRequest.Get( "https://www.google.com" ) ){yield return request.SendWebRequest();try{if ( request.result == UnityWebRequest.Result.Success ){if ( request.GetResponseHeader( "Date" ) != null ){string serverDate = request.GetResponseHeader( "Date" );Debug.Log( $"Google Server Time: {serverDate}" );var utcDate = ParseServerDateToUTC( serverDate );if ( utcDate != DateTime.MinValue ){OnPullServerTimeOK( utcDate );}}}}catch ( Exception e ){//不处理}}}#endregion#region Common/// <summary>/// Parses a UTC time string into a DateTime object./// </summary>/// <param name="utcString">The UTC time string.</param>/// <returns>A DateTime object in UTC, or DateTime.MinValue if parsing fails.</returns>public static bool TryParseUTCString( string utcString, out DateTime utcNow ){try{// Attempt to parse the string with DateTime.ParseDateTime parsedDate = DateTime.Parse( utcString, null, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal );utcNow = parsedDate;return true;}catch ( FormatException ){Debug.Log( "Failed to parse UTC time string." );utcNow = DateTime.MinValue;return false;}}/// <summary>/// Parses a server date string (from HTTP header) into a UTC DateTime object./// </summary>/// <param name="serverDate">The server date string in RFC1123 format.</param>/// <returns>A DateTime object in UTC.</returns>public static DateTime ParseServerDateToUTC( string serverDate ){try{// Use DateTime.ParseExact to parse RFC1123 formatDateTime parsedDate = DateTime.ParseExact(serverDate,"r", // "r" or "R" stands for RFC1123 patternCultureInfo.InvariantCulture,DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);return parsedDate;}catch ( FormatException ex ){Debug.Log( $"Error parsing server date: {ex.Message}" );return DateTime.MinValue; // Return MinValue to indicate failure}}/// <summary>/// 当服务器拉取到时间后调用, 锁定一次/// </summary>/// <param name="utcTime"></param>private void OnPullServerTimeOK( DateTime utcTime ){if ( m_Inited ) return;m_Inited = true;StopAllCoroutines();m_ServerTimePoint = Time.unscaledTime;m_NowUtc = utcTime;Log.Green( $"获取服务器时间成功: {utcTime}" );}#endregion}
}
m_NowSerivceTime 一个基于你发行游戏地区的时间偏移, 比如北京时区就是 8, 巴西就是-3,美国就是-5
具体根项目运营确认
//这个就是一个全局的定义,自己写一个类或者 写死一个也行
GlobalConfig.TIME_ZONE_OFFSET = 8;
using Newtonsoft.Json; 如果这里报错, 就说明项目没有 NewtonsoftJson插件, 没关系,这里你改成Unity内置的就行
改成 JsonUtility.FromJson( result ); //Unity 内置的Json库
总之就一句, 没有必要就直接使用NTPComponent.m_NowUtc, 有时区要求就用 NTPComponent.m_NowSerivceTime, 总之,优势在我!
原理, 这块没必要看,如果有同学有兴趣,可以继续看看
真实时间由两个部分组成, 一个是请求一次得到的 真实云UTC时间, 另外一个是当前游戏的秒数TimePoint
通过 基数 + 秒数偏移。 能在游戏内断网的时候有效获取到真实的云时间
需要注意的是,在游戏启动的时候,你得确保用户是联网的
当然,如果你的游戏是纯单机的,也不会报错,因为在Awake的时候默认用的是本地的TimePoint,如果你用纯单机也就不需要考虑真实的时间, 这里是能保证你的项目能在任何条件下安全的跑起来
在游戏启动的时候获取一个 UTC时间 基数
然后记录当前的游戏运行时间 Time.unscaledTime
获取当前真实的UTC时间时 => UTC时间 基数 + ( 当前游戏运行时间 - 记录时间 ) 秒数偏移