Flutter for OpenHarmony:构建一个 Flutter 数字消消乐游戏,深入解析网格状态管理、合并算法与重力系统
Flutter for OpenHarmony:构建一个 Flutter 数字消消乐游戏,深入解析网格状态管理、合并算法与重力系统
Flutter for OpenHarmony:构建一个 Flutter 数字消消乐游戏,深入解析网格状态管理、合并算法与重力系统
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
发布时间:2026年2月6日
技术栈:Flutter 3.22+、Dart 3.4+、Material Design 3
适用读者:中级 Flutter 开发者、休闲游戏开发者、对状态驱动交互与算法逻辑感兴趣的技术人员
引言:从“三消”到“数字合成”的玩法演进
“消除类”游戏是移动休闲市场的常青树。从经典的《宝石迷阵》(Bejeweled)到现象级的《2048》,再到如今的《Threes!》,其核心魅力在于简单规则下的深度策略性。玩家通过匹配、合并或消除元素,获得即时反馈与渐进式成就感。
今天我们要实现的“数字消消乐”,融合了两类经典机制:
- 相邻匹配(如 Candy Crush):仅允许上下左右相邻的相同数字合并
- 数值合成(如 2048):合并后生成更高一级的数字(1+1→2, 2+2→3, …)
这种设计既保留了传统三消的直观性,又引入了数值成长的长期目标,形成独特的“短反馈 + 长规划”双层激励结构。
更重要的是,这个项目展示了 如何用纯 Flutter 实现一个完整的网格游戏系统——包括状态表示、交互逻辑、物理模拟(重力)与胜利/失败判定,而无需任何游戏引擎或第三方库。
一、游戏机制与核心规则
基本设定
- 4×4 网格:共 16 个格子
- 初始状态:随机填入 4~6 个
1或2 - 操作规则:
- 点击一个非空格子 → 选中(高亮)
- 再点击一个相邻(上下左右)且数字相同的格子 → 合并
- 合并结果:新数字 = 原数字 + 1,放置于下方或右侧的位置
- 重力系统:合并后,上方数字下落填补空位
- 新数字生成:每次合并后,在随机空位添加
1或2 - 失败条件:无任何可合并的相邻对 → 游戏结束
技术映射
| 游戏概念 | 代码实体 | 数据结构 |
|---|---|---|
| 网格 | grid |
List<List<int?>> (4×4) |
| 选中状态 | selectedRow, selectedCol |
int? |
| 合并逻辑 | _onCellTap |
状态变更函数 |
| 重力模拟 | _applyGravity |
列压缩算法 |
| 失败检测 | _canMerge |
遍历检查 |
二、数据模型:二维网格的状态表示
核心定义
final grid = List.generate(4, (i) => List<int?>.generate(4, (j) => null));
设计选择解析
int?类型:null表示空格,非null表示数字值- 行优先索引:
grid[row][col],符合数学矩阵习惯 - 不可变尺寸:硬编码 4×4,简化逻辑(可轻松扩展为 N×N)
✅ 内存效率:
int?在 Dart 中为 nullable int,开销极小。
为什么不用 List.generate 的 growable: false?
虽然固定大小网格理论上可用不可变列表,但频繁修改(合并、重力)需要可变性,当前方案在简洁性与性能间取得平衡。
三、初始化系统:可控的随机性
初始填充逻辑
void _fillInitial() {
int count = 4 + _random.nextInt(3); // 4~6 个数字
for (int i = 0; i < count; i++) {
_addRandomNumber();
}
}

随机数生成器
void _addRandomNumber() {
// 1. 收集所有空格坐标
List<List<int>> emptyCells = [];
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
if (grid[r][c] == null) {
emptyCells.add([r, c]);
}
}
}
// 2. 随机选择一个空格
if (emptyCells.isNotEmpty) {
final cell = emptyCells[_random.nextInt(emptyCells.length)];
grid[cell[0]][cell[1]] = _random.nextBool() ? 1 : 2;
}
}

