Unity C#执行24小时积分重置代码时游戏卡死问题排查
问题根因
游戏冻结是主线程被超长死循环阻塞导致的,三个bug叠加触发了问题:
- 初始时间配置错误
代码中LastReset的默认值取的是字符串"0",对应DateTime.Ticks的基准时间是公元1年1月1日,和当前真实时间的差值超过2000年,换算成24小时间隔数超过73万。玩家首次启动游戏本地无存档时,GetIntervalsPassed()会直接返回70w+的间隔数,触发超大规模循环。 - 整数除法导致积分永远无法降到阈值下
ReduceScore()里的扣除额用int整数计算,规则为向下取整:当玩家积分仅比阈值高1分时(比如7001),(scrMain - scoreThreshold)/2的计算结果为0,相当于扣0分,积分会永远停在7001,始终满足scrMain>7000的触发条件。 - 循环内同步IO放大阻塞
每次ReduceScore()调用都会执行PlayerPrefs.Save(),这个接口是同步写磁盘的IO操作,单次调用就有毫秒级开销,几十万次调用叠加后主线程会被完全占满,根本走不完循环,直接表现为游戏永久无响应。
编辑器内运行正常是因为本地已经留存了之前测试写入的LastReset值,时间差最多是两次打开编辑器的间隔,通常仅数天,intervalsPassed数值极小;同时测试时很少会把积分调到7001这类卡整数除法边界的值,所以不会触发异常。
修复方案
- 修正初始时间逻辑:首次启动无
LastReset存档时,直接写入当前时间,不要用0做默认值,避免算出天量间隔数。 - 修正积分扣除边界:当超出阈值的积分不足2分时,直接扣1分把积分拉到阈值,避免出现永远扣不完的1分差额。
- 优化IO逻辑:去掉循环内的
PlayerPrefs.Save()调用,所有积分计算完成后统一存一次,减少无意义的磁盘开销。 - 优化补算逻辑:不需要真的按间隔数循环扣积分,计算时如果已经扣到阈值以下直接跳出循环;间隔数极大时可以直接用等比数列公式算最终积分,把O(n)的循环改成O(1)的计算,从根源避免长循环卡顿。
修复后的核心代码参考:
private void Start(){ LegendaryTrophies = PlayerPrefs.GetInt("LegendaryTrophies"); scrMain = PlayerPrefs.GetInt("scrMain"); MainCoins = PlayerPrefs.GetInt("MainCoins"); // 首次启动直接初始化重置时间为当前时间 if (!PlayerPrefs.HasKey("LastReset")) { TimeLastReset = (ulong)DateTime.Now.Ticks; PlayerPrefs.SetString("LastReset", TimeLastReset.ToString()); } else { TimeLastReset = ulong.Parse(PlayerPrefs.GetString("LastReset")); } } private void Update(){ if (scrMain > scoreThreshold) { int intervalsPassed = GetIntervalsPassed(); if (intervalsPassed > 0){ int exceed = scrMain - scoreThreshold; int totalDeduct = 0; // 补算多间隔的扣除额,扣到阈值就停,不用跑完全部循环 for (int i = 0; i < intervalsPassed; i++) { int stepDeduct = exceed / 2; // 处理整数除法边界,剩1分也扣完 if (stepDeduct == 0 && exceed > 0) stepDeduct = 1; totalDeduct += stepDeduct; exceed -= stepDeduct; if (exceed == 0) break; } // 统一更新数值 MainCoins += totalDeduct * 100; LegendaryTrophies += totalDeduct; scrMain -= totalDeduct; // 统一写入存档,只存一次 PlayerPrefs.SetInt("MainCoins", MainCoins); PlayerPrefs.SetInt("scrMain", scrMain); PlayerPrefs.SetInt("LegendaryTrophies", LegendaryTrophies); PlayerPrefs.Save(); // 更新重置时间 TimeLastReset = (ulong)DateTime.Now.Ticks; PlayerPrefs.SetString("LastReset", TimeLastReset.ToString()); } } }
内容的提问来源于stack exchange,提问作者Ilya
相关产品推荐
相关产品推荐

