Unity如何让协程在游戏暂停/离线后正常计时并发放奖励?
解决方案:基于绝对时间戳的烹饪计时系统
核心问题分析
原实现的问题根源在于WaitForSeconds依赖Unity的帧循环和Time.timeScale:
- 游戏暂停(
Time.timeScale=0)时,协程会完全冻结,恢复后继续计时,但逻辑耦合易引发崩溃 - 离线后协程直接销毁,进度丢失,且尝试的离线处理逻辑存在错误(如离线时长达标时未发放奖励反而直接返回)
解决思路
替换为基于UTC时间戳的计时方案,核心是记录烹饪的开始时间而非依赖协程等待时长:
- 为每个食物添加烹饪状态和开始时间的存储字段
- 启动烹饪时仅记录状态与时间,不依赖协程等待
- 游戏启动/恢复时批量计算所有烹饪中食物的总耗时(含离线时间)
- 用非帧依赖的方式实时更新剩余时间,彻底避免暂停崩溃
具体实现步骤
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
相关产品推荐
相关产品推荐