设计优势
- 避免重复扫描:一次性收集所有空格,O(n²) → O(k),k=空格数
- 公平分布:每个空格被选中概率相等
- 安全边界:
if (emptyCells.isNotEmpty)防止越界
💡 可扩展性:未来可支持不同难度(如高级模式初始填 8 个数字)。
四、交互逻辑:双击模型与相邻检测
用户操作流程
[空闲]
│
▼ (点击非空格)
[选中状态] ——(点击自身)——→ [空闲](隐式取消)
│
▼ (点击相邻同值格)
[执行合并] → [应用重力] → [添加新数字] → [检查失败]
│
▼ (点击无效格)
[重置为空闲]
核心函数 _onCellTap
void _onCellTap(int row, int col) {
if (gameOver || grid[row][col] == null) return;
if (selectedRow == null) {
// 第一次点击:选中
selectedRow = row; selectedCol = col;
} else {
// 第二次点击
int r1 = selectedRow!, c1 = selectedCol!;
int r2 = row, c2 = col;
// 重置选择状态
selectedRow = null; selectedCol = null;
// 检查相邻且同值
bool adjacent = (r1 == r2 && (c1 - c2).abs() == 1) ||
(c1 == c2 && (r1 - r2).abs() == 1);
if (adjacent && grid[r1][c1] == grid[r2][c2]) {
// 执行合并...
}
}
setState(() {});
}
相邻检测算法
bool adjacent =
(同行且列差为1) || (同列且行差为1);
- 精确匹配:仅允许正交相邻(不含对角线)
- 绝对值处理:
(c1 - c2).abs() == 1覆盖左右两种情况
✅ 无状态副作用:选择状态在合并前已重置,避免 UI 不一致。
五、合并与重力系统:模拟物理下落
合并规则
// 保留下方或右侧的格子
int targetR = r1 > r2 ? r1 : r2;
int targetC = c1 > c2 ? c1 : c2;
grid[targetR][targetC] = newValue;
设计哲学
- 确定性结果:相同输入必得相同输出(利于调试)
- 视觉合理性:“下方/右侧”符合用户对“合并方向”的直觉
重力模拟:列压缩算法
void _applyGravity() {
for (int col = 0; col < 4; col++) {
// 1. 提取当前列
List<int?> column = [grid[0][col], grid[1][col], grid[2][col], grid[3][col]];
// 2. 移除空值
column.removeWhere((v) => v == null);
// 3. 顶部补 null
while (column.length < 4) {
column.insert(0, null);
}
// 4. 写回网格
for (int row = 0; row < 4; row++) {
grid[row][col] = column[row];
}
}
}

