TimeManager 进阶优化

问题回顾

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

必备知识点

Unity中 在协同程序中使用 yield return new WaitForSeconds()​ 会受到Time.timeScale​影响
但是Unity也提供了不受其影响的 yield return new WaitForSecondsRealtime()
我们将利用它制作不受timeScale​影响的计时器

计时器模块 进阶优化

主要制作思路:

在计时器模块中,保留之前的受 Time.timeScale​ 影响的计时器
并添加一种不受其影响的计时器,让开发者可以根据需求选择使用

主要修改处:

  1. 添加一个字典容器专门记录不受其影响的计时器

    1
    2
    3
    4
    /// <summary>
    /// 用于管理所有不受Time.timeScale影响的字典容器
    /// </summary>
    private Dictionary<int, TimerItem> realTimerDic = new Dictionary<int, TimerItem>();
  2. 多开一个协同程序专门用于处理不受其影响的计时器

    至于协同程序,我们可以在原来的基础上复用,添加不受Time.timeScale​影响的挂起和遍历realTimerDic​的分支,
    因此,需要添加 是否是不受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
    /// <summary>
    /// 用于管理所有计时器的字典容器
    /// </summary>
    private Dictionary<int, TimerItem> timerDic = new Dictionary<int, TimerItem>();

    /// <summary>
    /// 用于管理所有不受Time.timeScale影响的字典容器
    /// </summary>
    private Dictionary<int, TimerItem> realTimerDic = new Dictionary<int, TimerItem>();

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

    /// <summary>
    /// 计时器管理器中唯一计时用的协程的间隔时间
    /// </summary>
    private const float intervalTimeSec = 0.1f;
    //为了避免内存的浪费,我们可以直接提前声明成员变量
    private readonly WaitForSecondsRealtime waitForSecondsRealtime = new WaitForSecondsRealtime(intervalTimeSec);
    private readonly WaitForSeconds waitForSeconds = new WaitForSeconds(intervalTimeSec);

    //计时用的协程
    private IEnumerator StartTiming(bool isRealTime, Dictionary<int, TimerItem> timerDic)
    {
    while (true)
    {
    //每隔一段时间进行一次计时
    if (isRealTime)
    yield return waitForSecondsRealtime;
    else
    yield return waitForSeconds;
    //遍历所有的计时器,进行数据更新
    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();
    }
    }
  3. 修改相关方法

    • 开启计时和关闭计时

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

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

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

      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
      /// <summary>
      /// 用于管理所有计时器的字典容器
      /// </summary>
      private Dictionary<int, TimerItem> timerDic = new Dictionary<int, TimerItem>();

      /// <summary>
      /// 用于管理所有不受Time.timeScale影响的字典容器
      /// </summary>
      private Dictionary<int, TimerItem> realTimerDic = new Dictionary<int, TimerItem>();

      /// <summary>
      /// 创建单个计时器
      /// </summary>
      /// <param name="isRealTime">是否是不受TimeScale的计时</param>
      /// <param name="allTimeMiniSec">总延迟时间(单位:毫秒)</param>
      /// <param name="overCallBack">总时间结束回调</param>
      /// <param name="intervalTimeMiniSec">间隔计时时间(单位:毫秒)</param>
      /// <param name="callBack">间隔计时时间结束回调</param>
      /// <returns>创建出来的计时器ID,用于外部控制对应计时器</returns>
      public int CreateTimer(bool isRealTime, 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);
      if (isRealTime)
      realTimerDic.Add(keyID, timerItem);
      else
      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);
      }
      else if (realTimerDic.ContainsKey(keyID))
      {
      PoolManager.Instance.PushObj(realTimerDic[keyID]);
      realTimerDic.Remove(keyID);
      }
      }

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

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

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

使用示例

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
void Update()
{
if (Input.GetKeyDown(KeyCode.T))
{
TimerManager.Instance.CreateTimer(false, 5000, () =>
{
print("5s计时结束");
}, 1000, () =>
{
print("间隔1秒");
});
}

if (Input.GetKeyDown(KeyCode.Y))
{
TimerManager.Instance.CreateTimer(true, 5000, () =>
{
print("不受Time.timeScale的5s计时结束");
}, 1000, () =>
{
print("不受Time.timeScale的间隔1秒");
});
}

if (Input.GetKeyDown(KeyCode.U))
{
Time.timeScale = Time.timeScale == 1 ? 0 : 1;
}
}

同时按下T和Y,在第二秒时令Time.timeScale = 0​,在第五秒时令Time.timeScale = 1​,观察两种消息输出的变化

输出:image

具体代码

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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>
/// 用于管理所有不受Time.timeScale影响的字典容器
/// </summary>
private Dictionary<int, TimerItem> realTimerDic = new Dictionary<int, TimerItem>();
/// <summary>
/// 待移除的列表
/// </summary>
private List<TimerItem> delList = new List<TimerItem>();

private Coroutine timer;
private Coroutine realtimer;

/// <summary>
/// 计时器管理器中唯一计时用的协程的间隔时间
/// </summary>
private const float intervalTimeSec = 0.1f;
//为了避免内存的浪费,我们可以直接提前声明成员变量
private readonly WaitForSecondsRealtime waitForSecondsRealtime = new WaitForSecondsRealtime(intervalTimeSec);
private readonly WaitForSeconds waitForSeconds = new WaitForSeconds(intervalTimeSec);

private TimerManager()
{
Start();
}

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

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

//计时用的协程
private IEnumerator StartTiming(bool isRealTime, Dictionary<int, TimerItem> timerDic)
{
while (true)
{
//每隔一段时间进行一次计时
if (isRealTime)
yield return waitForSecondsRealtime;
else
yield return waitForSeconds;
//遍历所有的计时器,进行数据更新
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="isRealTime">是否是不受TimeScale的计时</param>
/// <param name="allTimeMiniSec">总延迟时间(单位:毫秒)</param>
/// <param name="overCallBack">总时间结束回调</param>
/// <param name="intervalTimeMiniSec">间隔计时时间(单位:毫秒)</param>
/// <param name="callBack">间隔计时时间结束回调</param>
/// <returns>创建出来的计时器ID,用于外部控制对应计时器</returns>
public int CreateTimer(bool isRealTime, 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);
if (isRealTime)
realTimerDic.Add(keyID, timerItem);
else
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);
}
else if (realTimerDic.ContainsKey(keyID))
{
PoolManager.Instance.PushObj(realTimerDic[keyID]);
realTimerDic.Remove(keyID);
}
}

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

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

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