UFL10-2——TimeManager 具体实现

TimeManager 具体实现

1
2
3
4
public class TimerManager : BaseManager<TimerManager>
{
private TimerManager() { }
}
  • 需要声明的关键成员

    1. 计时器字典容器

      1
      2
      3
      4
      /// <summary>
      /// 用于管理所有计时器的字典容器
      /// </summary>
      private Dictionary<int, TimerItem> timerDic = new Dictionary<int, TimerItem>();
    2. 待移除计时器列表容器

      1
      2
      3
      4
      /// <summary>
      /// 待移除的列表
      /// </summary>
      private List<TimerItem> delList = new List<TimerItem>();

    等等

  • 需要实现的关键方法

    1. 开启计时器管理器(实例化时就执行)

      这里的计时逻辑由协程执行,因此需要声明一个协程变量,来管理开启时返的协程,便于关闭计时

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      private Coroutine timer;

      private TimerManager()
      {
      Start();
      }

      //开启计时器管理器的方法
      public void Start()
      {
      timer = MonoManager.Instance.StartCoroutine(StartTiming());
      }

    2. 关闭计时器管理器

      1
      2
      3
      4
      5
      6
      7
      private Coroutine timer;

      //关闭计时器管理器的方法
      public void Stop()
      {
      MonoManager.Instance.StopCoroutine(timer);
      }
    3. 计时协程

      可以通过修改 intervalTime​ 来修改每隔多少时间记一次时,间隔越短精确度越高,但性能也越差
      每记一次时间以后,就可以遍历 timerDic​ 内的计时器对象,

      如果该对象有间隔时间要执行的委托,先对间隔时间减去经过的时间,再检测间隔时间是否归0,
      如果归0,就执行需要间隔执行的委托,并重置间隔时间

      然后对总时间减去经过的时间,再检测总时间是否归0,
      如果归0,执行最终要执行的委托,然后将该计时器对象移入到待移除列表delList​内

      然后将待移除列表内的计时器对象从 timerDic​ 内,并压入缓存池,最后清空待移除列表

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      /// <summary>
      /// 用于管理所有计时器的字典容器
      /// </summary>
      private Dictionary<int, TimerItem> timerDic = new Dictionary<int, TimerItem>();

      /// <summary>
      /// 待移除的列表
      /// </summary>
      private List<TimerItem> delList = new List<TimerItem>();

      private Coroutine timer;

      /// <summary>
      /// 计时器管理器中唯一计时用的协程的间隔时间
      /// </summary>
      private const float intervalTimeSec = 0.1f;

      //计时用的协程
      private IEnumerator StartTiming()
      {
      while (true)
      {
      //每隔一段时间进行一次计时
      yield return new WaitForSeconds(intervalTimeSec);
      foreach (TimerItem item in timerDic.Values)
      {
      if (!item.isRunning)
      continue;
      //判断计时对象是否有间隔时间执行的需求
      if (item.callBack != null)
      {
      //减去经过的间隔时间
      item.intervalTimeMiniSec -= (int)(intervalTimeSec * 1000);
      if (item.intervalTimeMiniSec <= 0)
      {
      item.callBack.Invoke();
      item.intervalTimeMiniSec = item.maxIntervalTimeMiniSec;
      }
      }
      //总的时间更新
      item.allTimeMiniSec -= (int)(intervalTimeSec * 1000);
      //计时时间到,需要执行完成回调函数
      if (item.allTimeMiniSec <= 0)
      {
      item.overCallBack.Invoke();
      delList.Add(item);
      }
      }
      //移除待移除列表中的数据
      for (int i = 0; i < delList.Count; i++)
      {
      //从字典中移除
      timerDic.Remove(delList[i].keyID);
      //放入到缓存池中
      PoolManager.Instance.PushObj(delList[i]);
      }
      //移除结束后清空列表
      delList.Clear();
      }
      }
    4. 创建单个计时器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      /// <summary>
      /// 用于记录当前将要创建的计时器对象唯一ID
      /// </summary>
      private int TIMER_KEY = 0;

      /// <summary>
      /// 创建单个计时器
      /// </summary>
      /// <param name="allTimeMiniSec">总延迟时间(单位:毫秒)</param>
      /// <param name="overCallBack">总时间结束回调</param>
      /// <param name="intervalTimeMiniSec">间隔计时时间(单位:毫秒)</param>
      /// <param name="callBack">间隔计时时间结束回调</param>
      /// <returns>创建出来的计时器ID,用于外部控制对应计时器</returns>
      public int CreateTimer(int allTimeMiniSec, UnityAction overCallBack, int intervalTimeMiniSec = 0, UnityAction callBack = null)
      {
      int keyID = ++TIMER_KEY;
      //从缓存池取出对应的计时器,并初始化数据,然后记录到字典内
      TimerItem timerItem = PoolManager.Instance.GetObj<TimerItem>();
      timerItem.InitInfo(keyID, allTimeMiniSec, overCallBack, intervalTimeMiniSec, callBack);
      timerDic.Add(keyID, timerItem);
      return keyID;
      }
    5. 移除单个计时器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      /// <summary>
      /// 移除单个计时器
      /// </summary>
      /// <param name="keyID">要移除的计时器对象ID</param>
      public void RemoveTimer(int keyID)
      {
      if (timerDic.ContainsKey(keyID))
      {
      PoolManager.Instance.PushObj(timerDic[keyID]);
      timerDic.Remove(keyID);
      }
      }
    6. 重置单个计时器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /// <summary>
      /// 重置单个计时器
      /// </summary>
      /// <param name="keyID">要重置计时的计时器对象ID</param>
      public void ResetTimer(int keyID)
      {
      if (timerDic.ContainsKey(keyID))
      {
      timerDic[keyID].ResetTimer();
      }
      }
    7. 开启单个计时器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /// <summary>
      /// 开启单个计时器
      /// </summary>
      /// <param name="keyID">要开始计时的计时器对象ID</param>
      public void StartTimer(int keyID)
      {
      if (timerDic.ContainsKey(keyID))
      {
      timerDic[keyID].isRunning = true;
      }
      }
    8. 停止单个计时器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /// <summary>
      /// 停止单个计时器的计时
      /// </summary>
      /// <param name="keyID">要停止计时的计时器对象ID</param>
      public void StopTimer(int keyID)
      {
      if (timerDic.ContainsKey(keyID))
      {
      timerDic[keyID].isRunning = false;
      }
      }

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private int timerID;

