Unity实现离线计时器

一:TimeSpan

TimeSpan是C#中的一个类,常用以下几种方法

using System;
using UnityEngine;

public class Test : MonoBehaviour
{
    private void Awake()
    {
        //将TimeSpan结构的新实例初始化为指定的刻度数
        TimeSpan t1 = new TimeSpan(36); //00:00:00.0000036

        //将TimeSpan结构的新实例初始化为指定的小时数、分钟数和秒数
        TimeSpan t2 = new TimeSpan(20, 35, 21); //20:35:21

        //将TimeSpan结构的新实例初始化为指定的天数、小时数、分钟数和秒数
        TimeSpan t3 = new TimeSpan(4, 20, 35, 21); //4:20:35:21
        TimeSpan t4 = new TimeSpan(4, 24, 35, 21); //*****自动进位5:00:35:21

        //将TimeSpan结构的新实例初始化为指定的天数、小时数、分钟数、秒数和毫秒数(1秒=1000毫秒)
        TimeSpan t5 = new TimeSpan(4, 20, 35, 21, 60); //4:20:35:21:0600000

        //直接取出TimeSpan结构所表示的时间间隔的天数、小时数、分钟数、秒数和毫秒数
        TimeSpan t6 = new TimeSpan(4, 20, 35, 21, 60);
        Debug.Log(String.Format("天数:{0}\n小时数:{1}\n分钟数:{2}\n秒数:{3}\n毫秒数:{4}", t6.Days, t6.Hours, t6.Minutes, t6.Seconds,
            t6.Milliseconds)); //天数:4    小时数:20    分钟数:35    秒数:21    毫秒数:60

        //将TimeSpan结构所表示的时间间隔换算成等效天数、小时数、分钟数、秒数和毫秒数
        TimeSpan t7 = new TimeSpan(4, 20, 35, 21, 60);
        Debug.Log(String.Format("等效天数:{0}\n等效小时数:{1}\n等效分钟数:{2}\n等效秒数:{3}\n等效毫秒数:{4}", t7.TotalDays, t7.TotalHours, t7.TotalMinutes, t7.TotalSeconds,
            t7.TotalMilliseconds)); //等效天数:4.857...    等效小时数:116.58...    等效分钟数:6995.3...    等效秒数:419721...    等效毫秒数:419721060
    }
}

二:实现离线计时器

要实现离线的计时器,需要得到两个数据,一个是关闭游戏时的时间,一个是打开游戏时的时间,打开游戏的时间用DateTime类中的Now就可以得到,每次关闭游戏时的时间需要存储起来,DateTime类中有一个FromBinary方法,可以把一个long类型的字节转换为DateTime格式
将OfflineTimeManager脚本挂载到一个游戏物体身上即可

using System;
using UnityEngine;

//时间的类型
public enum TimeType
{
    Hour,
    Minute,
    Second,
}

public class OfflineTimeManager : MonoBehaviour
{
    private static OfflineTimeManager _instance;

    public static OfflineTimeManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<OfflineTimeManager>();
            }

            return _instance;
        }
    }

    /// <summary>
    /// 退出游戏存储离线时的时间
    /// </summary>
    private void OnApplicationQuit()
    {
        PlayerPrefs.SetString("LastTime", DateTime.Now.ToBinary().ToString());
    }

    /// <summary>
    /// 离开游戏存储离线时的时间,进入游戏获得离线收益
    /// </summary>
    /// <param name="isPause">是否暂停</param>
    private void OnApplicationPause(bool isPause)
    {
        if (isPause)
        {
            PlayerPrefs.SetString("LastTime", DateTime.Now.ToBinary().ToString());
        }
        else
        {
            //TODO:获得离线收益
        }
    }

    /// <summary>
    /// 得到离线的时间(得到的时间是int类型)
    /// </summary>
    /// <param name="type">时间的类型</param>
    public int GetOfflineTimeToInt(TimeType type = TimeType.Minute)
    {
        DateTime nowTime = DateTime.Now;
        long last = Convert.ToInt64(PlayerPrefs.GetString("LastTime", DateTime.Now.ToBinary().ToString()));
        DateTime lastTime = DateTime.FromBinary(last);
        TimeSpan timeSpan = nowTime.Subtract(lastTime);
        switch (type)
        {
            case TimeType.Hour:
                return (int) timeSpan.TotalHours;
            case TimeType.Minute:
                return (int) timeSpan.TotalMinutes;
            case TimeType.Second:
                return (int) timeSpan.TotalSeconds;
            default:
                return (int) timeSpan.TotalMinutes;
        }
    }

    /// <summary>
    /// 得到离线的时间(得到的时间是double类型)
    /// </summary>
    /// <param name="type">时间的类型</param>
    public double GetOfflineTimeToDouble(TimeType type = TimeType.Minute)
    {
        DateTime nowTime = DateTime.Now;
        long last = Convert.ToInt64(PlayerPrefs.GetString("LastTime", DateTime.Now.ToBinary().ToString()));
        DateTime lastTime = DateTime.FromBinary(last);
        TimeSpan timeSpan = nowTime.Subtract(lastTime);
        switch (type)
        {
            case TimeType.Hour:
                return timeSpan.TotalHours;
            case TimeType.Minute:
                return timeSpan.TotalMinutes;
            case TimeType.Second:
                return timeSpan.TotalSeconds;
            default:
                return timeSpan.TotalMinutes;
        }
    }
}

