一、效果展示

鸿蒙+Flutter 跨平台开发——简易猜数字竞猜游戏实现

📝 前言

随着移动互联网的快速发展和智能设备的普及,跨平台开发已成为移动应用开发的重要趋势。华为鸿蒙操作系统(HarmonyOS)作为新一代分布式操作系统,与Flutter框架的结合,为开发者提供了更广阔的应用场景和更高效的开发方式。本文将详细介绍如何使用Flutter框架开发一款简易猜数字竞猜游戏,并实现鸿蒙平台的跨平台部署。

🎮 游戏介绍

猜数字游戏是一款经典的益智游戏,规则简单易懂,适合各个年龄段的玩家:

游戏规则

  1. 🎲 系统随机生成一个1-100之间的整数
  2. 📱 玩家输入一个数字进行猜测
  3. 📊 系统根据玩家的猜测给出提示:
    • “猜大了!”:玩家猜测的数字大于目标数字
    • “猜小了!”:玩家猜测的数字小于目标数字
    • “恭喜你,猜对了!”:玩家猜测的数字等于目标数字
  4. 🏆 玩家需要在限定次数内猜对数字,挑战成功
  5. 📈 游戏记录玩家的猜测次数和用时,生成排行榜

游戏特点

  • 📱 跨平台支持:同时支持鸿蒙、Android、iOS等平台
  • 🎨 简洁美观的UI设计:采用Material Design设计语言
  • ⚡ 流畅的游戏体验:60fps流畅渲染
  • 🏆 完整的游戏系统:包含游戏主界面、设置界面、排行榜
  • 📊 实时的游戏数据统计:猜测次数、用时、胜率等
  • 🔊 丰富的音效反馈:增强游戏沉浸感
  • 🌓 深色/浅色主题:支持系统主题切换

🏗️ 核心功能实现

2. 核心技术选型

技术组件 用途 优势
Flutter UI框架 跨平台、高性能、丰富的组件库
Provider 状态管理 简单易用、性能优秀、适合中小型应用
Hive 本地存储 轻量级、高性能、适合存储游戏数据
AudioPlayers 音效管理 跨平台支持、简单易用
HarmonyOS SDK 鸿蒙平台支持 原生API访问、分布式能力支持

3. 游戏核心逻辑实现

3.1 游戏数据模型
/// 游戏难度枚举
enum GameDifficulty {
  /// 简单难度:1-100,10次机会
  easy,
  /// 中等难度:1-200,8次机会
  medium,
  /// 困难难度:1-500,6次机会
  hard,
}

/// 游戏状态枚举
enum GameStatus {
  /// 游戏未开始
  notStarted,
  /// 游戏进行中
  playing,
  /// 游戏胜利
  won,
  /// 游戏失败
  lost,
}

/// 游戏数据模型
class GameData {
  /// 目标数字
  int targetNumber;
  /// 玩家猜测次数
  int guessCount;
  /// 最大猜测次数
  int maxGuesses;
  /// 游戏状态
  GameStatus status;
  /// 游戏难度
  GameDifficulty difficulty;
  /// 游戏开始时间
  DateTime? startTime;
  /// 游戏结束时间
  DateTime? endTime;
  /// 玩家猜测的数字列表
  List<int> guessHistory;
  
  /// 构造函数
  GameData({
    required this.targetNumber,
    required this.guessCount,
    required this.maxGuesses,
    required this.status,
    required this.difficulty,
    this.startTime,
    this.endTime,
    this.guessHistory = const [],
  });
  
  /// 重置游戏数据
  void reset() {
    guessCount = 0;
    status = GameStatus.notStarted;
    startTime = null;
    endTime = null;
    guessHistory = [];
  }
  
  /// 获取游戏用时(秒)
  int get gameDuration {
    if (startTime == null || endTime == null) {
      return 0;
    }
    return endTime!.difference(startTime!).inSeconds;
  }
}
3.2 游戏逻辑实现
/// 猜数字游戏逻辑管理类
class GuessNumberGame {
  /// 随机数生成器
  final Random _random = Random();
  
