Flutter 框架跨平台鸿蒙开发 - 消消乐游戏开发教程

Flutter消消乐游戏开发教程

项目简介

消消乐是一款经典的三消益智游戏,玩家通过交换相邻宝石,使三个或更多相同颜色的宝石连成一线即可消除并获得分数。本项目使用Flutter实现了完整的消消乐游戏,包含连击系统、动画特效、步数限制等核心玩法。

运行效果图

核心特性

  • 8×8游戏棋盘:标准的消消乐游戏布局
  • 6种宝石类型:红、蓝、绿、黄、紫、橙六种颜色宝石
  • 智能匹配算法:自动检测横向和纵向的三消组合
  • 流畅交换机制:支持相邻宝石交换,无效交换自动回退
  • 自动下落填充:消除后宝石自动下落,顶部生成新宝石
  • 连击系统:连续消除触发COMBO,额外加分
  • 步数限制:30步内尽可能获得高分
  • 最高分记录:使用SharedPreferences持久化存储
  • 精美动画:消除动画、选中高亮、连击特效

技术架构

数据模型设计

GemType枚举
dart 复制代码
enum GemType { red, blue, green, yellow, purple, orange }

定义了6种宝石类型,每种类型对应不同的颜色和图标。

Gem类
dart 复制代码
class Gem {
  GemType type;        // 宝石类型
  int row;             // 行坐标
  int col;             // 列坐标
  bool isMatched;      // 是否已匹配(用于消除动画)

  Color get color { ... }      // 根据类型返回颜色
  IconData get icon { ... }    // 根据类型返回图标
}

Gem类封装了宝石的所有属性和行为:

  • type:宝石类型,决定颜色和图标
  • rowcol:宝石在棋盘上的位置坐标
  • isMatched:标记宝石是否已被匹配,用于播放消除动画
  • color:根据类型返回对应的Material颜色
  • icon:根据类型返回对应的Material图标

游戏状态管理

dart 复制代码
class _GamePageState extends State<GamePage> with SingleTickerProviderStateMixin {
  static const int rows = 8;
  static const int cols = 8;
  
  late List<List<Gem?>> board;  // 二维数组存储棋盘
  Gem? selectedGem;             // 当前选中的宝石
  
  int score = 0;                // 当前得分
  int moves = 30;               // 剩余步数
  int combo = 0;                // 连击数
  int highScore = 0;            // 最高分
  
  bool isProcessing = false;    // 是否正在处理消除
  bool gameOver = false;        // 游戏是否结束
  
  late AnimationController _comboController;  // 连击动画控制器
  late Animation<double> _comboAnimation;     // 连击动画
}

核心算法详解

1. 棋盘初始化算法

dart 复制代码
void _initBoard() {
  // 生成随机棋盘
  board = List.generate(
    rows,
    (row) => List.generate(
      cols,
      (col) => _createRandomGem(row, col),
    ),
  );
  
  // 确保初始棋盘没有可消除的组合
  while (_hasMatches()) {
    _shuffleBoard();
  }
}

算法思路

  1. 使用List.generate创建8×8的二维数组
  2. 每个位置随机生成一个宝石
  3. 检查是否存在三消组合,如果存在则重新洗牌
  4. 重复步骤3直到没有初始可消除组合

这样可以确保游戏开始时玩家需要主动交换宝石才能消除,而不是一开始就有现成的消除组合。

2. 三消匹配算法

dart 复制代码
bool _hasMatches() {
  // 检查横向匹配
  for (int row = 0; row < rows; row++) {
    for (int col = 0; col < cols - 2; col++) {
      final gem1 = board[row][col];
      final gem2 = board[row][col + 1];
      final gem3 = board[row][col + 2];
      
      if (gem1 != null && gem2 != null && gem3 != null) {
        if (gem1.type == gem2.type && gem2.type == gem3.type) {
          return true;
        }
      }
    }
  }
  
  // 检查纵向匹配
  for (int col = 0; col < cols; col++) {
    for (int row = 0; row < rows - 2; row++) {
      final gem1 = board[row][col];
      final gem2 = board[row + 1][col];
      final gem3 = board[row + 2][col];
      
      if (gem1 != null && gem2 != null && gem3 != null) {
        if (gem1.type == gem2.type && gem2.type == gem3.type) {
          return true;
        }
      }
    }
  }
  
  return false;
}