算法复杂度
- 时间:O(4×4) = O(1)(固定网格)
- 空间:O(4) 临时列数组
💡 优化提示:对大型网格(如 10×10),可原地压缩避免额外空间,但 4×4 下简洁性优先。
六、失败检测:_canMerge 的遍历策略
实现
bool _canMerge() {
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
if (grid[r][c] != null) {
// 检查右邻居
if (c < 3 && grid[r][c] == grid[r][c + 1]) return true;
// 检查下邻居
if (r < 3 && grid[r][c] == grid[r + 1][c]) return true;
}
}
}
return false;
}
为什么只检查右和下?
- 避免重复:若检查全部四个方向,每对会被检测两次
- 覆盖完整:右+下遍历可覆盖所有水平与垂直相邻对
✅ 早期退出:一旦发现可合并对,立即返回
true,提升性能。
七、UI 架构:GridView + 动态样式
网格渲染
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemCount: 16,
itemBuilder: (context, index) {
int row = index ~/ 4;
int col = index % 4;
// ...
},
)
视觉反馈设计
| 状态 | 背景色 | 边框 |
|---|---|---|
| 空格 | Colors.grey[100] |
灰色细边 |
| 选中 | Colors.yellow.shade200 |
橙色粗边 (3dp) |
| 数字 | _getColorForValue(value) |
灰色细边 |
数字着色策略
Color _getColorForValue(int value) {
final colors = [Colors.blue[100]!, ..., Colors.pink[100]!];
return colors[(value - 1) % colors.length];
}
- 循环复用:8 种浅色,按数值循环分配
- 视觉区分:不同数字颜色不同,便于快速识别
🎨 Material 3 整合:使用
ColorScheme.fromSeed(seedColor: Colors.deepPurple)保持主题协调。
八、性能与用户体验优化
性能关键点
- 最小化 rebuild:仅
grid、selected、gameOver触发更新 - 高效算法:所有逻辑 O(1)(因网格固定)
- 常量优化:
const修饰SliverGridDelegate、EdgeInsets等
用户体验细节
- 即时反馈:选中高亮 + 粗边框,明确当前状态
- 清晰指引:底部文字说明规则
- 优雅失败:红色横幅提示 + FAB 重玩按钮
- 防误触:空格不可点击,游戏结束禁用交互
九、扩展方向:从休闲游戏到策略挑战
当前实现是一个优秀的 MVP,但可进一步升级:
1. 难度分级
- 初级:3×3 网格,仅 1/2
- 高级:5×5 网格,初始填 10 个数字
- 专家:引入障碍格(不可消除)
2. 得分系统
- 基础分:合并数字值 × 10
- 连击奖励:连续合并额外加分
- 最高数字成就:合成 10+ 显示徽章
3. 撤销功能
- 记录操作历史(网格快照)
- 支持一步撤销(Ctrl+Z)
4. 动画效果
- 合并时数字放大 + 淡出
- 重力下落平滑动画(
AnimatedPositioned)
5. 本地存储
- 保存最高分、最大合成数字
- 每日挑战模式
十、总结:网格游戏中的工程智慧
这个"数字消消乐"游戏完美诠释了一个工程真理:复杂的游戏体验,可以源于简单而严谨的逻辑组合。就像乐高积木一样,通过精心设计的模块化组件,我们构建出了富有深度的游戏玩法。
具体实现中,我们采用了以下关键技术:
-
二维网格状态表示
- 使用二维数组存储每个格子的数值和状态
- 配合Flutter的Grid视图组件实现可视化渲染
- 示例:
List<List<int>> grid = List.generate(5, (_) => List.filled(5, 0));
-
双击交互模型
- 通过GestureDetector捕获用户双击事件
- 精确计算点击位置对应的网格坐标
- 实现数值递增的连锁反应效果
-
列压缩重力算法
- 自上而下遍历每列元素
- 移除空白格时执行下落动画
- 新元素从顶部平滑补位
-
相邻检测与失败判定
- 四邻域检测算法判断相邻相同数字
- 实时检查网格填满状态
- 提供清晰的游戏结束反馈
这些技术组合让我们得以在不到200行Dart代码内,构建出一个具有以下特点的精品游戏:
- 规则清晰:简单易懂的数字消除机制
- 反馈及时:流畅的动画和即时响应
- 视觉愉悦:Material Design风格的UI设计
更重要的是,本项目充分展示了Flutter框架在构建实时交互应用方面的强大能力:
- 声明式UI:通过Widget树直观描述界面结构
- 响应式状态流:setState触发自动UI更新
- 高效渲染:Skia引擎保障60fps流畅体验
这种架构模式具有广泛的适用性,特别适合开发:
- 益智类游戏(如2048、数独)
- 棋盘类应用(象棋、围棋)
- 数据可视化工具(热力图、矩阵展示)
通过本项目积累的经验,你可以快速搭建起各类网格交互应用的开发框架,为更复杂的产品奠定坚实基础。
附录:动手实验建议
- 添加合并音效:成功合并播放“叮”声
- 实现动画:使用
AnimatedContainer实现数字下落 - 引入计分板:显示当前得分与最高分
- 支持键盘控制:方向键移动选择,空格确认
- 主题切换:深色模式、节日皮肤(如圣诞红绿配色)
🌟 Happy Coding with Flutter!
愿你的每一行代码,都如一个数字般精准合并;每一次交互,都带来逻辑的满足与游戏的乐趣。
更多推荐



所有评论(0)