void Update()
{
if (Input.GetKeyDown(KeyCode.T))
{
timerID = TimerManager.Instance.CreateTimer(5000, () =>
{
print("5s计时结束");
}, 1000, () =>
{
print("间隔1秒");
});
}

if (Input.GetKeyDown(KeyCode.Y))
{
TimerManager.Instance.RemoveTimer(timerID);
}
}

按下T后输出,中途如果按下Y就会停止输出:image

抛出问题

目前的计时器管理器会受到 Time.timeScale​ 的影响,我们应该如何让计时器管理器可以自己决定是否受到它的影响呢?

具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 计时器管理器,主要用于开启,停止,重置等等操作管理计时器
/// </summary>
public class TimerManager : BaseManager<TimerManager>
{
/// <summary>
/// 用于记录当前将要创建的计时器对象唯一ID
/// </summary>
private int TIMER_KEY = 0;
/// <summary>
/// 用于管理所有计时器的字典容器
/// </summary>
private Dictionary<int, TimerItem> timerDic = new Dictionary<int, TimerItem>();
/// <summary>
/// 待移除的列表
/// </summary>
private List<TimerItem> delList = new List<TimerItem>();

private Coroutine timer;

/// <summary>
/// 计时器管理器中唯一计时用的协程的间隔时间
/// </summary>
private const float intervalTimeSec = 0.1f;

private TimerManager()
{
Start();
}

//开启计时器管理器的方法
public void Start()
{
timer = MonoManager.Instance.StartCoroutine(StartTiming());
}

//关闭计时器管理器的方法
public void Stop()
{
MonoManager.Instance.StopCoroutine(timer);
}

//计时用的协程
private IEnumerator StartTiming()
{
while (true)
{
//每隔一段时间进行一次计时
yield return new WaitForSeconds(intervalTimeSec);
foreach (TimerItem item in timerDic.Values)
{
//如果计时器需要计时,才执行后边的逻辑
if (!item.isRunning)
continue;
//判断计时对象是否有间隔时间执行的需求
if (item.callBack != null)
{
//减去经过的间隔时间
item.intervalTimeMiniSec -= (int)(intervalTimeSec * 1000);
if (item.intervalTimeMiniSec <= 0)
{
item.callBack.Invoke();
item.intervalTimeMiniSec = item.maxIntervalTimeMiniSec;
}
}
//总的时间更新
item.allTimeMiniSec -= (int)(intervalTimeSec * 1000);
//计时时间到,需要执行完成回调函数
if (item.allTimeMiniSec <= 0)
{
item.overCallBack.Invoke();
delList.Add(item);
}
}
//移除待移除列表中的数据
for (int i = 0; i < delList.Count; i++)
{
//从字典中移除
timerDic.Remove(delList[i].keyID);
//放入到缓存池中
PoolManager.Instance.PushObj(delList[i]);
}
//移除结束后清空列表
delList.Clear();
}
}

/// <summary>
/// 创建单个计时器
/// </summary>
/// <param name="allTimeMiniSec">总延迟时间(单位:毫秒)</param>
/// <param name="overCallBack">总时间结束回调</param>
/// <param name="intervalTimeMiniSec">间隔计时时间(单位:毫秒)</param>
/// <param name="callBack">间隔计时时间结束回调</param>
/// <returns>创建出来的计时器ID,用于外部控制对应计时器</returns>
public int CreateTimer(int allTimeMiniSec, UnityAction overCallBack, int intervalTimeMiniSec = 0, UnityAction callBack = null)
{
int keyID = ++TIMER_KEY;
//从缓存池取出对应的计时器,并初始化数据,然后记录到字典内
TimerItem timerItem = PoolManager.Instance.GetObj<TimerItem>();
timerItem.InitInfo(keyID, allTimeMiniSec, overCallBack, intervalTimeMiniSec, callBack);
timerDic.Add(keyID, timerItem);
return keyID;
}

/// <summary>
/// 移除单个计时器
/// </summary>
/// <param name="keyID">要移除的计时器对象ID</param>
public void RemoveTimer(int keyID)
{
if (timerDic.ContainsKey(keyID))
{
PoolManager.Instance.PushObj(timerDic[keyID]);
timerDic.Remove(keyID);
}
}

/// <summary>
/// 重置单个计时器
/// </summary>
/// <param name="keyID">要重置计时的计时器对象ID</param>
public void ResetTimer(int keyID)
{
if (timerDic.ContainsKey(keyID))
{
timerDic[keyID].ResetTimer();
}
}

/// <summary>
/// 开启单个计时器
/// </summary>
/// <param name="keyID">要开始计时的计时器对象ID</param>
public void StartTimer(int keyID)
{
if (timerDic.ContainsKey(keyID))
{
timerDic[keyID].isRunning = true;
}
}

/// <summary>
/// 停止单个计时器的计时
/// </summary>
/// <param name="keyID">要停止计时的计时器对象ID</param>
public void StopTimer(int keyID)
{
if (timerDic.ContainsKey(keyID))
{
timerDic[keyID].isRunning = false;
}
}
}