算法思路

  1. 横向扫描:遍历每一行,检查连续3个宝石是否类型相同
  2. 纵向扫描:遍历每一列,检查连续3个宝石是否类型相同
  3. 只要找到一个匹配就返回true,否则返回false

时间复杂度:O(rows × cols)

3. 查找所有匹配算法

dart 复制代码
List<Gem> _findMatches() {
  final Set<Gem> matches = {};
  
  // 横向匹配(支持3个以上连续匹配)
  for (int row = 0; row < rows; row++) {
    for (int col = 0; col < cols - 2; col++) {
      final gem1 = board[row][col];
      final gem2 = board[row][col + 1];
      final gem3 = board[row][col + 2];
      
      if (gem1 != null && gem2 != null && gem3 != null) {
        if (gem1.type == gem2.type && gem2.type == gem3.type) {
          matches.addAll([gem1, gem2, gem3]);
          
          // 继续向右查找更多匹配
          for (int i = col + 3; i < cols; i++) {
            final gem = board[row][i];
            if (gem != null && gem.type == gem1.type) {
              matches.add(gem);
            } else {
              break;
            }
          }
        }
      }
    }
  }
  
  // 纵向匹配(同样支持3个以上)
  // ... 类似逻辑
  
  return matches.toList();
}

算法亮点

  • 使用Set自动去重,避免同一个宝石被重复添加
  • 支持4连消、5连消等更长的匹配
  • 返回所有需要消除的宝石列表

4. 宝石交换算法

dart 复制代码
Future<void> _swapGems(Gem gem1, Gem gem2) async {
  setState(() { isProcessing = true; });
  
  // 交换坐标
  final tempRow = gem1.row;
  final tempCol = gem1.col;
  gem1.row = gem2.row;
  gem1.col = gem2.col;
  gem2.row = tempRow;
  gem2.col = tempCol;
  
  // 更新棋盘
  board[gem1.row][gem1.col] = gem1;
  board[gem2.row][gem2.col] = gem2;
  
  setState(() {});
  await Future.delayed(const Duration(milliseconds: 200));
  
  // 检查是否产生匹配
  if (_hasMatches()) {
    setState(() {
      selectedGem = null;
      moves--;  // 扣除步数
    });
    await _processMatches();  // 处理消除
    
    if (moves <= 0) {
      _endGame();  // 步数用完,游戏结束
    }
  } else {
    // 无效交换,回退
    final tempRow2 = gem1.row;
    final tempCol2 = gem1.col;
    gem1.row = gem2.row;
    gem1.col = gem2.col;
    gem2.row = tempRow2;
    gem2.col = tempCol2;
    
    board[gem1.row][gem1.col] = gem1;
    board[gem2.row][gem2.col] = gem2;
    
    setState(() { selectedGem = null; });
  }
  
  setState(() { isProcessing = false; });
}

算法流程

  1. 锁定处理状态,防止重复操作
  2. 交换两个宝石的坐标和棋盘位置
  3. 延迟200ms播放交换动画
  4. 检查是否产生匹配:
    • 有匹配:扣除步数,处理消除
    • 无匹配:回退交换,恢复原状
  5. 解锁处理状态

5. 下落填充算法

dart 复制代码
Future<void> _dropGems() async {
  for (int col = 0; col < cols; col++) {
    int emptyRow = rows - 1;  // 从底部开始
    
    for (int row = rows - 1; row >= 0; row--) {
      if (board[row][col] != null) {
        if (row != emptyRow) {
          // 将宝石移动到空位
          board[emptyRow][col] = board[row][col];
          board[emptyRow][col]!.row = emptyRow;
          board[row][col] = null;
        }
        emptyRow--;  // 向上移动空位指针
      }
    }
  }
}

