You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity如何让协程在游戏暂停/离线后正常计时并发放奖励?

解决方案:基于绝对时间戳的烹饪计时系统

核心问题分析

原实现的问题根源在于WaitForSeconds依赖Unity的帧循环和Time.timeScale:

  • 游戏暂停(Time.timeScale=0)时,协程会完全冻结,恢复后继续计时,但逻辑耦合易引发崩溃
  • 离线后协程直接销毁,进度丢失,且尝试的离线处理逻辑存在错误(如离线时长达标时未发放奖励反而直接返回)

解决思路

替换为基于UTC时间戳的计时方案,核心是记录烹饪的开始时间而非依赖协程等待时长:

  1. 为每个食物添加烹饪状态和开始时间的存储字段
  2. 启动烹饪时仅记录状态与时间,不依赖协程等待
  3. 游戏启动/恢复时批量计算所有烹饪中食物的总耗时(含离线时间)
  4. 用非帧依赖的方式实时更新剩余时间,彻底避免暂停崩溃

具体实现步骤

1. 升级FoodData数据结构

修改食物数据类,增加计时相关字段用于持久化:

[Serializable]
public class FoodData
{
    public string FoodName;
    public float CookingTime; // 烹饪总时长(秒)
    public int Profit;
    public bool IsCooking; // 是否处于烹饪状态
    public long CookingStartTimeUtc; // 开始烹饪的UTC时间戳(毫秒)
    // 原有其他字段...
}

2. 重写烹饪启动逻辑

移除WaitForSeconds协程依赖,改为记录时间戳并启动实时跟踪:

public void CookFood(int foodIndex)
{
    FoodData foodData = foodManager.GetFoodData(foodIndex);
    if (foodData.IsCooking) return;

    // 标记烹饪状态,记录UTC开始时间
    foodData.IsCooking = true;
    foodData.CookingStartTimeUtc = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;

    foodManager.SaveFoodData(foodIndex, foodData); // 立即保存状态到本地
    InvokeEvent(HOGEventNames.OnCookFood, foodIndex); // 触发UI加载条更新

    // 启动非帧依赖的计时跟踪协程
    StartCoroutine(TrackCookingProgress(foodIndex));
}

3. 替换计时协程:基于时间戳的实时检查

用UTC时间计算耗时,彻底脱离WaitForSeconds:

private IEnumerator TrackCookingProgress(int foodIndex)
{
    FoodData foodData = foodManager.GetFoodData(foodIndex);
    float totalCookingTime = foodData.CookingTime;

    while (foodData.IsCooking)
    {
        // 计算从启动到当前的总耗时(毫秒转秒)
        long currentUtcMs = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
        float elapsedTime = (currentUtcMs - foodData.CookingStartTimeUtc) / 1000f;

        if (elapsedTime >= totalCookingTime)
        {
            // 烹饪完成,执行奖励发放
            CompleteCooking(foodIndex);
            break;
        }

        // 更新UI剩余时间
        float remainingTime = totalCookingTime - elapsedTime;
        InvokeEvent(HOGEventNames.UpdateCookingTimer, foodIndex, remainingTime);

        // 用WaitForEndOfFrame避免依赖Time.timeScale,暂停时也能正常循环
        yield return new WaitForEndOfFrame();
    }
}

private void CompleteCooking(int foodIndex)
{
    FoodData foodData = foodManager.GetFoodData(foodIndex);
    foodData.IsCooking = false;
    foodManager.SaveFoodData(foodIndex, foodData);

    // 发放烹饪奖励
    playerMoney.UpdateCurrency(foodData.Profit);
    InvokeEvent(HOGEventNames.MoneyToastOnCook, foodIndex);
}

4. 离线/游戏启动时的进度处理

游戏启动时批量处理所有烹饪中食物的离线进度:

public void ProcessOfflineCookingProgress()
{
    foreach (int foodIndex in foodManager.GetAllFoodIndices())
    {
        FoodData foodData = foodManager.GetFoodData(foodIndex);
        if (!foodData.IsCooking) continue;

        long currentUtcMs = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
        float totalElapsedTime = (currentUtcMs - foodData.CookingStartTimeUtc) / 1000f;

        if (totalElapsedTime >= foodData.CookingTime)
        {
            // 离线期间已完成烹饪,直接发放奖励
            CompleteCooking(foodIndex);
        }
        else
        {
            // 恢复烹饪进度,启动实时跟踪
            InvokeEvent(HOGEventNames.OnCookFood, foodIndex);
            StartCoroutine(TrackCookingProgress(foodIndex));
        }
    }
}

5. 游戏暂停的兼容处理

若需要暂停时停止计时,可在协程循环内添加判断:

// 在TrackCookingProgress的while循环内加入
if (Time.timeScale == 0)
{
    // 暂停时不计算时间,等待游戏恢复
    yield return new WaitForEndOfFrame();
    continue;
}

关键改进点

  • 彻底脱离WaitForSeconds,解决暂停崩溃和离线进度丢失问题
  • 用UTC时间戳记录,确保离线时长计算精准
  • 烹饪状态实时持久化,重启游戏后进度不丢失
  • 批量处理离线进度,逻辑清晰无冗余

内容的提问来源于stack exchange,提问作者Omri Ovadia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:35:14