三:移动端防作弊的离线计时器

以上的写法通过修改系统时间会影响正常的离线时间计算,所以需要一个获取内部时间的类去获取当前实际时间

using UnityEngine;
using System;
using System.Runtime.InteropServices;

public class UnbiasedTime : MonoBehaviour
{
    private static UnbiasedTime instance;

    public static UnbiasedTime Instance
    {
        get
        {
            if (instance == null)
            {
                GameObject g = new GameObject("UnbiasedTimeSingleton");
                instance = g.AddComponent<UnbiasedTime>();
                DontDestroyOnLoad(g);
            }

            return instance;
        }
    }

    // Estimated difference in seconds between device time and real world time
    // timeOffset = deviceTime - worldTime;
    [HideInInspector]
    public long timeOffset = 0;

    void Awake()
    {
        SessionStart();
    }

    void OnApplicationPause(bool pause)
    {
        if (pause)
        {
            SessionEnd();
        }
        else
        {
            SessionStart();
        }
    }

    void OnApplicationQuit()
    {
        SessionEnd();
    }

    // Returns estimated DateTime value taking into account possible device time changes
    public DateTime Now()
    {
        return DateTime.Now.AddSeconds(-1.0f * timeOffset);
    }

    // timeOffset value is cached for performance reasons (calls to native plugins can be expensive). 
    // This method is used to update offset value in cases if you think device time was changed by user. 
    // 
    // However, time offset is updated automatically when app gets backgrounded or foregrounded. 
    // 
    public void UpdateTimeOffset()
    {
#if UNITY_ANDROID
			UpdateTimeOffsetAndroid();
#elif UNITY_IPHONE
        UpdateTimeOffsetIOS();
#endif
    }

    // Returns true if native plugin was unable to calculate unbiased time and had fallen back to device DateTime. 
    // This can happen after device reboot. Player can cheat by closing the game, changing time and rebooting device. 
    // This method can help tracking this situation. 
    public bool IsUsingSystemTime()
    {
#if UNITY_ANDROID
			return UsingSystemTimeAndroid();
#elif UNITY_IPHONE
        return UsingSystemTimeIOS();
#else
			return true;
#endif
    }

    private void SessionStart()
    {
#if UNITY_ANDROID
			StartAndroid();
#elif UNITY_IPHONE
        StartIOS();
#endif
    }

    private void SessionEnd()
    {
#if UNITY_ANDROID
			EndAndroid();
#elif UNITY_IPHONE
        EndIOS();
#endif
    }

    // Platform specific code
    // 

#if UNITY_IPHONE
    [DllImport("__Internal")]
    private static extern void _vtcOnSessionStart();

    [DllImport("__Internal")]
    private static extern void _vtcOnSessionEnd();

    [DllImport("__Internal")]
    private static extern int _vtcTimestampOffset();

    [DllImport("__Internal")]
    private static extern int _vtcUsingSystemTime();

    private void UpdateTimeOffsetIOS()
    {
        if (Application.platform != RuntimePlatform.IPhonePlayer)
        {
            return;
        }

        timeOffset = _vtcTimestampOffset();
    }

    private void StartIOS()
    {
        if (Application.platform != RuntimePlatform.IPhonePlayer)
        {
            return;
        }

        _vtcOnSessionStart();
        timeOffset = _vtcTimestampOffset();
    }

    private void EndIOS()
    {
        if (Application.platform != RuntimePlatform.IPhonePlayer)
        {
            return;
        }

        _vtcOnSessionEnd();
    }

    private bool UsingSystemTimeIOS()
    {
        if (Application.platform != RuntimePlatform.IPhonePlayer)
        {
            return true;
        }

        return _vtcUsingSystemTime() != 0;
    }
#endif


#if UNITY_ANDROID
	private void UpdateTimeOffsetAndroid() {
		if (Application.platform != RuntimePlatform.Android) {
			return;
		}

		using (var activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) 
		using (var unbiasedTimeClass = new AndroidJavaClass("com.vasilij.unbiasedtime.UnbiasedTime")) {
			var playerActivityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
			if (playerActivityContext != null && unbiasedTimeClass != null) {
				timeOffset = unbiasedTimeClass.CallStatic <long> ("vtcTimestampOffset", playerActivityContext);
			}
		}		
	}

