[请在此处添加游戏界面截图,展示掉落的水果和底部的篮子]

引言:跨平台游戏开发的新时代

在前一个示例中,我们开发了打地鼠游戏,学到了如何使用定时器和动画来创建反应类游戏。现在,我们将进一步探索 Flutter 和 OpenHarmony 的强大能力,开发一个接水果游戏

这个游戏不仅展示了滑动手势识别、物理模拟和碰撞检测等高级概念,更重要的是,它充分利用了 Flutter 和 OpenHarmony 的跨平台特性。OpenHarmony 是华为推出的开源操作系统,与 Flutter 的结合为开发者提供了一个强大的跨平台开发框架。通过这个游戏,你将学到如何编写能够在 Android、iOS 和 OpenHarmony 设备上无缝运行的代码。

游戏概述与设计理念

游戏规则

接水果游戏的核心机制非常简单但富有挑战性:

  1. 水果掉落:各种水果从屏幕顶部随机位置掉落
  2. 滑动篮子:玩家通过左右滑动屏幕来移动篮子
  3. 接住水果:当水果掉落到篮子时,玩家获得相应的分数
  4. 时间限制:游戏持续 30 秒,时间结束时游戏结束

跨平台考虑

这个游戏的设计充分考虑了跨平台的需求:

  • 手势识别:使用 GestureDetector 识别水平滑动,这在 Android、iOS 和 OpenHarmony 上都能正常工作
  • 相对坐标:使用相对坐标而不是绝对像素,确保在不同屏幕尺寸的设备上都能正确显示
  • 性能优化:使用高效的列表管理和定时器控制,确保在低端设备上也能流畅运行

核心数据结构

FallingFruit 类

class FallingFruit {
  double x;
  double y;
  String emoji;
  int points;
  bool caught;

  FallingFruit({
    required this.x,
    required this.y,
    required this.emoji,
    required this.points,
    this.caught = false,
  });
}

这个类代表游戏中的一个掉落的水果。让我们详细分析每个属性:

x 和 y:水果在屏幕上的相对位置,范围从 0 到 1。x = 0.5 表示水平中心,y = 0 表示屏幕顶部,y = 1 表示屏幕底部。

emoji:水果的视觉表示,使用 Unicode 表情符号。这种方法简化了图形资源管理,特别适合跨平台开发。

points:玩家接住这个水果时获得的分数。不同类型的水果有不同的分数值,增加了游戏的策略性。

caught:一个布尔标志,表示水果是否已被接住。这防止了同一个水果被多次计分。

这种面向对象的设计使代码更加清晰和可维护。在 OpenHarmony 开发中,这种设计模式也是推荐的做法。

游戏状态管理

初始化与游戏循环

class _CatchFruitGameState extends State<CatchFruitGame> {
  int score = 0;
  int timeLeft = 30;
  bool gameActive = true;
  double basketX = 0.5;
  late Timer gameTimer;
  late Timer fruitTimer;
  List<FallingFruit> fruits = [];

  final List<Map<String, dynamic>> fruitTypes = [
    {'emoji': '🍎', 'points': 10},
    {'emoji': '🍌', 'points': 15},
    {'emoji': '🍊', 'points': 12},
    {'emoji': '🍓', 'points': 20},
    {'emoji': '🍉', 'points': 25},
  ];

这部分定义了游戏的核心状态变量。fruitTypes 列表定义了五种不同的水果,每种有不同的分数值。这种设计允许我们轻松添加新的水果类型,而无需修改游戏逻辑。

游戏启动

void startGame() {
  gameTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
    setState(() {
      timeLeft--;
      if (timeLeft <= 0) {
        endGame();
      }
    });
  });

  fruitTimer = Timer.periodic(const Duration(milliseconds: 600), (timer) {
    if (gameActive) {
      setState(() {
        final randomFruit = fruitTypes[Random().nextInt(fruitTypes.length)];
        fruits.add(
          FallingFruit(
            x: Random().nextDouble() * 0.8 + 0.1,
            y: 0,
            emoji: randomFruit['emoji'],
            points: randomFruit['points'],
          ),
        );
      });
    }
  });

  Timer.periodic(const Duration(milliseconds: 30), (timer) {
    if (gameActive) {
      setState(() {
        for (int i = fruits.length - 1; i >= 0; i--) {
          fruits[i].y += 0.02;

          // 检测是否被篮子接住
          if (fruits[i].y > 0.85 &&
              fruits[i].y < 0.95 &&
              (fruits[i].x - basketX).abs() < 0.15 &&
              !fruits[i].caught) {
            fruits[i].caught = true;
            score += fruits[i].points;
          }

          // 移除掉落出屏幕的水果
          if (fruits[i].y > 1.0) {
            fruits.removeAt(i);
          }
        }
      });
    }
  });
}

