
个人主页:ujainu
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
文章目录
-
- 一、为什么需要"游戏状态机"?
- [二、路由策略:为何选用 `pushReplacement`?](#二、路由策略:为何选用
pushReplacement?) - 三、GameScreen:资源管理与生命周期
-
- 关键组件:
- [正确做法:重写 `dispose()`](#正确做法:重写
dispose())
- 四、GameOverScreen:提供明确出口
- 五、MainMenuScreen:轻量入口,无状态负担
- 六、完整路由流程图
- 七、运行界面:
- [八、OpenHarmony 特别提示](#八、OpenHarmony 特别提示)
- 结语
#前言
在任何交互式应用中,状态管理 与页面导航是决定用户体验流畅度的核心。尤其在小游戏场景下,若处理不当,极易出现:
- 游戏结束后仍残留定时器(CPU 占用不降)
- 用户误触返回键回到"已结束"的游戏界面
- 多次进入游戏导致控制器重复创建(内存泄漏)
- 页面栈混乱,无法正确退出或重启
本文将围绕 MainMenuScreen → GameScreen → GameOverScreen 这一经典三段式流程,系统性地解决上述问题,实现:
✅ 状态隔离 :游戏结束时自动清理所有资源(控制器、动画、定时器)
✅ 路由优化 :使用 Navigator.pushReplacement 避免返回栈堆积
✅ 架构清晰 :通过状态机思想组织页面跳转逻辑
✅ OpenHarmony 兼容:不依赖特定平台 API,纯 Flutter 实现
💡 核心理念 :页面即状态,状态变更驱动路由。
一、为什么需要"游戏状态机"?
"状态机"听起来高深,其实本质很简单:一个游戏只能处于一种主状态,如:
MainMenu(主菜单)Playing(游戏中)GameOver(游戏结束)
这些状态互斥,且有明确的转换规则:
MainMenu
└──[开始游戏]──→ Playing
└──[失败/胜利]──→ GameOver
└──[重新开始]──→ MainMenu(或直接新游戏)
若不用状态机,开发者常犯的错误包括:
- 在
GameScreen中监听全局事件,即使已跳转到GameOver仍在运行 - 用
Navigator.push层层叠加页面,导致用户按返回键时"穿越回过去的游戏" - 忘记 dispose 控制器,造成内存泄漏和逻辑错乱
因此,我们必须将 页面路由 与 生命周期管理 深度绑定。
二、路由策略:为何选用 pushReplacement?
| 方法 | 行为 | 适用场景 |
|---|---|---|
Navigator.push |
将新页面压入栈顶 | 需要返回上一页(如设置页) |
Navigator.pushReplacement |
替换当前页,旧页被销毁 | 状态不可逆切换(如开始游戏、游戏结束) |
在我们的三屏流程中:
- 从主菜单 开始游戏 → 应销毁
MainMenuScreen,避免返回 - 游戏结束 → 应销毁
GameScreen,防止残留逻辑 - 从结束页 重新开始 → 应回到主菜单或直接新游戏,不应保留
GameOverScreen
因此,全部使用 pushReplacement 是最佳实践。
dart
// 开始游戏:替换主菜单
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const GameScreen()),
);
// 游戏结束:替换游戏界面
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => GameOverScreen(score: _score)),
);
✅ 效果:用户无法通过返回键"回到游戏中",彻底杜绝状态污染。
三、GameScreen:资源管理与生命周期
GameScreen 是资源密集型页面,必须在其销毁时清理一切。
关键组件:
AnimationController(控制球体运动)Timer(倒计时或帧更新)StreamSubscription(如有传感器数据)
正确做法:重写 dispose()
dart
class GameScreen extends StatefulWidget {
const GameScreen({super.key});
@override
State<GameScreen> createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
late AnimationController _controller;
Timer? _gameTimer;
int _score = 0;
@override
void initState() {
super.initState();
// 初始化动画控制器
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
// 启动游戏主循环(每秒更新)
_gameTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
_score++;
// 模拟游戏失败条件
if (_score >= 10) {
_onGameOver();
}
});
});
}
// 游戏结束回调
void _onGameOver() {
_gameTimer?.cancel(); // ✅ 关键:取消定时器
_controller.dispose(); // ✅ 关键:释放动画资源
// 跳转到结束页(替换当前页)
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => GameOverScreen(score: _score),
),
);
}
@override
void dispose() {
// 安全兜底:确保资源被释放
_gameTimer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('游戏中...', style: TextStyle(color: Colors.white, fontSize: 24)),
const SizedBox(height: 20),
Text('得分: $_score', style: TextStyle(color: Colors.white, fontSize: 18)),
const SizedBox(height: 40),
ElevatedButton(
onPressed: _onGameOver, // 手动触发结束(测试用)
child: const Text('强制结束'),
),
],
),
),
);
}
}
🔥 重点强调:
_onGameOver()中 立即取消定时器并 dispose 控制器dispose()作为最后防线,防止异常路径遗漏清理- 使用
TickerProviderStateMixin提供 vsync,避免动画卡顿
四、GameOverScreen:提供明确出口
结束页的目标是 引导用户下一步操作,通常有两个选项:
- 重新开始 → 回到主菜单 或 直接新游戏
- 退出 → 返回系统桌面(OpenHarmony 下可调用
SystemNavigator.pop())
dart
class GameOverScreen extends StatelessWidget {
final int score;
const GameOverScreen({required this.score, super.key});
void _restartGame(BuildContext context) {
// 方案A:回到主菜单(推荐,保持流程清晰)
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const MainMenuScreen()),
);
// 方案B:直接启动新游戏(需确保 GameScreen 可重复创建)
// Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const GameScreen()));
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[900],
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('游戏结束!', style: TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Text('最终得分: $score', style: TextStyle(color: Colors.white, fontSize: 20)),
const SizedBox(height: 40),
ElevatedButton.icon(
onPressed: () => _restartGame(context),
icon: const Icon(Icons.replay),
label: const Text('重新开始'),
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
),
const SizedBox(height: 16),
OutlinedButton(
onPressed: () {
// OpenHarmony / Android / iOS 通用退出方式
SystemNavigator.pop(); // 退出应用(谨慎使用)
// 或仅返回主菜单:_restartGame(context);
},
child: const Text('退出游戏', style: TextStyle(color: Colors.white)),
),
],
),
),
);
}
}
⚠️ 注意 :
SystemNavigator.pop()在部分平台可能无效(如 Web),生产环境建议引导至主菜单而非直接退出。
五、MainMenuScreen:轻量入口,无状态负担
主菜单应尽可能简单,避免持有任何游戏相关状态。
dart
class MainMenuScreen extends StatelessWidget {
const MainMenuScreen({super.key});
void _startGame(BuildContext context) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const GameScreen()),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueGrey[900],
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('球球快跑', style: TextStyle(color: Colors.white, fontSize: 40, fontWeight: FontWeight.bold)),
const SizedBox(height: 60),
ElevatedButton.icon(
onPressed: () => _startGame(context),
icon: const Icon(Icons.play_arrow),
label: const Text('开始游戏', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
backgroundColor: Colors.deepOrange,
),
),
],
),
),
);
}
}
✅ 优势:无
StatefulWidget,无资源占用,可无限次进入。
六、完整路由流程图
pushReplacement
pushReplacement
pushReplacement
SystemNavigator.pop
MainMenuScreen
GameScreen
GameOverScreen
退出应用
- 所有箭头均为单向替换,无返回路径
- GameScreen 是唯一有状态、需清理的页面
七、运行界面:



八、OpenHarmony 特别提示
- 后台清理 :OpenHarmony 应用进入后台时,系统可能回收资源。建议在
AppLifecycleState.paused时暂停游戏,在resumed时恢复或结束。 - 退出行为 :
SystemNavigator.pop()在 OpenHarmony 上表现良好,但需在AndroidManifest.xml中配置finishOnClose(如需要)。 - 内存监控 :使用 DevEco Studio 的内存分析工具,验证
GameScreen销毁后无对象残留。
结语
通过 状态机思维 + pushReplacement 路由 + 严格的 dispose 管理,我们构建了一个健壮、清晰、低内存占用的游戏状态流转系统。
这套模式不仅适用于小球游戏,还可扩展至:
- 关卡选择 → 游戏 → 暂停菜单 → 设置
- 登录 → 主界面 → 个人中心 → 退出登录
记住:页面不是堆叠的纸张,而是状态的具象化。状态切换时,旧状态必须彻底死亡。