void _fillBoard() {
  for (int col = 0; col < cols; col++) {
    for (int row = 0; row < rows; row++) {
      if (board[row][col] == null) {
        board[row][col] = _createRandomGem(row, col);
      }
    }
  }
}

算法思路

  1. 下落阶段

    • 按列处理,从底部向上扫描
    • 使用emptyRow指针标记当前可填充的最底部空位
    • 遇到非空宝石就移动到emptyRow位置
    • 移动后emptyRow向上移动一格
  2. 填充阶段

    • 遍历整个棋盘
    • 遇到空位就生成新的随机宝石

时间复杂度:O(rows × cols)

6. 连击处理算法

dart 复制代码
Future<void> _processMatches() async {
  combo = 0;  // 重置连击数
  
  while (true) {
    final matches = _findMatches();
    if (matches.isEmpty) break;  // 没有匹配,退出循环
    
    combo++;  // 连击数+1
    
    // 标记匹配的宝石
    for (var gem in matches) {
      gem.isMatched = true;
    }
    
    setState(() {});
    await Future.delayed(const Duration(milliseconds: 300));
    
    // 计算得分
    final baseScore = matches.length * 10;
    final comboBonus = combo > 1 ? (combo - 1) * 20 : 0;
    setState(() {
      score += baseScore + comboBonus;
    });
    
    // 播放连击动画
    if (combo > 1) {
      _comboController.forward(from: 0);
    }
    
    // 移除匹配的宝石
    for (var gem in matches) {
      board[gem.row][gem.col] = null;
    }
    
    setState(() {});
    await Future.delayed(const Duration(milliseconds: 200));
    
    // 下落和填充
    await _dropGems();
    _fillBoard();
    
    setState(() {});
    await Future.delayed(const Duration(milliseconds: 300));
  }
}

算法流程

  1. 进入循环,查找所有匹配
  2. 如果没有匹配,退出循环
  3. 连击数+1,标记匹配的宝石
  4. 计算得分:基础分 = 消除数量 × 10,连击奖励 = (连击数-1) × 20
  5. 播放消除动画和连击特效
  6. 移除匹配的宝石
  7. 执行下落和填充
  8. 回到步骤1,继续检查是否有新的匹配

连击机制

  • 第1次消除:combo=1,得分 = n×10
  • 第2次消除:combo=2,得分 = n×10 + 20
  • 第3次消除:combo=3,得分 = n×10 + 40
  • 以此类推,连击越高奖励越多

UI组件设计

1. 得分板

dart 复制代码
Widget _buildScoreBoard() {
  return Container(
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Theme.of(context).colorScheme.primaryContainer,
    ),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        _buildScoreItem('得分', score.toString()),
        _buildScoreItem('步数', moves.toString()),
        _buildScoreItem('最高分', highScore.toString()),
      ],
    ),
  );
}

得分板显示三个关键信息:

  • 得分:当前游戏得分
  • 步数:剩余可用步数
  • 最高分:历史最高分记录

2. 游戏棋盘

dart 复制代码
Widget _buildBoard() {
  return Container(
    margin: const EdgeInsets.all(8),
    decoration: BoxDecoration(
      color: Theme.of(context).colorScheme.surface,
      borderRadius: BorderRadius.circular(16),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withValues(alpha: 0.2),
          blurRadius: 10,
          offset: const Offset(0, 5),
        ),
      ],
    ),
    child: GridView.builder(
      padding: const EdgeInsets.all(8),
      physics: const NeverScrollableScrollPhysics(),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: cols,
        mainAxisSpacing: 4,
        crossAxisSpacing: 4,
      ),
      itemCount: rows * cols,
      itemBuilder: (context, index) {
        final row = index ~/ cols;
        final col = index % cols;
        final gem = board[row][col];
        
        // 渲染宝石或空位
        // ...
      },
    ),
  );
}

