一: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;
}
}
}