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 个 12
  • 操作规则
    • 点击一个非空格子 → 选中(高亮)
    • 再点击一个相邻(上下左右)且数字相同的格子 → 合并
    • 合并结果:新数字 = 原数字 + 1,放置于下方或右侧的位置
  • 重力系统:合并后,上方数字下落填补空位
  • 新数字生成:每次合并后,在随机空位添加 12
  • 失败条件:无任何可合并的相邻对 → 游戏结束

技术映射

游戏概念 代码实体 数据结构
网格 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.generategrowable: 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:仅 gridselectedgameOver 触发更新
  • 高效算法:所有逻辑 O(1)(因网格固定)
  • 常量优化const 修饰 SliverGridDelegateEdgeInsets

用户体验细节

  • 即时反馈:选中高亮 + 粗边框,明确当前状态
  • 清晰指引:底部文字说明规则
  • 优雅失败:红色横幅提示 + FAB 重玩按钮
  • 防误触:空格不可点击,游戏结束禁用交互

九、扩展方向:从休闲游戏到策略挑战

当前实现是一个优秀的 MVP,但可进一步升级:

1. 难度分级

  • 初级:3×3 网格,仅 1/2
  • 高级:5×5 网格,初始填 10 个数字
  • 专家:引入障碍格(不可消除)

2. 得分系统

  • 基础分:合并数字值 × 10
  • 连击奖励:连续合并额外加分
  • 最高数字成就:合成 10+ 显示徽章

3. 撤销功能

  • 记录操作历史(网格快照)
  • 支持一步撤销(Ctrl+Z)

4. 动画效果

  • 合并时数字放大 + 淡出
  • 重力下落平滑动画(AnimatedPositioned

5. 本地存储

  • 保存最高分、最大合成数字
  • 每日挑战模式

十、总结:网格游戏中的工程智慧

这个"数字消消乐"游戏完美诠释了一个工程真理:复杂的游戏体验,可以源于简单而严谨的逻辑组合。就像乐高积木一样,通过精心设计的模块化组件,我们构建出了富有深度的游戏玩法。

具体实现中,我们采用了以下关键技术:

  1. 二维网格状态表示

    • 使用二维数组存储每个格子的数值和状态
    • 配合Flutter的Grid视图组件实现可视化渲染
    • 示例:List<List<int>> grid = List.generate(5, (_) => List.filled(5, 0));
  2. 双击交互模型

    • 通过GestureDetector捕获用户双击事件
    • 精确计算点击位置对应的网格坐标
    • 实现数值递增的连锁反应效果
  3. 列压缩重力算法

    • 自上而下遍历每列元素
    • 移除空白格时执行下落动画
    • 新元素从顶部平滑补位
  4. 相邻检测与失败判定

    • 四邻域检测算法判断相邻相同数字
    • 实时检查网格填满状态
    • 提供清晰的游戏结束反馈

这些技术组合让我们得以在不到200行Dart代码内,构建出一个具有以下特点的精品游戏:

  • 规则清晰:简单易懂的数字消除机制
  • 反馈及时:流畅的动画和即时响应
  • 视觉愉悦:Material Design风格的UI设计

更重要的是,本项目充分展示了Flutter框架在构建实时交互应用方面的强大能力

  • 声明式UI:通过Widget树直观描述界面结构
  • 响应式状态流:setState触发自动UI更新
  • 高效渲染:Skia引擎保障60fps流畅体验

这种架构模式具有广泛的适用性,特别适合开发:

  • 益智类游戏(如2048、数独)
  • 棋盘类应用(象棋、围棋)
  • 数据可视化工具(热力图、矩阵展示)

通过本项目积累的经验,你可以快速搭建起各类网格交互应用的开发框架,为更复杂的产品奠定坚实基础。


附录:动手实验建议

  1. 添加合并音效:成功合并播放“叮”声
  2. 实现动画:使用 AnimatedContainer 实现数字下落
  3. 引入计分板:显示当前得分与最高分
  4. 支持键盘控制:方向键移动选择,空格确认
  5. 主题切换:深色模式、节日皮肤(如圣诞红绿配色)

🌟 Happy Coding with Flutter!
愿你的每一行代码,都如一个数字般精准合并;每一次交互,都带来逻辑的满足与游戏的乐趣。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