设计统一响应暂停事件的Unity协程管理系统

设计一套能够统一响应暂停事件的协程管理机制需要考虑多个方面,包括协程的生命周期管理、暂停状态同步、不同类型协程的处理等。以下是一个完整的设计方案:

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. 总结与最佳实践

设计要点

  1. 中央管理:使用单例模式的CoroutineManager统一管理所有协程
  2. 类型分类:将协程分为标准、不受时间缩放影响和免疫暂停三种类型
  3. 状态跟踪:跟踪每个协程的运行状态,支持暂停和恢复
  4. 组管理:支持将协程分组,可以对整组协程进行操作
  5. 事件通知:提供协程完成事件,便于后续操作
  6. 自定义Yield指令:创建专用的Yield指令处理暂停逻辑
  7. 实用工具方法:提供常用的协程操作封装
  8. 性能优化:缓存常用对象,定期清理无效协程
  9. 调试支持:提供详细的调试信息和状态报告

使用建议

  1. 所有需要响应暂停的协程都应通过CoroutineManager启动
  2. 根据协程的需求选择合适的类型(标准/不受缩放影响/免疫暂停)
  3. 为重要协程添加标签,便于跟踪和管理
  4. 使用协程组管理相关联的协程集合
  5. 在对象销毁前停止其拥有的所有协程
  6. 利用提供的工具方法简化常见操作
  7. 在开发阶段使用调试功能监控协程状态

这套协程管理系统提供了统一、灵活且强大的协程控制机制,能够有效地响应游戏暂停事件,同时提供了丰富的功能扩展和优化措施,适用于各种规模的Unity项目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值