这个方法是游戏的心脏,它创建了三个独立的定时器,形成了完整的游戏循环:

gameTimer:每秒执行一次,负责倒计时。这是最低频率的定时器,用于更新显示的时间。

fruitTimer:每 600 毫秒执行一次,生成新的水果。通过随机选择 fruitTypes 中的一个,我们确保了水果类型的多样性。新水果的 x 坐标在 0.1 到 0.9 之间随机生成,确保水果不会出现在屏幕边缘。

物理更新定时器:这是最高频率的定时器(每 30 毫秒),负责更新水果的位置和检测碰撞。这个频率(约 33 FPS)在大多数设备上都能提供流畅的游戏体验,包括 OpenHarmony 设备。

碰撞检测逻辑

if (fruits[i].y > 0.85 &&
    fruits[i].y < 0.95 &&
    (fruits[i].x - basketX).abs() < 0.15 &&
    !fruits[i].caught) {
  fruits[i].caught = true;
  score += fruits[i].points;
}

这段代码实现了简单但有效的碰撞检测。让我们分解它:

  • fruits[i].y > 0.85 && fruits[i].y < 0.95:检查水果是否在篮子的高度范围内(篮子位于屏幕下方)
  • (fruits[i].x - basketX).abs() < 0.15:检查水果的水平位置是否在篮子的范围内。0.15 是篮子的有效接住范围
  • !fruits[i].caught:确保这个水果还没有被接住过

这种碰撞检测方法简单高效,特别适合移动设备和 OpenHarmony 设备,因为它不需要复杂的物理引擎。

手势识别与交互

滑动篮子

void updateBasketPosition(DragUpdateDetails details) {
  setState(() {
    basketX += details.delta.dx / MediaQuery.of(context).size.width;
    basketX = basketX.clamp(0.1, 0.9);
  });
}

这个方法处理玩家的滑动输入。DragUpdateDetails 包含了滑动的增量信息。通过将 delta.dx(水平移动的像素数)除以屏幕宽度,我们将像素坐标转换为相对坐标。

clamp(0.1, 0.9) 确保篮子不会滑出屏幕边界。这种边界检查在游戏开发中至关重要,防止了不合理的游戏状态。

手势检测器集成

GestureDetector(
  onHorizontalDragUpdate: updateBasketPosition,
  child: Stack(
    children: [
      // 游戏内容
    ],
  ),
)

GestureDetector 是 Flutter 中处理用户手势的标准方式。onHorizontalDragUpdate 回调在用户进行水平滑动时持续触发,提供了流畅的控制体验。这个方法在 Android、iOS 和 OpenHarmony 上都能一致地工作。

渲染与动画

水果渲染

...fruits.map((fruit) {
  return Positioned(
    left: fruit.x * screenSize.width,
    top: fruit.y * screenSize.height,
    child: Opacity(
      opacity: fruit.caught ? 0.5 : 1.0,
      child: Container(
        width: 40,
        height: 40,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.yellow.withOpacity(0.3),
        ),
        child: Center(
          child: Text(
            fruit.emoji,
            style: const TextStyle(fontSize: 28),
          ),
        ),
      ),
    ),
  );
}).toList(),

这段代码使用 map 函数将每个 FallingFruit 对象转换为 Positioned Widget。关键点包括:

  • 坐标转换:将相对坐标乘以屏幕尺寸,得到绝对像素位置
  • Opacity 效果:当水果被接住时,透明度降低到 0.5,给玩家视觉反馈
  • 圆形容器:使用 BoxShape.circle 创建圆形背景,增强视觉效果

