Unity中WaitForSeconds在游戏暂停时的行为与冻结方法

WaitForSeconds在暂停时的表现

当在协程中使用WaitForSeconds(2)指令等待2秒,其行为与Unity的时间缩放系统(Time.timeScale)直接相关。

基本行为

  • WaitForSeconds依赖于Time.timeScale来计算实际等待时间
  • Time.timeScale = 0(游戏暂停)时,WaitForSeconds无限期等待
  • 这是因为WaitForSeconds实际上是等待的游戏时间而非真实时间

行为示例

IEnumerator ExampleCoroutine()
{
    Debug.Log("协程开始");
    yield return new WaitForSeconds(2); // 应等待2秒
    Debug.Log("协程结束"); // 如果Time.timeScale=0,这一行永远不会执行
}

void PauseGame()
{
    Time.timeScale = 0; // 暂停游戏
}

Time.timeScale = 0时,上面的协程会永远停在WaitForSeconds(2)处,因为游戏时间不再流逝,2秒的等待永远不会结束。

让协程在暂停时完全冻结的方法

有几种方法可以确保协程在游戏暂停时也完全冻结:

1. 使用WaitForSecondsRealtime

WaitForSecondsRealtime使用真实时间而非游戏时间,不受Time.timeScale影响:

IEnumerator ExampleCoroutine()
{
    Debug.Log("协程开始");

    if (Time.timeScale > 0)
    {
        // 游戏运行时使用游戏时间
        yield return new WaitForSeconds(2);
    }
    else
    {
        // 游戏暂停时立即继续而不等待
        yield return null;
    }

    Debug.Log("协程结束");
}

2. 自定义暂停感知的协程系统

创建一个可以检测游戏暂停状态的协程管理系统:

public class PauseAwareCoroutineManager : MonoBehaviour
{
    private static PauseAwareCoroutineManager _instance;
    public static PauseAwareCoroutineManager Instance
    {
        get
        {
            if (_instance == null)
            {
                GameObject go = new GameObject("PauseAwareCoroutineManager");
                _instance = go.AddComponent<PauseAwareCoroutineManager>();
                DontDestroyOnLoad(go);
            }
            return _instance;
        }
    }

    private bool isPaused = false;
    private List<(Coroutine coroutine, MonoBehaviour owner)> activeCoroutines =
        new List<(Coroutine, MonoBehaviour)>();

    // 启动可暂停的等待协程
    public Coroutine WaitForSecondsWithPause(MonoBehaviour owner, float seconds, Action onComplete)
    {
        Coroutine coroutine = StartCoroutine(WaitForSecondsCoroutine(owner, seconds, onComplete));
        activeCoroutines.Add((coroutine, owner));
        return coroutine;
    }

    private IEnumerator WaitForSecondsCoroutine(MonoBehaviour owner, float seconds, Action onComplete)
    {
        float timer = 0;

        while (timer < seconds)
        {
            if (!isPaused)
            {
                timer += Time.deltaTime;
            }
            yield return null;
        }

        onComplete?.Invoke();
        activeCoroutines.RemoveAll(x => x.coroutine == null || x.owner == null);
    }

    // 暂停所有协程
    public void PauseAllCoroutines()
    {
        isPaused = true;
    }

    // 恢复所有协程
    public void ResumeAllCoroutines()
    {
        isPaused = false;
    }
}

使用方法:

// 替代WaitForSeconds
PauseAwareCoroutineManager.Instance.WaitForSecondsWithPause(this, 2f, () => {
    Debug.Log("2秒后执行,即使游戏暂停也会正确冻结和恢复");
});

// 暂停游戏时
void PauseGame()
{
    Time.timeScale = 0;
    PauseAwareCoroutineManager.Instance.PauseAllCoroutines();
}

// 恢复游戏时
void ResumeGame()
{
    Time.timeScale = 1;
    PauseAwareCoroutineManager.Instance.ResumeAllCoroutines();
}

3. 使用自定义的YieldInstruction

创建一个自定义的PauseAwareWaitForSeconds类:

public class PauseAwareWaitForSeconds : CustomYieldInstruction
{
    private float targetTime;
    private GamePauseManager pauseManager;

    public PauseAwareWaitForSeconds(float seconds, GamePauseManager pauseManager)
    {
        this.targetTime = Time.time + seconds;
        this.pauseManager = pauseManager;
    }

    public override bool keepWaiting
    {
        get
        {
            if (pauseManager.IsPaused)
            {
                // 游戏暂停时,更新目标时间以"冻结"等待
                targetTime = Time.time + (targetTime - Time.time);
                return true;
            }

            return Time.time < targetTime;
        }
    }
}

// 使用方法
IEnumerator WaitWithPause(float seconds)
{
    yield return new PauseAwareWaitForSeconds(seconds, gamePauseManager);
    Debug.Log("等待完成,正确处理了暂停");
}

4. 协程暂停标记法

在全局创建一个暂停标记,在协程中检查该标记:

public static class GameManager
{
    public static bool IsGamePaused { get; private set; }

    public static void PauseGame()
    {
        Time.timeScale = 0;
        IsGamePaused = true;
    }

    public static void ResumeGame()
    {
        Time.timeScale = 1;
        IsGamePaused = false;
    }
}

// 在协程中使用
IEnumerator DelayedAction(float seconds)
{
    float timer = 0;

    while (timer < seconds)
    {
        // 只有在游戏未暂停时才增加计时器
        if (!GameManager.IsGamePaused)
        {
            timer += Time.deltaTime;
        }
        yield return null;
    }

    Debug.Log("计时完成,正确处理了暂停");
}

最佳实践总结

  1. 避免直接使用WaitForSeconds:在可能暂停的游戏中,不要直接使用WaitForSeconds,除非你希望它在暂停时无限等待
  2. 使用暂停感知的计时器:自己实现计时逻辑,在暂停时不增加计时器
  3. 分离游戏时间和真实时间:对于需要在暂停时仍然运行的协程(如UI动画),使用WaitForSecondsRealtime
  4. 创建集中的协程管理系统:实现一个中央协程管理器,统一处理暂停和恢复
  5. 使用标志变量:在全局维护一个暂停标志,所有协程检查这个标志

通过这些方法,你可以确保协程在游戏暂停时正确冻结,并在游戏恢复时继续执行,从而实现更加可靠的游戏时间系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值