	private void StartAndroid() {
		if (Application.platform != RuntimePlatform.Android) {
			return;
		}

		using (var activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
		using (var unbiasedTimeClass = new AndroidJavaClass("com.vasilij.unbiasedtime.UnbiasedTime")) {
			var playerActivityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
			if (playerActivityContext != null && unbiasedTimeClass != null) {
				unbiasedTimeClass.CallStatic ("vtcOnSessionStart", playerActivityContext);
				timeOffset = unbiasedTimeClass.CallStatic <long> ("vtcTimestampOffset");
			}
		}
	}

	private void EndAndroid() {
		if (Application.platform != RuntimePlatform.Android) {
			return;
		}

		using (var activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) 
		using (var unbiasedTimeClass = new AndroidJavaClass("com.vasilij.unbiasedtime.UnbiasedTime")) {
			var playerActivityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
			if (playerActivityContext != null && unbiasedTimeClass != null) {
				unbiasedTimeClass.CallStatic ("vtcOnSessionEnd", playerActivityContext);
			}
		}
	}

	private bool UsingSystemTimeAndroid() {
		if (Application.platform != RuntimePlatform.Android) {
			return true;
		}
		
		using (var activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) 
		using (var unbiasedTimeClass = new AndroidJavaClass("com.vasilij.unbiasedtime.UnbiasedTime")) {
			var playerActivityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
			if (playerActivityContext != null && unbiasedTimeClass != null) {
				return unbiasedTimeClass.CallStatic <bool> ("vtcUsingDeviceTime");
			}
		}
		return true;
	}
#endif
}

之后修改一下之前的OfflineTimeManager类,将OfflineTimeManager脚本挂载到一个游戏物体身上即可

using System;
using UnityEngine;

//时间的类型
public enum TimeType
{
    Hour,
    Minute,
    Second,
}

public class OfflineTimeManager : MonoBehaviour
{
    private static OfflineTimeManager _instance;

    public static OfflineTimeManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<OfflineTimeManager>();
            }

            return _instance;
        }
    }

    /// <summary>
    /// 退出游戏存储离线时的时间
    /// </summary>
    private void OnApplicationQuit()
    {
        PlayerPrefs.SetString("LastTime", DateTime.Now.ToBinary().ToString());
    }

    /// <summary>
    /// 离开游戏存储离线时的时间,进入游戏获得离线收益
    /// </summary>
    /// <param name="isPause">是否暂停</param>
    private void OnApplicationPause(bool isPause)
    {
        if (isPause)
        {
            PlayerPrefs.SetString("LastTime", DateTime.Now.ToBinary().ToString());
        }
        else
        {
            //TODO:获得离线收益
        }
    }

    /// <summary>
    /// 得到离线的时间(得到的时间是int类型)
    /// </summary>
    /// <param name="type">时间的类型</param>
    public int GetOfflineTimeToInt(TimeType type = TimeType.Minute)
    {
        DateTime nowTime = UnbiasedTime.Instance.Now();
        long last = Convert.ToInt64(PlayerPrefs.GetString("LastTime", nowTime.ToBinary().ToString()));
        DateTime lastTime = DateTime.FromBinary(last);
        TimeSpan timeSpan = nowTime.Subtract(lastTime);
        switch (type)
        {
            case TimeType.Hour:
                return (int) timeSpan.TotalHours;
            case TimeType.Minute:
                return (int) timeSpan.TotalMinutes;
            case TimeType.Second:
                return (int) timeSpan.TotalSeconds;
            default:
                return (int) timeSpan.TotalMinutes;
        }
    }

    /// <summary>
    /// 得到离线的时间(得到的时间是double类型)
    /// </summary>
    /// <param name="type">时间的类型</param>
    public double GetOfflineTimeToDouble(TimeType type = TimeType.Minute)
    {
        DateTime nowTime = UnbiasedTime.Instance.Now();
        long last = Convert.ToInt64(PlayerPrefs.GetString("LastTime", nowTime.ToBinary().ToString()));
        DateTime lastTime = DateTime.FromBinary(last);
        TimeSpan timeSpan = nowTime.Subtract(lastTime);
        switch (type)
        {
            case TimeType.Hour:
                return timeSpan.TotalHours;
            case TimeType.Minute:
                return timeSpan.TotalMinutes;
            case TimeType.Second:
                return timeSpan.TotalSeconds;
            default:
                return timeSpan.TotalMinutes;
        }
    }
}
发布了139 篇原创文章 · 获赞 292 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/LLLLL__/article/details/104315929
今日推荐