  /// 游戏数据
  GameData gameData;
  
  /// 构造函数
  GuessNumberGame({
    required GameDifficulty difficulty,
  }) : gameData = GameData(
          targetNumber: 0,
          guessCount: 0,
          maxGuesses: _getMaxGuesses(difficulty),
          status: GameStatus.notStarted,
          difficulty: difficulty,
        ) {
    // 初始化游戏
    initializeGame();
  }
  
  /// 根据难度获取最大猜测次数
  static int _getMaxGuesses(GameDifficulty difficulty) {
    switch (difficulty) {
      case GameDifficulty.easy:
        return 10;
      case GameDifficulty.medium:
        return 8;
      case GameDifficulty.hard:
        return 6;
    }
  }
  
  /// 根据难度获取数字范围
  static int _getNumberRange(GameDifficulty difficulty) {
    switch (difficulty) {
      case GameDifficulty.easy:
        return 100;
      case GameDifficulty.medium:
        return 200;
      case GameDifficulty.hard:
        return 500;
    }
  }
  
  /// 初始化游戏
  void initializeGame() {
    // 生成目标数字
    final range = _getNumberRange(gameData.difficulty);
    gameData.targetNumber = _random.nextInt(range) + 1;
    // 重置游戏数据
    gameData.reset();
    // 设置游戏状态为进行中
    gameData.status = GameStatus.playing;
    // 记录游戏开始时间
    gameData.startTime = DateTime.now();
  }
  
  /// 玩家猜测数字
  /// [guess] 玩家猜测的数字
  /// 返回猜测结果提示
  String makeGuess(int guess) {
    // 检查游戏状态
    if (gameData.status != GameStatus.playing) {
      return '游戏未开始';
    }
    
    // 增加猜测次数
    gameData.guessCount++;
    // 添加到猜测历史
    gameData.guessHistory.add(guess);
    
    // 检查猜测结果
    if (guess < gameData.targetNumber) {
      // 猜小了
      return '猜小了!';
    } else if (guess > gameData.targetNumber) {
      // 猜大了
      return '猜大了!';
    } else {
      // 猜对了,游戏胜利
      gameData.status = GameStatus.won;
      gameData.endTime = DateTime.now();
      return '恭喜你,猜对了!';
    }
  }
  
  /// 检查游戏是否结束
  bool isGameOver() {
    return gameData.status == GameStatus.won || 
           gameData.status == GameStatus.lost;
  }
  
  /// 检查是否达到最大猜测次数
  void checkMaxGuesses() {
    if (gameData.guessCount >= gameData.maxGuesses && 
        gameData.status == GameStatus.playing) {
      // 达到最大猜测次数,游戏失败
      gameData.status = GameStatus.lost;
      gameData.endTime = DateTime.now();
    }
  }
  
  /// 切换游戏难度
  void changeDifficulty(GameDifficulty difficulty) {
    gameData.difficulty = difficulty;
    gameData.maxGuesses = _getMaxGuesses(difficulty);
    initializeGame();
  }
}

3. UI界面实现

3.1 游戏主页面
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../viewmodels/game_viewmodel.dart';
import '../widgets/guess_input_widget.dart';
import '../widgets/guess_history_widget.dart';
import '../widgets/game_info_widget.dart';

/// 猜数字游戏主页面
class GuessNumberScreen extends StatelessWidget {
  const GuessNumberScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => GameViewModel(),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('猜数字游戏'),
          backgroundColor: Colors.purple,
          actions: [
            // 设置按钮
            IconButton(
              onPressed: () => _navigateToSettings(context),
              icon: const Icon(Icons.settings),
            ),
            // 排行榜按钮
            IconButton(
              onPressed: () => _navigateToLeaderboard(context),
              icon: const Icon(Icons.leaderboard),
            ),
          ],
        ),
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                // 游戏信息显示
                const GameInfoWidget(),
                const SizedBox(height: 20),
                // 猜测输入区域
                const GuessInputWidget(),
                const SizedBox(height: 20),
                // 猜测历史记录
                const Expanded(child: GuessHistoryWidget()),
              ],
            ),
          ),
        ),
      ),
    );
  }
  
  /// 导航到设置页面
  void _navigateToSettings(BuildContext context) {
    Navigator.pushNamed(context, '/settings');
  }
  
  /// 导航到排行榜页面
  void _navigateToLeaderboard(BuildContext context) {
    Navigator.pushNamed(context, '/leaderboard');
  }
}
3.2 猜测输入组件
/// 猜测输入组件
class GuessInputWidget extends StatelessWidget {
  const GuessInputWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final gameViewModel = Provider.of<GameViewModel>(context);
    final TextEditingController _controller = TextEditingController();
    