设计要点

  • 使用GridView.builder创建8×8网格
  • AspectRatio(aspectRatio: 1)确保棋盘是正方形
  • 添加圆角和阴影提升视觉效果
  • NeverScrollableScrollPhysics禁止滚动

3. 宝石渲染

dart 复制代码
GestureDetector(
  onTap: () => _onGemTap(row, col),
  child: AnimatedContainer(
    duration: const Duration(milliseconds: 200),
    decoration: BoxDecoration(
      color: gem.isMatched
          ? Colors.transparent
          : gem.color.withValues(alpha: 0.8),
      borderRadius: BorderRadius.circular(8),
      border: isSelected
          ? Border.all(color: Colors.white, width: 3)
          : null,
      boxShadow: isSelected
          ? [
              BoxShadow(
                color: gem.color.withValues(alpha: 0.5),
                blurRadius: 10,
                spreadRadius: 2,
              ),
            ]
          : null,
    ),
    child: AnimatedScale(
      duration: const Duration(milliseconds: 200),
      scale: gem.isMatched ? 0.0 : (isSelected ? 1.1 : 1.0),
      child: Icon(
        gem.icon,
        color: Colors.white,
        size: 32,
      ),
    ),
  ),
)

动画效果

  • AnimatedContainer:平滑过渡颜色、边框、阴影变化
  • AnimatedScale
    • 选中状态:放大到1.1倍
    • 消除状态:缩小到0(消失效果)
    • 正常状态:1.0倍
  • 选中时添加白色边框和发光阴影

4. 连击指示器

