

子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)
大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。
我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,
在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。
技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出
我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。
子玥酱 · 前端成长记录官 ✨
👋 如果你正在做前端,或准备长期走前端这条路
📚 关注我,第一时间获取前端行业趋势与实践总结
🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)
💡 一起把技术学"明白",也用"到位"
持续写作,持续进阶。
愿我们都能在代码和生活里,走得更稳一点 🌱
文章目录
-
- 引言
- [第一个被后台机制击穿的,是 deltaTime](#第一个被后台机制击穿的,是 deltaTime)
- "简单跳过一帧",是最危险的修复方式
- 后台下,网络逻辑同样不可信
- 一个"看起来合理"的错误设计
- 正确的做法:把后台"隔离"出来
- 实时同步,必须显式面对"时间断层"
- 强实时游戏的"止损方案"
- 后台机制,其实在倒逼架构成熟
- 总结
引言
如果你在 HarmonyOS 上写过实时游戏逻辑,大概率写过类似代码:
ts
onBackground() {
this.paused = true
}
onForeground() {
this.paused = false
}
逻辑很直觉,对吧?
但如果你真用这套逻辑跑过一段时间,迟早会遇到:
- 前后台切一次,时间直接跳
- 恢复那一帧,角色瞬移
- 同步状态突然不可信
问题不是代码写错了,而是:
后台机制打破了你对"时间连续性"的假设。
第一个被后台机制击穿的,是 deltaTime
实时游戏的核心几乎都长这样:
ts
let lastTime = now()
function loop() {
const current = now()
const deltaTime = current - lastTime
lastTime = current
update(deltaTime)
render()
requestNextFrame(loop)
}
在前台,这段代码是成立的。
但当 App 进入后台时,会发生什么?
text
loop 停止调用
线程调度被挂起
定时器精度下降
然后某一刻,App 回来了。
ts
// 回到前台后的第一帧
deltaTime = 3521ms
这 3.5 秒,你的游戏世界要怎么办?
"简单跳过一帧",是最危险的修复方式
很多项目第一反应是:
ts
if (deltaTime > 100) return
或者:
ts
deltaTime = Math.min(deltaTime, 16)
看起来问题解决了。但副作用是:
- 服务器时间还在跑
- 客户端时间被"掐断"
- 同步误差被埋进状态里
你不是解决了时间问题,而是隐藏了时间问题。
后台下,网络逻辑同样不可信
很多实时游戏默认:
ts
socket.send(input)
socket.onMessage(sync)
在后台时,你可能仍然能收到回调,也可能完全收不到。
于是你会看到这种逻辑开始出现:
ts
if (isBackground) {
queueInput(input)
} else {
sendInput(input)
}
但问题在于:
你无法判断这段 input,
到底发生在什么时间点。
等你回前台再发,服务器世界已经推进了 N 个 tick。
一个"看起来合理"的错误设计
下面是很多项目都出现过的结构:
ts
onBackground() {
game.pause()
}
onForeground() {
game.resume()
}
而 pause() 内部做了大量事情:
ts
pause() {
this.stopLoop()
this.stopNetwork()
this.freezePhysics()
}
这种设计的致命问题是:
生命周期直接控制了游戏世界。
后台事件,本不该知道:
- 你有多少系统
- 哪些该停
- 哪些能继续
正确的做法:把后台"隔离"出来
成熟项目往往会引入一个中间层:
ts
class RuntimeContext {
visible: boolean = true
suspended: boolean = false
timeScale: number = 1
}
App 生命周期只改这个 Context:
ts
onBackground() {
runtime.visible = false
runtime.suspended = true
}
onForeground() {
runtime.visible = true
runtime.suspended = false
}
而游戏循环只认它:
ts
function loop() {
if (runtime.suspended) return
const deltaTime = calcDeltaTime() * runtime.timeScale
update(deltaTime)
render()
}
注意:
App 生命周期不再"指挥"游戏,
它只是改变环境条件。
实时同步,必须显式面对"时间断层"
对实时游戏来说,更关键的是同步策略。
错误假设
ts
// 假设客户端一直在跑
serverTick += deltaTime
更现实的做法
ts
onForeground() {
requestServerSnapshot()
}
ts
onSnapshot(snapshot) {
world.resetTo(snapshot)
}
也就是说:
前后台切换,本质是一次"世界重建",
而不是"继续播放"。
强实时游戏的"止损方案"
如果你做的是强同步对战游戏,最干净的策略反而很简单:
ts
onBackground() {
endMatch("player left")
}
这看起来残忍,但它满足了三点:
- 时间不造假
- 状态不扭曲
- 同步逻辑简单
很多成熟游戏,最终都会走到这一步。
后台机制,其实在倒逼架构成熟
当你真的接受这件事:
后台不可依赖
你会自然做出这些设计:
- 游戏循环不再假设连续时间
- 同步模型支持断点恢复
- 生命周期不再直连游戏逻辑
HarmonyOS 并没有为难你,
它只是拒绝帮你兜底。
总结
HarmonyOS 的后台机制,对实时游戏的最大影响不是:
- "我能不能继续跑?"
而是逼你回答:
如果不能连续跑,
你的世界该怎么继续?
后台不是"慢前台",
而是一条清晰的断点。
你要么:
- 显式承认它
- 显式重建世界
要么:
- 在不知不觉中
把时间和状态一起搞乱
这道选择题,每一个实时游戏,迟早都要做。