    return Column(
      children: [
        // 输入框
        TextField(
          controller: _controller,
          keyboardType: TextInputType.number,
          decoration: InputDecoration(
            labelText: '请输入你猜测的数字',
            border: const OutlineInputBorder(),
            suffixIcon: IconButton(
              onPressed: () {
                _controller.clear();
              },
              icon: const Icon(Icons.clear),
            ),
          ),
          onSubmitted: (value) {
            // 提交猜测
            _makeGuess(context, value, _controller);
          },
        ),
        const SizedBox(height: 16),
        // 猜测按钮
        ElevatedButton(
          onPressed: () {
            // 提交猜测
            _makeGuess(context, _controller.text, _controller);
          },
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
            textStyle: const TextStyle(fontSize: 18),
          ),
          child: const Text('猜一下'),
        ),
        const SizedBox(height: 16),
        // 重新开始按钮
        OutlinedButton(
          onPressed: () {
            // 重新开始游戏
            gameViewModel.restartGame();
          },
          child: const Text('重新开始'),
        ),
      ],
    );
  }
  
  /// 提交猜测
  void _makeGuess(BuildContext context, String value, TextEditingController controller) {
    final gameViewModel = Provider.of<GameViewModel>(context, listen: false);
    
    // 检查输入是否为空
    if (value.isEmpty) {
      // 显示提示
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入数字')),
      );
      return;
    }
    
    // 转换为整数
    final guess = int.tryParse(value);
    if (guess == null) {
      // 显示提示
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入有效的数字')),
      );
      return;
    }
    
    // 执行猜测
    final result = gameViewModel.makeGuess(guess);
    
    // 显示猜测结果
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(result)),
    );
    
    // 清除输入框
    controller.clear();
    
    // 检查游戏是否结束
    if (gameViewModel.isGameOver()) {
      // 显示游戏结束弹窗
      _showGameOverDialog(context);
    }
  }
  
  /// 显示游戏结束弹窗
  void _showGameOverDialog(BuildContext context) {
    final gameViewModel = Provider.of<GameViewModel>(context, listen: false);
    
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(gameViewModel.isGameWon() ? '游戏胜利!' : '游戏失败!'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('目标数字:${gameViewModel.targetNumber}'),
            Text('猜测次数:${gameViewModel.guessCount}'),
            Text('用时:${gameViewModel.gameDuration}秒'),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              gameViewModel.restartGame();
            },
            child: const Text('再玩一次'),
          ),
        ],
      ),
    );
  }
}

4. 鸿蒙平台适配

4.1 项目配置

在Flutter项目中添加鸿蒙平台支持,需要在pubspec.yaml文件中添加相关配置:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.5
  hive: ^2.2.3
  hive_flutter: ^1.1.0
  audioplayers: ^5.2.1
  
dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^2.4.6
  hive_generator: ^2.0.1

flutter:
  uses-material-design: true
  
  # 鸿蒙平台特定配置
  harmonyos:
    resources:
      - type: color
        name: color_primary
        value: '#9C27B0'
      - type: string
        name: app_name
        value: '猜数字游戏'
      - type: media
        name: app_icon
        file: assets/images/app_icon.png
4.2 权限配置

在鸿蒙项目的module.json5文件中添加必要的权限:

