在单机游戏开发中(某些网络游戏也需要),经常需要统计玩家的登录数据,比如:登录次数、注册天数、注册日期、上次登录|退出时间、是否是新的一天登录等。
以下为通用的时间管理器:
using Newtonsoft.Json;
using SG.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class TimeManager2 : MonoSingleton<TimeManager2>
{
[Serializable]
public class TimeLocal
{
public TimeLocal()
{
var now = DateTime.Now;
mFirstLoginTicks = now.Ticks;
mLastLoginTicks = now.Ticks;
mLastQuitGameTicks = now.Ticks;
mDaysOfLogin = 1;
mDaysOfRegistration = 1;
}
//首次登录时间
public long mFirstLoginTicks = 0;
//上次打开游戏时间
public long mLastLoginTicks = 0;
//上次退出游戏时间
public long mLastQuitGameTicks = 0;
//登录天数
public int mDaysOfLogin = 0;
//注册天数
public int mDaysOfRegistration = 0;
//进入游戏回调
public void OnInGame()
{
var now = DateTime.Now;
var registerDate = new DateTime(mFirstLoginTicks);
if (!IsSameDay(now, registerDate))
{
var span = now - registerDate;
if (span.TotalSeconds > 0 && span.TotalSeconds < 24 * 60 * 60)
{
mDaysOfRegistration = 2;
}
else
{
mDaysOfRegistration = span.Days + 1;
}
}
var lastLoginTime = new DateTime(mLastLoginTicks);
if (!IsSameDay(now, lastLoginTime))
{
TimeManager2.Instance.mIsNewDay = true;
mDaysOfLogin++;
}
else
{
TimeManager2.Instance.mIsNewDay = false;
}
}
//退出游戏回调
public void OnQuitGame()
{
mLastLoginTicks = TimeManager2.Instance.mLoginTicks;
mLastQuitGameTicks = DateTime.Now.Ticks;
}
//游戏中跨天回调
public void OnAcrossDay()
{
OnInGame();
//跨天后重置登录时间
TimeManager2.Instance.mLoginTicks = DateTime.Now.Ticks;
mLastLoginTicks = TimeManager2.Instance.mLoginTicks;
mLastQuitGameTicks = TimeManager2.Instance.mLoginTicks;
}
public bool IsSameDay(DateTime d1, DateTime d2)
{
if (d1.Year == d2.Year && d1.Month == d2.Month && d1.Day == d2.Day)
{
return true;
}
return false;
}
}
string mLocalKeyOfTimeLocal = "mLocalKeyOfTimeLocal===";
//持久化时间数据
[SerializeField]
TimeLocal mTimeLocal = null;
public TimeLocal UserTimeLocal { get { return mTimeLocal; } }
[SerializeField]
long mLoginTicks = 0;
//今天是否是新的一天
public bool mIsNewDay = false;
//当前剩余时间(秒)
public int mLeftSecondToday = 0;
[SerializeField]
List<Action> mAcrossDayActions = new List<Action>();
[SerializeField]
List<Action<int>> mPerSecondActions = new List<Action<int>>();
public new void Init()
{
mLoginTicks = DateTime.Now.Ticks;
string timeLocal = PlayerPrefs.GetString(mLocalKeyOfTimeLocal, "");
if (!string.IsNullOrEmpty(timeLocal))
{
mTimeLocal = JsonConvert.DeserializeObject<TimeLocal>(timeLocal);
if (mTimeLocal.mLastQuitGameTicks < mTimeLocal.mLastLoginTicks)
{
mTimeLocal.mLastQuitGameTicks = mTimeLocal.mLastLoginTicks;
}
mTimeLocal.OnInGame();
SaveTimeLocal();
}
else
{
mTimeLocal = new TimeLocal();
SaveTimeLocal();
}
StartCoroutine(UpdateBySecond());
#if UNITY_EDITOR
UnityEditor.EditorApplication.playModeStateChanged += delegate (PlayModeStateChange state)
{
DebugUtils.Log("=====StateChanged:" + state);
if (state == PlayModeStateChange.ExitingPlayMode)
{
OnApplicationQuit();
}
};
#endif
}
//跨天回调
public void AddAcrossDayAction(Action action)
{
if (!mAcrossDayActions.Contains(action))
{
mAcrossDayActions.Add(action);
}
}
public void RemoveAcrossDayAction(Action action)
{
mAcrossDayActions.Remove(action);
}
//每秒回调,回调参数为当天剩余秒数
public void AddPerSecondAction(Action<int> action)
{
if (!mPerSecondActions.Contains(action))
{
mPerSecondActions.Add(action);
}
}
public void RemovePerSecondAction(Action action)
{
mAcrossDayActions.Remove(action);
}
IEnumerator UpdateBySecond()
{
var wait1Sec = new WaitForSeconds(1);
while (true)
{
var now = DateTime.Now;
mLeftSecondToday = (23 - now.Hour) * 60 * 60 + (59 - now.Minute) * 60 + (60 - now.Second);
DoPerSecondActions();
yield return wait1Sec;
var nextNow = DateTime.Now;
if (!mTimeLocal.IsSameDay(nextNow, now))
{
mTimeLocal.OnAcrossDay();
DoAcrossDayActions();
}
}
}
void DoAcrossDayActions()
{
foreach (var item in mAcrossDayActions)
{
try
{
item.Invoke();
}
catch (Exception e)
{
DebugUtils.LogError("TimeManager.DoAcrossDayActions: msg={0} \n stack={1}", e.Message, e.StackTrace);
}
}
}
void DoPerSecondActions()
{
foreach (var item in mPerSecondActions)
{
try
{
item.Invoke(mLeftSecondToday);
}
catch (Exception e)
{
DebugUtils.LogError("TimeManager.DoPerSecondActions: msg={0} \n stack={1}", e.Message, e.StackTrace);
}
}
}
private void OnApplicationQuit()
{
mTimeLocal.OnQuitGame();
SaveTimeLocal();
}
void SaveTimeLocal()
{
PlayerPrefs.SetString(mLocalKeyOfTimeLocal, JsonConvert.SerializeObject(mTimeLocal));
}
}