设计一套能够统一响应暂停事件的协程管理机制需要考虑多个方面,包括协程的生命周期管理、暂停状态同步、不同类型协程的处理等。以下是一个完整的设计方案:
1. 核心架构设计
协程管理器类
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoroutineManager : MonoBehaviour
{
// 单例模式
private static CoroutineManager _instance;
public static CoroutineManager Instance
{
get
{
if (_instance == null)
{
GameObject go = new GameObject("[CoroutineManager]");
_instance = go.AddComponent<CoroutineManager>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
// 暂停状态
private bool _isPaused = false;
public bool IsPaused => _isPaused;
// 协程分类
public enum CoroutineType
{
Standard, // 标准协程,暂停时冻结
Unscaled, // 不受时间缩放影响的协程
PauseImmune, // 免疫暂停的协程(即使在暂停时也会继续运行)
}
// 协程信息结构
private class CoroutineInfo
{
public Coroutine Coroutine;
public CoroutineType Type;
public string Tag;
public MonoBehaviour Owner;
public IEnumerator OriginalRoutine;
public bool IsRunning;
}
// 存储所有活动协程
private List<CoroutineInfo> _activeCoroutines = new List<CoroutineInfo>();
// 协程完成事件
public event Action<string> OnCoroutineCompleted;
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
}
// 暂停所有受影响的协程
public void PauseCoroutines()
{
if (_isPaused) return;
_isPaused = true;
// 处理每个协程
for (int i = _activeCoroutines.Count - 1; i >= 0; i--)
{
var info = _activeCoroutines[i];
// 只处理标准类型的协程
if (info.Type == CoroutineType.Standard && info.IsRunning)
{
StopCoroutine(info.Coroutine);
info.IsRunning = false;
}
}
}
// 恢复所有暂停的协程
public void ResumeCoroutines()
{
if (!_isPaused) return;
_isPaused = false;
// 恢复每个暂停的协程
for (int i = _activeCoroutines.Count - 1; i >= 0; i--)
{
var info = _activeCoroutines[i];
// 只恢复标准类型的协程
if (info.Type == CoroutineType.Standard && !info.IsRunning)
{
// 重新启动协程
info.Coroutine = StartCoroutine(RunManagedCoroutine(info));
info.IsRunning = true;
}
}
}
// 启动受管理的协程
public Coroutine StartManagedCoroutine(IEnumerator routine, CoroutineType type = CoroutineType.Standard, string tag = "")
{
CoroutineInfo info = new CoroutineInfo
{
Type = type,
Tag = tag,
Owner = this,
OriginalRoutine = routine,
IsRunning = true
};
info.Coroutine = StartCoroutine(RunManagedCoroutine(info));
_activeCoroutines.Add(info);
return info.Coroutine;
}
// 为特定MonoBehaviour启动受管理的协程
public Coroutine StartManagedCoroutine(MonoBehaviour owner, IEnumerator routine, CoroutineType type = CoroutineType.Standard, string tag = "")
{
if (owner == null)
{
Debug.LogError("Cannot start coroutine on null MonoBehaviour");
return null;
}
CoroutineInfo info = new CoroutineInfo
{
Type = type,
Tag = tag,
Owner = owner,
OriginalRoutine = routine,
IsRunning = true
};
info.Coroutine = StartCoroutine(RunManagedCoroutine(info));
_activeCoroutines.Add(info);
return info.Coroutine;
}
// 协程包装器
private IEnumerator RunManagedCoroutine(CoroutineInfo info)
{
IEnumerator routine = info.OriginalRoutine;
bool finished = false;
while (!finished)
{
// 处理暂停逻辑
if (_isPaused && info.Type == CoroutineType.Standard)
{
yield break; // 暂停时退出,会在恢复时重新启动
}
// 尝试前进协程
try
{
if (routine.MoveNext())
{
// 处理不同类型的yield指令
object current = routine.Current;
if (current is WaitForSeconds && info.Type != CoroutineType.Unscaled)
{
// 替换为自定义的等待,以便正确处理暂停
float waitTime = GetWaitForSecondsTime(current as WaitForSeconds);
yield return StartCoroutine(WaitForSecondsWithPause(waitTime));
}
else
{
yield return current;
}
}
else
{
finished = true;
}
}
catch (Exception e)
{
Debug.LogError($"Error in coroutine {info.Tag}: {e.Message}\n{e.StackTrace}");
finished = true;
}
}
// 协程完成,从活动列表中移除
_activeCoroutines.Remove(info);
// 触发完成事件
if (!string.IsNullOrEmpty(info.Tag))
{
OnCoroutineCompleted?.Invoke(info.Tag);
}
}
// 自定义的WaitForSeconds,考虑暂停状态
private IEnumerator WaitForSecondsWithPause(float seconds)
{
float timer = 0;
while (timer < seconds)
{
if (!_isPaused)
{
timer += Time.deltaTime;
}
yield return null;
}
}
// 从WaitForSeconds对象中提取等待时间
private float GetWaitForSecondsTime(WaitForSeconds waitObject)
{
// 使用反射获取WaitForSeconds中的时间值
System.Reflection.FieldInfo fieldInfo = waitObject.GetType().GetField("m_Seconds",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (fieldInfo != null)
{
return (float)fieldInfo.GetValue(waitObject);
}
return 0.1f; // 默认值
}
// 停止特定标签的所有协程
public void StopCoroutinesByTag(string tag)
{
if (string.IsNullOrEmpty(tag)) return;
for (int i = _activeCoroutines.Count - 1; i >= 0; i--)
{
var info = _activeCoroutines[i];
if (info.Tag == tag)
{
if (info.IsRunning)
{
StopCoroutine(info.Coroutine);
}
_activeCoroutines.RemoveAt(i);
}
}
}
// 停止特定所有者的所有协程
public void StopAllCoroutinesOfOwner(MonoBehaviour owner)
{
if (owner == null) return;
for (int i = _activeCoroutines.Count - 1; i >= 0; i--)
{
var info = _activeCoroutines[i];
if (info.Owner == owner)
{
if (info.IsRunning)
{
StopCoroutine(info.Coroutine);
}
_activeCoroutines.RemoveAt(i);
}
}
}
// 清理无效协程
public void CleanupDeadCoroutines()
{
for (int i = _activeCoroutines.Count - 1; i >= 0; i--)
{
var info = _activeCoroutines[i];
if (info.Owner == null || !info.Owner.gameObject.activeInHierarchy)
{
if (info.IsRunning)
{
StopCoroutine(info.Coroutine);
}
_activeCoroutines.RemoveAt(i);
}
}
}
// 获取活动协程数量
public int GetActiveCoroutineCount()
{
return _activeCoroutines.Count;
}
// 获取特定标签的协程数量
public int GetCoroutineCountByTag(string tag)
{
int count = 0;
foreach (var info in _activeCoroutines)
{
if (info.Tag == tag)
{
count++;
}
}
return count;
}
}
自定义Yield指令
// 不受暂停影响的等待时间
public class WaitForSecondsUnaffectedByPause : CustomYieldInstruction
{
private float _targetTime;
public WaitForSecondsUnaffectedByPause(float seconds)
{
_targetTime = Time.unscaledTime + seconds;
}
public override bool keepWaiting
{
get { return Time.unscaledTime < _targetTime; }
}
}
// 可暂停的等待时间
public class WaitForSecondsPausable : CustomYieldInstruction
{
private float _duration;
private float _timer;
public WaitForSecondsPausable(float seconds)
{
_duration = seconds;
_timer = 0;
}
public override bool keepWaiting
{
get
{
if (!CoroutineManager.Instance.IsPaused)
{
_timer += Time.deltaTime;
}
return _timer < _duration;
}
}
}
// 等待直到游戏恢复
public class WaitUntilGameResumes : CustomYieldInstruction
{
public override bool keepWaiting
{
get { return CoroutineManager.Instance.IsPaused; }
}
}
2. 与游戏暂停系统集成
public class GamePauseManager : MonoBehaviour
{
private bool _isPaused = false;
public bool IsPaused => _isPaused;
// 暂停事件
public event Action OnGamePaused;
public event Action OnGameResumed;
public void PauseGame()
{
if (_isPaused) return;
_isPaused = true;
Time.timeScale = 0f;
// 暂停所有协程
CoroutineManager.Instance.PauseCoroutines();
// 触发暂停事件
OnGamePaused?.Invoke();
}
public void ResumeGame()
{
if (!_isPaused) return;
_isPaused = false;
Time.timeScale = 1f;
// 恢复所有协程
CoroutineManager.Instance.ResumeCoroutines();
// 触发恢复事件
OnGameResumed?.Invoke();
}
public void TogglePause()
{
if (_isPaused)
{
ResumeGame();
}
else
{
PauseGame();
}
}
}
3. 扩展功能:协程组和优先级
// 在CoroutineManager类中添加
// 协程组
private Dictionary<string, List<CoroutineInfo>> _coroutineGroups = new Dictionary<string, List<CoroutineInfo>>();
// 启动协程并添加到组
public Coroutine StartCoroutineInGroup(IEnumerator routine, string groupName, CoroutineType type = CoroutineType.Standard, string tag = "")
{
CoroutineInfo info = new CoroutineInfo
{
Type = type,
Tag = tag,
Owner = this,
OriginalRoutine = routine,
IsRunning = true
};
info.Coroutine = StartCoroutine(RunManagedCoroutine(info));
_activeCoroutines.Add(info);
// 添加到组
if (!_coroutineGroups.ContainsKey(groupName))
{
_coroutineGroups[groupName] = new List<CoroutineInfo>();
}
_coroutineGroups[groupName].Add(info);
return info.Coroutine;
}
// 暂停特定组的协程
public void PauseCoroutineGroup(string groupName)
{
if (!_coroutineGroups.ContainsKey(groupName)) return;
foreach (var info in _coroutineGroups[groupName])
{
if (info.IsRunning && info.Type == CoroutineType.Standard)
{
StopCoroutine(info.Coroutine);
info.IsRunning = false;
}
}
}
// 恢复特定组的协程
public void ResumeCoroutineGroup(string groupName)
{
if (!_coroutineGroups.ContainsKey(groupName)) return;
foreach (var info in _coroutineGroups[groupName])
{
if (!info.IsRunning && info.Type == CoroutineType.Standard)
{
info.Coroutine = StartCoroutine(RunManagedCoroutine(info));
info.IsRunning = true;
}
}
}
// 停止特定组的所有协程
public void StopCoroutineGroup(string groupName)
{
if (!_coroutineGroups.ContainsKey(groupName)) return;
foreach (var info in _coroutineGroups[groupName])
{
if (info.IsRunning)
{
StopCoroutine(info.Coroutine);
}
_activeCoroutines.Remove(info);
}
_coroutineGroups.Remove(groupName);
}
4. 实用工具方法
// 在CoroutineManager类中添加
// 延迟执行
public Coroutine DelayedCall(float delay, Action action, CoroutineType type = CoroutineType.Standard, string tag = "")
{
return StartManagedCoroutine(DelayedCallRoutine(delay, action), type, tag);
}
private IEnumerator DelayedCallRoutine(float delay, Action action)
{
yield return new WaitForSeconds(delay);
action?.Invoke();
}
// 重复执行
public Coroutine RepeatCall(float interval, Action action, int repeatCount = -1, CoroutineType type = CoroutineType.Standard, string tag = "")
{
return StartManagedCoroutine(RepeatCallRoutine(interval, action, repeatCount), type, tag);
}
private IEnumerator RepeatCallRoutine(float interval, Action action, int repeatCount)
{
int count = 0;
while (repeatCount < 0 || count < repeatCount)
{
yield return new WaitForSeconds(interval);
action?.Invoke();
count++;
}
}
// 平滑值变化
public Coroutine LerpValue(float from, float to, float duration, Action<float> onUpdate, Action onComplete = null, CoroutineType type = CoroutineType.Standard, string tag = "")
{
return StartManagedCoroutine(LerpValueRoutine(from, to, duration, onUpdate, onComplete), type, tag);
}
private IEnumerator LerpValueRoutine(float from, float to, float duration, Action<float> onUpdate, Action onComplete)
{
float time = 0;
while (time < duration)
{
float t = time / duration;
float value = Mathf.Lerp(from, to, t);
onUpdate?.Invoke(value);
time += Time.deltaTime;
yield return null;
}
onUpdate?.Invoke(to);
onComplete?.Invoke();
}
// 序列执行多个操作
public Coroutine Sequence(IEnumerable<IEnumerator> actions, CoroutineType type = CoroutineType.Standard, string tag = "")
{
return StartManagedCoroutine(SequenceRoutine(actions), type, tag);
}
private IEnumerator SequenceRoutine(IEnumerable<IEnumerator> actions)
{
foreach (IEnumerator action in actions)
{
yield return StartCoroutine(action);
}
}
5. 使用示例
基本使用
public class GameController : MonoBehaviour
{
private GamePauseManager pauseManager;
void Start()
{
pauseManager = GetComponent<GamePauseManager>();
// 启动一个标准协程(会响应暂停)
CoroutineManager.Instance.StartManagedCoroutine(StandardRoutine(), CoroutineManager.CoroutineType.Standard, "standard");
// 启动一个不受暂停影响的协程
CoroutineManager.Instance.StartManagedCoroutine(PauseImmuneRoutine(), CoroutineManager.CoroutineType.PauseImmune, "immune");
// 使用工具方法
CoroutineManager.Instance.DelayedCall(5f, () => {
Debug.Log("5秒后执行,会正确响应暂停");
});
}
void Update()
{
// 按ESC键切换暂停状态
if (Input.GetKeyDown(KeyCode.Escape))
{
pauseManager.TogglePause();
}
}
IEnumerator StandardRoutine()
{
Debug.Log("标准协程开始");
for (int i = 0; i < 10; i++)
{
Debug.Log($"标准协程计数: {i}");
yield return new WaitForSeconds(1f); // 这会被正确处理暂停
}
Debug.Log("标准协程结束");
}
IEnumerator PauseImmuneRoutine()
{
Debug.Log("免疫暂停的协程开始");
for (int i = 0; i < 10; i++)
{
Debug.Log($"免疫暂停的协程计数: {i}");
yield return new WaitForSecondsRealtime(1f); // 使用真实时间
}
Debug.Log("免疫暂停的协程结束");
}
}
高级使用
public class AdvancedExample : MonoBehaviour
{
void Start()
{
// 创建协程组
StartCoroutineGroup("enemyAI");
StartCoroutineGroup("playerEffects");
// 使用序列
List<IEnumerator> sequence = new List<IEnumerator>
{
FadeIn(2f),
ShowMessage(3f),
FadeOut(2f)
};
CoroutineManager.Instance.Sequence(sequence, CoroutineManager.CoroutineType.Standard, "introSequence");
// 订阅协程完成事件
CoroutineManager.Instance.OnCoroutineCompleted += OnCoroutineFinished;
}
void OnDestroy()
{
// 取消订阅
if (CoroutineManager.Instance != null)
{
CoroutineManager.Instance.OnCoroutineCompleted -= OnCoroutineFinished;
}
}
void OnCoroutineFinished(string tag)
{
Debug.Log($"协程 {tag} 已完成");
if (tag == "introSequence")
{
Debug.Log("介绍序列已完成,开始游戏");
StartGame();
}
}
void StartCoroutineGroup(string groupName)
{
for (int i = 0; i < 5; i++)
{
int index = i;
CoroutineManager.Instance.StartCoroutineInGroup(
GroupedRoutine(index, groupName),
groupName,
CoroutineManager.CoroutineType.Standard,
$"{groupName}_{index}"
);
}
}
IEnumerator GroupedRoutine(int index, string groupName)
{
Debug.Log($"{groupName} 协程 {index} 开始");
float duration = UnityEngine.Random.Range(3f, 8f);
float timer = 0;
while (timer < duration)
{
Debug.Log($"{groupName} 协程 {index}: {timer}/{duration}");
yield return new WaitForSeconds(1f);
timer += 1f;
}
Debug.Log($"{groupName} 协程 {index} 结束");
}
// 示例序列协程
IEnumerator FadeIn(float duration)
{
Debug.Log("开始淡入");
yield return new WaitForSeconds(duration);
Debug.Log("淡入完成");
}
IEnumerator ShowMessage(float duration)
{
Debug.Log("显示消息");
yield return new WaitForSeconds(duration);
Debug.Log("消息显示完成");
}
IEnumerator FadeOut(float duration)
{
Debug.Log("开始淡出");
yield return new WaitForSeconds(duration);
Debug.Log("淡出完成");
}
void StartGame()
{
Debug.Log("游戏开始");
}
// 暂停特定组
public void PauseEnemyAI()
{
CoroutineManager.Instance.PauseCoroutineGroup("enemyAI");
}
// 恢复特定组
public void ResumeEnemyAI()
{
CoroutineManager.Instance.ResumeCoroutineGroup("enemyAI");
}
}
6. 性能优化
// 在CoroutineManager类中添加
// 缓存常用的WaitForSeconds对象以减少GC
private Dictionary<float, WaitForSeconds> _waitForSecondsCache = new Dictionary<float, WaitForSeconds>();
// 获取缓存的WaitForSeconds
public WaitForSeconds GetCachedWaitForSeconds(float seconds)
{
if (!_waitForSecondsCache.TryGetValue(seconds, out var wait))
{
wait = new WaitForSeconds(seconds);
_waitForSecondsCache[seconds] = wait;
}
return wait;
}
// 定期清理
private void OnEnable()
{
StartCoroutine(PeriodicCleanup());
}
private IEnumerator PeriodicCleanup()
{
WaitForSeconds wait = new WaitForSeconds(30f); // 每30秒清理一次
while (true)
{
yield return wait;
CleanupDeadCoroutines();
}
}
7. 调试支持
// 在CoroutineManager类中添加
// 调试信息
public void LogCoroutineStatus()
{
Debug.Log($"活动协程总数: {_activeCoroutines.Count}");
Dictionary<string, int> tagCounts = new Dictionary<string, int>();
Dictionary<CoroutineType, int> typeCounts = new Dictionary<CoroutineType, int>();
foreach (var info in _activeCoroutines)
{
// 统计标签
string tag = string.IsNullOrEmpty(info.Tag) ? "[无标签]" : info.Tag;
if (!tagCounts.ContainsKey(tag))
{
tagCounts[tag] = 0;
}
tagCounts[tag]++;
// 统计类型
if (!typeCounts.ContainsKey(info.Type))
{
typeCounts[info.Type] = 0;
}
typeCounts[info.Type]++;
}
// 输出标签统计
Debug.Log("按标签统计:");
foreach (var pair in tagCounts)
{
Debug.Log($" {pair.Key}: {pair.Value}");
}
// 输出类型统计
Debug.Log("按类型统计:");
foreach (var pair in typeCounts)
{
Debug.Log($" {pair.Key}: {pair.Value}");
}
// 输出组统计
Debug.Log("按组统计:");
foreach (var pair in _coroutineGroups)
{
Debug.Log($" {pair.Key}: {pair.Value.Count}");
}
}
// 在Unity编辑器中显示调试信息
#if UNITY_EDITOR
private void OnGUI()
{
if (!Debug.isDebugBuild) return;
GUILayout.BeginArea(new Rect(10, 10, 300, 100));
GUILayout.Label($"活动协程: {_activeCoroutines.Count}");
GUILayout.Label($"暂停状态: {(_isPaused ? "已暂停" : "运行中")}");
if (GUILayout.Button("输出协程状态"))
{
LogCoroutineStatus();
}
GUILayout.EndArea();
}
#endif
8. 总结与最佳实践
设计要点
- 中央管理:使用单例模式的CoroutineManager统一管理所有协程
- 类型分类:将协程分为标准、不受时间缩放影响和免疫暂停三种类型
- 状态跟踪:跟踪每个协程的运行状态,支持暂停和恢复
- 组管理:支持将协程分组,可以对整组协程进行操作
- 事件通知:提供协程完成事件,便于后续操作
- 自定义Yield指令:创建专用的Yield指令处理暂停逻辑
- 实用工具方法:提供常用的协程操作封装
- 性能优化:缓存常用对象,定期清理无效协程
- 调试支持:提供详细的调试信息和状态报告
使用建议
- 所有需要响应暂停的协程都应通过CoroutineManager启动
- 根据协程的需求选择合适的类型(标准/不受缩放影响/免疫暂停)
- 为重要协程添加标签,便于跟踪和管理
- 使用协程组管理相关联的协程集合
- 在对象销毁前停止其拥有的所有协程
- 利用提供的工具方法简化常见操作
- 在开发阶段使用调试功能监控协程状态
这套协程管理系统提供了统一、灵活且强大的协程控制机制,能够有效地响应游戏暂停事件,同时提供了丰富的功能扩展和优化措施,适用于各种规模的Unity项目。