{
  "module": {
    "name": "guess_number",
    "type": "entry",
    "description": "鸿蒙版猜数字游戏",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone", "tablet", "smartWatch"],
    "reqPermissions": [
      {
        "name": "ohos.permission.READ_USER_STORAGE",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.WRITE_USER_STORAGE",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ],
    // 其他配置...
  }
}
4.3 构建与部署

使用以下命令构建和部署鸿蒙应用:

# 构建鸿蒙发布版本
flutter build harmonyos --release

# 部署到鸿蒙设备
flutter run -d harmonyos

🔧 核心技术亮点

1. 跨平台特性

猜数字游戏基于Flutter框架开发,实现了真正的跨平台支持:

  • 📱 鸿蒙平台:支持鸿蒙2.0及以上版本
  • 🤖 Android平台:支持Android 5.0及以上版本
  • 🍎 iOS平台:支持iOS 11.0及以上版本
  • 💻 桌面平台:支持Windows、macOS、Linux

2. 性能优化

  • 状态管理优化:使用Provider进行局部状态更新,减少不必要的UI重绘
  • 内存优化:及时释放不再使用的资源,优化内存占用
  • 渲染优化:使用const构造函数和RepaintBoundary优化渲染性能
  • 存储优化:使用Hive进行高效的本地数据存储,读写速度快

3. 游戏体验优化

  • 流畅的动画效果:添加适当的过渡动画,提升用户体验
  • 丰富的音效反馈:在关键操作点添加音效,增强游戏沉浸感
  • 直观的UI设计:清晰的游戏信息展示,友好的用户界面
  • 实时的反馈机制:及时的猜测结果反馈,帮助玩家调整策略

4. 鸿蒙分布式能力集成

利用鸿蒙的分布式特性,实现跨设备的游戏体验:

// 分布式游戏示例
Future<void> startDistributedGame(List<String> deviceIds) async {
  // 生成目标数字
  final targetNumber = _random.nextInt(100) + 1;
  
  // 将游戏信息分发到其他设备
  for (final deviceId in deviceIds) {
    await HarmonyOS.distributeData(
      deviceId,
      {
        'gameType': 'guessNumber',
        'targetNumber': targetNumber,
        'difficulty': 'easy',
      },
    );
  }
  
  // 等待其他设备的响应
  HarmonyOS.onDistributedDataReceived.listen((data) {
    // 处理其他设备的猜测结果
    final deviceId = data['deviceId'];
    final guess = data['guess'];
    final result = _checkGuess(guess, targetNumber);
    
    // 返回结果给对应设备
    HarmonyOS.distributeData(deviceId, {
      'result': result,
    });
  });
}

📊 游戏数据统计

1. 本地存储设计

使用Hive进行游戏数据的本地存储,包括:

  • 🎮 游戏历史记录
  • 🏆 排行榜数据
  • ⚙️ 游戏设置
  • 📊 玩家数据
/// 本地存储服务类
class LocalStorageService {
  /// Hive数据库实例
  static late Box _gameBox;
  static late Box _leaderboardBox;
  static late Box _settingsBox;
  
  /// 初始化本地存储
  static Future<void> initialize() async {
    await Hive.initFlutter();
    
    // 注册适配器
    Hive.registerAdapter(GameDataAdapter());
    Hive.registerAdapter(PlayerDataAdapter());
    Hive.registerAdapter(LeaderboardDataAdapter());
    Hive.registerAdapter(GameSettingsAdapter());
    
    // 打开Box
    _gameBox = await Hive.openBox('game_data');
    _leaderboardBox = await Hive.openBox('leaderboard');
    _settingsBox = await Hive.openBox('settings');
  }
  
  /// 保存游戏数据
  static void saveGameData(GameData gameData) {
    _gameBox.put('current_game', gameData);
  }
  
  /// 获取游戏数据
  static GameData? getGameData() {
    return _gameBox.get('current_game');
  }
  
  /// 保存排行榜数据
  static void saveLeaderboardData(LeaderboardData leaderboardData) {
    final List<LeaderboardData> leaderboard = getLeaderboardData();
    leaderboard.add(leaderboardData);
    // 按用时排序,取前10名
    leaderboard.sort((a, b) => a.gameDuration.compareTo(b.gameDuration));
    if (leaderboard.length > 10) {
      leaderboard.removeRange(10, leaderboard.length);
    }
    _leaderboardBox.put('leaderboard', leaderboard);
  }
  
  /// 获取排行榜数据
  static List<LeaderboardData> getLeaderboardData() {
    return _leaderboardBox.get('leaderboard', defaultValue: []);
  }
  
  /// 保存游戏设置
  static void saveGameSettings(GameSettings settings) {
    _settingsBox.put('settings', settings);
  }
  
  /// 获取游戏设置
  static GameSettings getGameSettings() {
    return _settingsBox.get('settings', defaultValue: GameSettings());
  }
}

2. 排行榜功能实现

/// 排行榜数据模型
class LeaderboardData {
  /// 玩家名称
  String playerName;
  /// 游戏难度
  GameDifficulty difficulty;
  /// 猜测次数
  int guessCount;
  /// 游戏用时(秒)
  int gameDuration;
  /// 游戏时间
  DateTime gameTime;
  
  /// 构造函数
  LeaderboardData({
    required this.playerName,
    required this.difficulty,
    required this.guessCount,
    required this.gameDuration,
    required this.gameTime,
  });
}

/// 排行榜ViewModel
class LeaderboardViewModel extends ChangeNotifier {
  /// 排行榜数据列表
  List<LeaderboardData> _leaderboardData = [];
  
  /// 获取排行榜数据
  List<LeaderboardData> get leaderboardData => _leaderboardData;
  
  /// 构造函数
  LeaderboardViewModel() {
    // 加载排行榜数据
    loadLeaderboardData();
  }
  
  /// 加载排行榜数据
  void loadLeaderboardData() {
    _leaderboardData = LocalStorageService.getLeaderboardData();
    notifyListeners();
  }
  
  /// 清空排行榜数据
  void clearLeaderboardData() {
    LocalStorageService.saveLeaderboardData([]);
    _leaderboardData = [];
    notifyListeners();
  }
}

📝 总结

本文详细介绍了使用Flutter框架开发鸿蒙版简易猜数字竞猜游戏的全过程,包括游戏架构设计、核心功能实现、UI界面开发、鸿蒙平台适配和性能优化策略。通过本次开发,我们可以得出以下经验:

  1. 鸿蒙+Flutter的完美结合:Flutter的跨平台能力与鸿蒙的分布式特性相辅相成,为开发者提供了更广阔的应用场景

  2. 模块化设计的重要性:将游戏拆分为游戏逻辑、UI界面、数据存储等模块,便于维护和扩展

  3. 良好的状态管理是关键:使用Provider进行状态管理,确保了UI与数据的高效同步

  4. 性能优化不容忽视:通过渲染优化、内存优化和启动优化,提升了应用的整体性能

  5. 用户体验是核心:简洁直观的UI设计、流畅的交互体验和丰富的游戏反馈是游戏成功的关键

随着鸿蒙生态的不断发展和Flutter框架的持续优化,跨平台开发将变得更加高效和便捷。未来,我们可以进一步扩展猜数字游戏的功能,如添加多人对战、AI对手、更丰富的游戏模式等,为用户提供更加有趣和多样化的游戏体验。

🔮 未来展望

  • 👥 多人对战模式:支持多玩家在线对战
  • 🤖 AI对手:添加不同难度的AI对手
  • 🎨 主题切换:支持自定义主题
  • 🔊 更多音效:添加更丰富的游戏音效
  • 📱 分布式游戏:利用鸿蒙分布式能力,实现跨设备游戏
  • 📊 更详细的数据统计:玩家胜率、平均用时、最佳成绩等
  • 🎁 成就系统:添加游戏成就和奖励机制

通过不断优化和扩展功能,这款简易猜数字竞猜游戏将成为一款功能丰富、体验优秀的跨平台游戏应用,展示出Flutter+鸿蒙跨平台开发的强大潜力。


📚 参考资料

  1. Flutter官方文档
  2. HarmonyOS开发者文档
  3. Provider状态管理
  4. Hive本地存储
  5. AudioPlayers音效库
  6. Flutter for HarmonyOS

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