dart 复制代码
Widget _buildComboIndicator() {
  return AnimatedBuilder(
    animation: _comboAnimation,
    builder: (context, child) {
      return Transform.scale(
        scale: _comboAnimation.value,
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
          margin: const EdgeInsets.only(bottom: 16),
          decoration: BoxDecoration(
            gradient: const LinearGradient(
              colors: [Colors.orange, Colors.deepOrange],
            ),
            borderRadius: BorderRadius.circular(24),
            boxShadow: [
              BoxShadow(
                color: Colors.orange.withValues(alpha: 0.5),
                blurRadius: 10,
                spreadRadius: 2,
              ),
            ],
          ),
          child: Text(
            'COMBO x$combo',
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      );
    },
  );
}

动画实现

dart 复制代码
_comboController = AnimationController(
  duration: const Duration(milliseconds: 500),
  vsync: this,
);

_comboAnimation = Tween<double>(begin: 1.0, end: 1.5).animate(
  CurvedAnimation(parent: _comboController, curve: Curves.elasticOut),
);
  • 使用AnimationController控制动画
  • Tween<double>(begin: 1.0, end: 1.5):从1.0倍放大到1.5倍
  • Curves.elasticOut:弹性曲线,产生弹跳效果
  • 渐变背景和发光阴影增强视觉冲击力

数据持久化

最高分存储

dart 复制代码
Future<void> _loadHighScore() async {
  final prefs = await SharedPreferences.getInstance();
  setState(() {
    highScore = prefs.getInt('high_score') ?? 0;
  });
}

Future<void> _saveHighScore() async {
  if (score > highScore) {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setInt('high_score', score);
    setState(() {
      highScore = score;
    });
  }
}

使用shared_preferences包实现本地存储:

  • 游戏启动时加载最高分
  • 游戏结束时检查是否刷新最高分
  • 只有当前得分超过历史最高分才更新

游戏流程图

有匹配
无匹配












游戏开始
初始化棋盘
检查初始匹配
重新洗牌
等待玩家操作
点击宝石
是否已选中
选中宝石
是否相邻
交换宝石
是否产生匹配
回退交换
扣除步数
处理消除
标记匹配宝石
播放消除动画
计算得分
移除宝石
下落填充
是否有新匹配
连击数+1
步数是否用完
游戏结束
保存最高分
显示结果
重新开始?
结束

核心算法复杂度分析

算法 时间复杂度 空间复杂度 说明
棋盘初始化 O(n²) O(n²) n为棋盘边长(8)
匹配检测 O(n²) O(1) 遍历整个棋盘
查找所有匹配 O(n²) O(k) k为匹配宝石数量
宝石交换 O(1) O(1) 常数时间操作
下落填充 O(n²) O(1) 按列处理
连击处理 O(m·n²) O(k) m为连击次数

游戏平衡性设计

得分系统

总得分=∑i=1n(消除数量i×10+连击奖励i) \text{总得分} = \sum_{i=1}^{n} (\text{消除数量}_i \times 10 + \text{连击奖励}_i) 总得分=i=1∑n(消除数量i×10+连击奖励i)

连击奖励i={0if combo=1(combo−1)×20if combo>1 \text{连击奖励}_i = \begin{cases} 0 & \text{if combo} = 1 \\ (\text{combo} - 1) \times 20 & \text{if combo} > 1 \end{cases} 连击奖励i={0(combo−1)×20if combo=1if combo>1

步数限制

  • 初始步数:30步
  • 每次有效交换扣除1步
  • 无效交换不扣步数
  • 步数用完游戏结束

难度曲线

  • 前期:棋盘随机性高,容易找到消除机会
  • 中期:需要策略性思考,寻找连击机会
  • 后期:步数紧张,需要精打细算

功能扩展建议

1. 道具系统

dart 复制代码
enum PowerUpType {
  bomb,      // 炸弹:消除周围3×3区域
  hammer,    // 锤子:消除单个宝石
  shuffle,   // 洗牌:重新排列棋盘
  colorBomb, // 彩虹糖:消除所有同色宝石
}

class PowerUp {
  PowerUpType type;
  int count;
  
  void use(int row, int col) {
    switch (type) {
      case PowerUpType.bomb:
        _explodeBomb(row, col);
        break;
      case PowerUpType.hammer:
        _useHammer(row, col);
        break;
      // ...
    }
    count--;
  }
}

2. 特殊宝石

dart 复制代码
enum SpecialGemType {
  striped,   // 条纹宝石:消除整行或整列
  wrapped,   // 包装宝石:消除周围3×3
  color,     // 彩色宝石:消除所有同色
}

class SpecialGem extends Gem {
  SpecialGemType specialType;
  
  // 4连消生成条纹宝石
  // 5连消生成彩色宝石
  // L型或T型消除生成包装宝石
}

3. 关卡模式

dart 复制代码
class Level {
  int levelNumber;
  int targetScore;      // 目标分数
  int maxMoves;         // 最大步数
  List<GemType> gems;   // 可用宝石类型
  List<Obstacle> obstacles; // 障碍物
  
  bool isCompleted(int score) {
    return score >= targetScore;
  }
}

class Obstacle {
  int row;
  int col;
  ObstacleType type;  // 冰块、锁链等
  int health;         // 需要消除几次
}

4. 每日挑战

dart 复制代码
class DailyChallenge {
  DateTime date;
  int seed;           // 随机种子,确保所有玩家相同棋盘
  int targetScore;
  List<int> rewards;  // 奖励道具
  
  bool isAvailable() {
    return DateTime.now().day == date.day;
  }
}

5. 成就系统

dart 复制代码
class Achievement {
  String id;
  String title;
  String description;
  int progress;
  int target;
  bool isUnlocked;
  
  // 示例成就
  static final achievements = [
    Achievement(
      id: 'combo_master',
      title: '连击大师',
      description: '达成10连击',
      target: 10,
    ),
    Achievement(
      id: 'high_scorer',
      title: '高分玩家',
      description: '单局得分超过5000',
      target: 5000,
    ),
    Achievement(
      id: 'efficient',
      title: '效率专家',
      description: '用20步内完成游戏',
      target: 20,
    ),
  ];
}

6. 音效和震动反馈

dart 复制代码
import 'package:audioplayers/audioplayers.dart';
import 'package:vibration/vibration.dart';

class SoundManager {
  final AudioPlayer _player = AudioPlayer();
  
  void playMatchSound() {
    _player.play(AssetSource('sounds/match.mp3'));
  }
  
  void playComboSound(int combo) {
    _player.play(AssetSource('sounds/combo_$combo.mp3'));
  }
  
  void playSwapSound() {
    _player.play(AssetSource('sounds/swap.mp3'));
  }
}

class HapticManager {
  void lightImpact() {
    Vibration.vibrate(duration: 50);
  }
  
  void mediumImpact() {
    Vibration.vibrate(duration: 100);
  }
  
  void heavyImpact() {
    Vibration.vibrate(duration: 200);
  }
}

7. 排行榜系统

dart 复制代码
class Leaderboard {
  List<LeaderboardEntry> entries;
  
  Future<void> submitScore(int score) async {
    // 提交到云端排行榜
    await FirebaseFirestore.instance
        .collection('leaderboard')
        .add({
      'score': score,
      'userId': currentUserId,
      'timestamp': FieldValue.serverTimestamp(),
    });
  }
  
  Future<List<LeaderboardEntry>> getTopScores(int limit) async {
    final snapshot = await FirebaseFirestore.instance
        .collection('leaderboard')
        .orderBy('score', descending: true)
        .limit(limit)
        .get();
    
    return snapshot.docs
        .map((doc) => LeaderboardEntry.fromJson(doc.data()))
        .toList();
  }
}

class LeaderboardEntry {
  String userId;
  String username;
  int score;
  DateTime timestamp;
}

性能优化建议

1. 对象池模式

dart 复制代码
class GemPool {
  final List<Gem> _pool = [];
  
  Gem obtain(GemType type, int row, int col) {
    if (_pool.isNotEmpty) {
      final gem = _pool.removeLast();
      gem.type = type;
      gem.row = row;
      gem.col = col;
      gem.isMatched = false;
      return gem;
    }
    return Gem(type: type, row: row, col: col);
  }
  
  void recycle(Gem gem) {
    _pool.add(gem);
  }
}

2. 减少setState调用

dart 复制代码
// 不好的做法
for (var gem in matches) {
  gem.isMatched = true;
  setState(() {});  // 每次循环都重建UI
}

// 好的做法
for (var gem in matches) {
  gem.isMatched = true;
}
setState(() {});  // 只调用一次

3. 使用const构造函数

dart 复制代码
// 减少不必要的重建
const SizedBox(height: 8)
const Text('得分')
const Icon(Icons.refresh)

4. 延迟加载资源

dart 复制代码
class AssetPreloader {
  static Future<void> preloadAssets() async {
    await Future.wait([
      precacheImage(AssetImage('assets/images/gem_red.png'), context),
      precacheImage(AssetImage('assets/images/gem_blue.png'), context),
      // ...
    ]);
  }
}

测试用例设计

单元测试

dart 复制代码
void main() {
  group('Gem Tests', () {
    test('Gem color should match type', () {
      final gem = Gem(type: GemType.red, row: 0, col: 0);
      expect(gem.color, Colors.red);
    });
    
    test('Gem icon should match type', () {
      final gem = Gem(type: GemType.blue, row: 0, col: 0);
      expect(gem.icon, Icons.water_drop);
    });
  });
  
  group('Match Detection Tests', () {
    test('Should detect horizontal match', () {
      final board = [
        [Gem(type: GemType.red, row: 0, col: 0),
         Gem(type: GemType.red, row: 0, col: 1),
         Gem(type: GemType.red, row: 0, col: 2)],
      ];
      expect(hasHorizontalMatch(board, 0, 0), true);
    });
    
    test('Should detect vertical match', () {
      final board = [
        [Gem(type: GemType.blue, row: 0, col: 0)],
        [Gem(type: GemType.blue, row: 1, col: 0)],
        [Gem(type: GemType.blue, row: 2, col: 0)],
      ];
      expect(hasVerticalMatch(board, 0, 0), true);
    });
    
    test('Should not detect match with only 2 gems', () {
      final board = [
        [Gem(type: GemType.green, row: 0, col: 0),
         Gem(type: GemType.green, row: 0, col: 1),
         Gem(type: GemType.red, row: 0, col: 2)],
      ];
      expect(hasHorizontalMatch(board, 0, 0), false);
    });
  });
  
  group('Swap Tests', () {
    test('Should detect adjacent gems', () {
      final gem1 = Gem(type: GemType.red, row: 0, col: 0);
      final gem2 = Gem(type: GemType.blue, row: 0, col: 1);
      expect(isAdjacent(gem1, gem2), true);
    });
    
    test('Should not detect non-adjacent gems', () {
      final gem1 = Gem(type: GemType.red, row: 0, col: 0);
      final gem2 = Gem(type: GemType.blue, row: 2, col: 2);
      expect(isAdjacent(gem1, gem2), false);
    });
  });
  
  group('Score Calculation Tests', () {
    test('Should calculate base score correctly', () {
      final matches = List.generate(3, (i) => 
        Gem(type: GemType.red, row: 0, col: i));
      expect(calculateScore(matches, 1), 30); // 3 * 10
    });
    
    test('Should calculate combo bonus correctly', () {
      final matches = List.generate(3, (i) => 
        Gem(type: GemType.red, row: 0, col: i));
      expect(calculateScore(matches, 2), 50); // 3*10 + 20
      expect(calculateScore(matches, 3), 70); // 3*10 + 40
    });
  });
}

Widget测试

dart 复制代码
void main() {
  testWidgets('Should display score board', (tester) async {
    await tester.pumpWidget(const MyApp());
    
    expect(find.text('得分'), findsOneWidget);
    expect(find.text('步数'), findsOneWidget);
    expect(find.text('最高分'), findsOneWidget);
  });
  
  testWidgets('Should display 8x8 grid', (tester) async {
    await tester.pumpWidget(const MyApp());
    
    expect(find.byType(GridView), findsOneWidget);
    // 验证有64个宝石格子
  });
  
  testWidgets('Should show combo indicator on combo', (tester) async {
    await tester.pumpWidget(const MyApp());
    
    // 模拟连击
    // ...
    
    expect(find.textContaining('COMBO'), findsOneWidget);
  });
  
  testWidgets('Should show game over dialog when moves run out', 
      (tester) async {
    await tester.pumpWidget(const MyApp());
    
    // 模拟步数用完
    // ...
    
    await tester.pumpAndSettle();
    expect(find.text('游戏结束'), findsOneWidget);
    expect(find.text('重新开始'), findsOneWidget);
  });
}

调试技巧

1. 打印棋盘状态

dart 复制代码
void _printBoard() {
  print('=== Board State ===');
  for (int row = 0; row < rows; row++) {
    String line = '';
    for (int col = 0; col < cols; col++) {
      final gem = board[row][col];
      if (gem == null) {
        line += '- ';
      } else {
        line += '${gem.type.toString().split('.').last[0]} ';
      }
    }
    print(line);
  }
  print('Score: $score, Moves: $moves, Combo: $combo');
}

2. 匹配可视化

dart 复制代码
void _highlightMatches() {
  final matches = _findMatches();
  print('Found ${matches.length} matches:');
  for (var gem in matches) {
    print('  ${gem.type} at (${gem.row}, ${gem.col})');
  }
}

3. 性能监控

dart 复制代码
void _measurePerformance(String operation, Function fn) {
  final stopwatch = Stopwatch()..start();
  fn();
  stopwatch.stop();
  print('$operation took ${stopwatch.elapsedMilliseconds}ms');
}

// 使用
_measurePerformance('Process Matches', () {
  _processMatches();
});

常见问题解决

1. 宝石不下落

问题 :消除后宝石没有下落
原因 :下落算法中没有正确更新宝石的row属性
解决

dart 复制代码
board[emptyRow][col]!.row = emptyRow;  // 必须更新row

2. 无限循环

问题 :连击处理陷入无限循环
原因 :填充新宝石后立即产生匹配
解决:添加循环次数限制或改进填充算法

dart 复制代码
int maxIterations = 10;
while (maxIterations-- > 0) {
  final matches = _findMatches();
  if (matches.isEmpty) break;
  // ...
}

3. 动画卡顿

问题 :消除动画不流畅
原因 :setState调用过于频繁
解决:合并setState调用,使用AnimatedWidget

dart 复制代码
// 使用AnimatedBuilder减少重建
AnimatedBuilder(
  animation: _animation,
  builder: (context, child) {
    return Transform.scale(scale: _animation.value, child: child);
  },
  child: Icon(gem.icon),  // child不会重建
)

4. 内存泄漏

问题 :长时间游戏后内存占用增加
原因 :AnimationController未释放
解决

dart 复制代码
@override
void dispose() {
  _comboController.dispose();
  super.dispose();
}

项目结构建议

复制代码
lib/
├── main.dart
├── models/
│   ├── gem.dart              # 宝石模型
│   ├── level.dart            # 关卡模型
│   └── achievement.dart      # 成就模型
├── screens/
│   ├── game_page.dart        # 游戏主页面
│   ├── menu_page.dart        # 菜单页面
│   └── leaderboard_page.dart # 排行榜页面
├── widgets/
│   ├── game_board.dart       # 游戏棋盘组件
│   ├── gem_widget.dart       # 宝石组件
│   ├── score_board.dart      # 得分板组件
│   └── combo_indicator.dart  # 连击指示器组件
├── services/
│   ├── game_logic.dart       # 游戏逻辑服务
│   ├── sound_manager.dart    # 音效管理
│   └── storage_service.dart  # 数据存储服务
└── utils/
    ├── constants.dart        # 常量定义
    └── helpers.dart          # 辅助函数

依赖包说明

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.2.2  # 本地数据存储
  
  # 可选扩展
  audioplayers: ^6.0.0        # 音效播放
  vibration: ^1.8.4           # 震动反馈
  firebase_core: ^2.24.2      # Firebase核心
  cloud_firestore: ^4.14.0    # 云端排行榜

总结

本项目实现了一个功能完整的消消乐游戏,包含以下核心技术点:

  1. 算法设计:三消匹配、下落填充、连击计算
  2. 动画系统:AnimationController、AnimatedContainer、AnimatedScale
  3. 状态管理:使用setState管理游戏状态
  4. 数据持久化:SharedPreferences存储最高分
  5. UI设计:Material Design 3风格,流畅的交互体验

通过本教程,你可以学习到:

  • 二维数组的操作和算法
  • Flutter动画系统的使用
  • 游戏逻辑的设计和实现
  • 性能优化的技巧
  • 代码组织和架构设计

这个项目可以作为学习Flutter游戏开发的起点,通过扩展功能可以打造更加丰富的游戏体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
数通工程师2 小时前
IPv4和IPv6 地址分配:从划分到工具全解析
网络·网络协议·tcp/ip·华为
夜雨声烦丿2 小时前
Flutter 框架跨平台鸿蒙开发 - 数独求解器开发教程
flutter·游戏·华为·harmonyos
世人万千丶3 小时前
Day 5: Flutter 框架文件系统交互 - 鸿蒙沙盒机制下的文件读写与安全策略
学习·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
南村群童欺我老无力.4 小时前
Flutter 框架跨平台鸿蒙开发 - 白噪音助眠应用开发指南
flutter·华为·harmonyos
猛扇赵四那边好嘴.4 小时前
Flutter 框架跨平台鸿蒙开发 - 睡眠记录应用开发教程
flutter·华为·harmonyos
鸣弦artha4 小时前
Flutter框架跨平台鸿蒙开发——InheritedWidget基础使用-计数器案例
android·flutter·harmonyos
da_vinci_x4 小时前
图标量产:从“手绘地狱”到“风格克隆”?Style Reference 的工业化实战
前端·游戏·ui·prompt·aigc·设计师·游戏美术
鸣弦artha5 小时前
Flutter框架跨平台鸿蒙开发——Future基础与数据加载
flutter
ljt27249606615 小时前
Flutter笔记--Isolate
笔记·flutter