篮子渲染

Positioned(
  bottom: 20,
  left: basketX * screenSize.width - 30,
  child: Container(
    width: 60,
    height: 40,
    decoration: BoxDecoration(
      color: Colors.brown,
      borderRadius: const BorderRadius.only(
        topLeft: Radius.circular(30),
        topRight: Radius.circular(30),
      ),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withOpacity(0.3),
          blurRadius: 8,
        ),
      ],
    ),
    child: const Center(
      child: Text(
        '🧺',
        style: TextStyle(fontSize: 32),
      ),
    ),
  ),
)

篮子使用 Positioned 放置在屏幕底部。left: basketX * screenSize.width - 30 确保篮子的中心对齐到 basketX 位置。圆角边框和阴影效果使篮子看起来更立体和真实。

OpenHarmony 兼容性考虑

跨平台适配

这个游戏的设计充分考虑了 OpenHarmony 的特性:

  1. 相对坐标系统:使用 0-1 范围的相对坐标,自动适应不同的屏幕尺寸和分辨率
  2. 手势识别:Flutter 的 GestureDetector 在 OpenHarmony 上有完整的支持
  3. 性能优化:避免了复杂的动画和物理计算,确保在各种硬件上都能流畅运行
  4. 简化的图形:使用 emoji 而不是复杂的图片资源,减少了资源占用

未来的 OpenHarmony 特定功能

虽然当前的实现完全跨平台,但在未来可以添加 OpenHarmony 特定的功能:

  • 振动反馈:使用 OpenHarmony 的振动 API 在接住水果时提供触觉反馈
  • 声音效果:集成 OpenHarmony 的音频系统
  • 传感器支持:使用设备的加速度计来控制篮子(倾斜设备移动篮子)

游戏设计分析

难度曲线

游戏的难度由几个参数控制:

  • 水果生成频率:600 毫秒的间隔提供了适度的挑战
  • 水果下落速度:每帧增加 0.02 的 y 坐标,给玩家足够的反应时间
  • 篮子宽度:0.15 的有效范围既不太宽也不太窄,需要一定的精准度

分数系统

不同的水果有不同的分数值,鼓励玩家优先接住高分水果。这增加了游戏的策略性和可玩性。

视觉反馈

  • 水果变淡:接住水果时透明度降低,给玩家明确的反馈
  • 实时得分显示:玩家可以立即看到他们的得分变化
  • 倒计时显示:时间的流逝给玩家紧迫感

性能优化

内存管理

if (fruits[i].y > 1.0) {
  fruits.removeAt(i);
}

及时移除掉落出屏幕的水果,防止列表无限增长,这是重要的内存优化。

定时器频率

选择合适的定时器频率是性能的关键:

  • 物理更新:30 毫秒(约 33 FPS)
  • 水果生成:600 毫秒
  • 时间更新:1000 毫秒

这种分层的定时器设计确保了游戏的流畅性,同时避免了不必要的计算。

扩展与改进方向

  1. 难度级别:添加简单、中等、困难三个难度,改变水果生成频率和下落速度
  2. 特殊水果:添加炸弹(减分)或星星(加倍分数)等特殊水果
  3. 连击系统:连续接住多个水果时获得额外分数
  4. 排行榜:使用数据库保存玩家的最高分
  5. 声音和振动:添加音效和触觉反馈
  6. 多人模式:两个玩家竞争或合作

总结

接水果游戏展示了如何在 Flutter 和 OpenHarmony 上开发一个完整的、可玩的游戏。关键的学习点包括:

  • 自定义数据类的设计和使用
  • 多定时器的协调管理
  • 简单但有效的碰撞检测算法
  • 手势识别和交互处理
  • 相对坐标系统的跨平台适配
  • 性能优化和内存管理
  • OpenHarmony 兼容性考虑

通过这个项目,你已经掌握了开发更复杂游戏所需的核心技能。在接下来的游戏中,我们将继续探索更高级的概念,如物理引擎、AI 对手和多人联网。


Logo

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

更多推荐