Flutter for OpenHarmony从零到一:构建《冰火人》双人合作闯关游戏

Flutter for OpenHarmony从零到一:构建《冰火人》双人合作闯关游戏

在移动游戏开发领域,将经典玩法与现代框架相结合,是激发创造力的绝佳方式。本文将深入剖析一段使用 Flutter

框架实现的《冰火人》风格双人合作闯关游戏的完整源码。我们将逐层拆解其架构设计、核心逻辑与交互细节,揭示如何用 Dart

语言构建一个功能完备、体验流畅的 2D 平台游戏。

完整效果展示


一、整体架构:清晰的页面导航与状态管理

游戏采用了经典的 三层页面结构,确保了逻辑的清晰分离:

  1. MainMenu (主菜单): 游戏的入口,提供"开始游戏"和"游戏说明"选项。
  2. LevelSelectScreen (关卡选择): 以网格形式展示所有可玩关卡。
  3. GameScreen (游戏主界面) : 核心的游戏逻辑和渲染都在此 StatefulWidget 中完成。

这种结构使得代码易于维护和扩展。例如,要添加新关卡,只需在 LevelSelectScreenGridView 中增加一项,并在 GameScreen_loadLevel 方法中实现新的关卡逻辑即可。

关键代码:页面导航

dart 复制代码
// 从主菜单跳转到关卡选择
Navigator.push(context, MaterialPageRoute(builder: (context) => const LevelSelectScreen()));

// 从关卡选择进入具体游戏
Navigator.push(context, MaterialPageRoute(builder: (context) => GameScreen(level: level)));

Flutter 的 Navigator 提供了简洁的路由管理,实现了页面间的无缝切换。

二、数据模型:定义游戏世界的基石

游戏世界由一系列自定义的 Dart 类 (Platform, LavaPool, WaterPool, Gem) 构成。这些简单的数据模型是整个游戏逻辑的基石。

dart 复制代码
class Platform {
  final int x, y, width, height; // 平台的位置和尺寸
  const Platform({required this.x, ...});
}

class Gem {
  final double x, y; // 宝石位置(使用double以支持更精细的定位)
  final Color color; // 宝石颜色,用于区分归属(红/蓝/绿)
  const Gem({...});
}

设计亮点

  • 类型安全:每个游戏元素都有明确的属性,避免了使用 Map 或 List 存储数据带来的类型混乱和潜在错误。
  • 可读性强platform.xgameData[0] 更直观,极大地提升了代码的可读性和可维护性。

三、核心引擎:游戏循环与物理模拟

GameScreenState 类是整个游戏的"心脏",它通过一个 定时器驱动的游戏循环 来更新世界状态。

3.1 游戏循环 (_startGame)

dart 复制代码
void _startGame() {
  _gameTimer = Timer.periodic(const Duration(milliseconds: 16), (timer) {
    if (!_isPaused && !_gameOver && !_gameWin) {
      _updateGame(); // 核心更新逻辑
    }
  });
}
  • 帧率控制Duration(milliseconds: 16) 对应约 60 FPS,保证了流畅的动画效果。
  • 状态检查:循环只在游戏进行中(非暂停、非结束)时执行,节省了系统资源。

3.2 物理与移动 (_updateGame)

这是最核心的逻辑,每帧都会执行:

  1. 处理输入 :根据键盘或虚拟按键的状态(如 _leftPressed),设置玩家角色的水平速度 (_fireboyVelocityX)。
  2. 应用重力 :持续给垂直速度 (_velocityY) 增加一个值(+= 0.5),模拟重力下落效果。
  3. 更新位置:将速度向量加到当前位置上。
  4. 碰撞检测:这是平台游戏的灵魂,代码中实现了三种关键碰撞:
  • 平台碰撞 (_checkPlatformCollision): 判断角色是否站在平台上(用于跳跃)或撞到平台侧面/底部。
    • 危险区域碰撞 (_checkDangerZones): 检测火焰人是否掉入水池或冰人是否掉入岩浆,触发游戏结束。
    • 宝石收集 (_collectGems): 检测角色是否触碰到宝石,并根据颜色(红/蓝/绿)增加对应玩家的分数。
    • 门碰撞 (_checkDoorCollision): 检测两名角色是否同时到达各自的终点门,触发过关。

3.3 碰撞检测详解

以平台碰撞为例,代码不仅检测了重叠 (overlaps),还通过判断 速度方向碰撞位置 来区分是从上方落下(需要站在上面)还是从下方/侧面撞上(需要被阻挡)。

dart 复制代码
// 从上方落下
if (_fireboyVelocityY > 0 && position.dy + _playerSize.height <= platformRect.top + 10) {
  // 将角色位置精确调整到平台顶部
  _fireboyPosition = Offset(position.dx, platformRect.top - _playerSize.height);
  _fireboyOnGround = true; // 允许再次跳跃
  _fireboyVelocityY = 0; // 垂直速度归零
}

这种精细化的处理是实现流畅、符合直觉的平台跳跃体验的关键。

四、用户交互:跨平台的输入适配

为了让游戏在桌面和移动端都能玩,代码巧妙地实现了 双重输入方案

4.1 键盘输入 (桌面端)

通过 RawKeyboardListener(虽然代码中未显式写出,但通常配合 FocusNode

使用)或全局键盘事件监听,可以捕获 WASDIJKL 等按键,并更新对应的布尔状态(如 _leftPressed = true)。这些状态会在 _updateGame 中被读取,从而控制角色移动。

4.2 虚拟按键 (移动端)

对于触摸屏设备,游戏提供了精美的 虚拟摇杆

dart 复制代码
GestureDetector(
  onTapDown: (_) => setState(() => _watergirlJumpPressed = true),
  onTapUp: (_) => setState(() => _watergirlJumpPressed = false),
  child: Container(...), // 绘制一个圆形按钮
)
  • onTapDown / onTapUp: 这两个回调完美模拟了物理按键的"按下"和"松开"状态。
  • 视觉反馈: 按钮使用了半透明的蓝色/橙色,并带有白色边框和方向图标,清晰地指示了其功能和所属角色。

此外,游戏还贴心地加入了 显示/隐藏控制 的功能,玩家点击屏幕任意空白处即可切换虚拟按键的可见性,避免遮挡视线。

五、UI 与视觉呈现:沉浸式的游戏体验

Flutter 强大的 StackPositioned 组件是构建 2D 游戏 UI 的利器。

5.1 游戏世界渲染

整个游戏区域是一个 Stack,所有游戏元素(平台、岩浆、水池、宝石、门、玩家角色)都作为 Positioned 子组件被精确地放置在指定坐标上。

dart 复制代码
Stack(
  children: [
    ..._platforms.map((p) => Positioned(left: p.x, top: p.y, child: ...)),
    ..._gems.map((g) => Positioned(left: g.x, top: g.y, child: ...)),
    Positioned(left: _fireboyPosition.dx, top: _fireboyPosition.dy, child: ...),
  ],
)

这种"绝对定位"的方式非常适合 2D 游戏的坐标系。

5.2 角色与环境设计

  • 角色 :火焰人和冰人分别用 LinearGradient 渲染出橙红色和蓝白色的渐变效果,并辅以 BoxShadow 制造发光感,再配上 local_fire_departmentac_unit 图标,形象生动。
  • 环境 :岩浆 (LavaPool) 和水池 (WaterPool) 同样使用渐变色 (red->orange->yellowlightBlue->blue->darkBlue),营造出逼真的视觉效果。
  • 宝石:不同颜色的圆形宝石带有同色光晕,非常醒目。

5.3 状态覆盖层

游戏通过条件渲染 (if (_gameOver) ...) 在 Stack 顶部动态添加覆盖层,用于显示 暂停、游戏结束、胜利 等状态。这种方式简单高效,且能完全屏蔽底层的游戏交互。

六、关卡设计:数据驱动的可扩展性

游戏的关卡逻辑完全由 _loadLevel1/2/3 等方法中的 数据初始化 决定。

dart 复制代码
void _loadLevel1() {
  _fireboyPosition = const Offset(50, 400);
  _platforms = [const Platform(x: 0, y: 480, ...), ...];
  _lavaPools = [const LavaPool(x: 150, y: 460, ...)];
  ...
}

这种 数据驱动 的设计模式意味着:

  • 易于修改:设计师可以直接调整数字来改变关卡布局。
  • 易于扩展:未来可以轻松地将关卡数据从 JSON 文件或网络加载,而无需改动核心游戏逻辑。

总结

这段《冰火人》游戏代码是一个教科书式的 Flutter 2D 游戏开发范例。它展示了如何利用 Flutter 的声明式 UI、强大的状态管理和丰富的 Widget 生态,来构建一个包含 复杂物理模拟、精细碰撞检测、跨平台输入适配和精美视觉效果 的完整游戏。

🌐 加入社区

欢迎加入 开源鸿蒙跨平台开发者社区 ,获取最新资源与技术支持:

👉 开源鸿蒙跨平台开发者社区
完整代码展示

bash 复制代码
import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(const FireboyWatergirlGame());
}

class FireboyWatergirlGame extends StatelessWidget {
  const FireboyWatergirlGame({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '冰火人闯关',
      theme: ThemeData(
        primarySwatch: Colors.orange,
        useMaterial3: true,
        brightness: Brightness.dark,
      ),
      home: const MainMenu(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MainMenu extends StatelessWidget {
  const MainMenu({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Colors.red.withOpacity(0.6), Colors.blue.withOpacity(0.6)],
          ),
        ),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(
                Icons.extension,
                size: 120,
                color: Colors.white,
              ),
              const SizedBox(height: 20),
              const Text(
                '冰火人闯关',
                style: TextStyle(
                  fontSize: 48,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                  shadows: [
                    Shadow(
                      color: Colors.black,
                      blurRadius: 10,
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 60),
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 20),
                  textStyle: const TextStyle(fontSize: 24),
                  backgroundColor: Colors.green,
                  foregroundColor: Colors.white,
                ),
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const LevelSelectScreen(),
                    ),
                  );
                },
                child: const Text('开始游戏'),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 20),
                  textStyle: const TextStyle(fontSize: 24),
                  backgroundColor: Colors.blue,
                  foregroundColor: Colors.white,
                ),
                onPressed: () {
                  showDialog(
                    context: context,
                    builder: (context) => AlertDialog(
                      title: const Text('游戏说明'),
                      content: SingleChildScrollView(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            const Text('🔥 火焰人操作:', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.orange)),
                            const Text('• WASD 或方向键移动'),
                            const Text('• A/D: 左右移动'),
                            const Text('• W: 跳跃'),
                            const SizedBox(height: 16),
                            const Text('💧 冰人操作:', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue)),
                            const Text('• J/L: 左右移动'),
                            const Text('• I: 跳跃'),
                            const SizedBox(height: 16),
                            const Text('🎯 游戏目标:', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.yellow)),
                            const Text('• 火焰人到达红色门'),
                            const Text('• 冰人到达蓝色门'),
                            const Text('• 两人必须同时到达才能过关'),
                            const SizedBox(height: 16),
                            const Text('⚠️ 注意:', style: TextStyle(fontWeight: FontWeight.bold)),
                            const Text('• 火焰人碰到水会死亡'),
                            const Text('• 冰人碰到岩浆会死亡'),
                            const Text('• 任意一人死亡则游戏结束'),
                          ],
                        ),
                      ),
                      actions: [
                        TextButton(
                          onPressed: () => Navigator.pop(context),
                          child: const Text('知道了'),
                        ),
                      ],
                    ),
                  );
                },
                child: const Text('游戏说明'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class LevelSelectScreen extends StatelessWidget {
  const LevelSelectScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('选择关卡'),
        centerTitle: true,
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1,
          crossAxisSpacing: 16,
          mainAxisSpacing: 16,
        ),
        itemCount: 9,
        itemBuilder: (context, index) {
          int level = index + 1;
          return GestureDetector(
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => GameScreen(level: level),
                ),
              );
            },
            child: Container(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [Colors.red, Colors.blue],
                ),
                borderRadius: BorderRadius.circular(16),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.3),
                    blurRadius: 8,
                    offset: const Offset(0, 4),
                  ),
                ],
              ),
              child: Center(
                child: Text(
                  '$level',
                  style: const TextStyle(
                    fontSize: 48,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

class GameScreen extends StatefulWidget {
  final int level;

  const GameScreen({super.key, required this.level});

  @override
  State<GameScreen> createState() => _GameState();
}

class _GameState extends State<GameScreen> {
  late Timer _gameTimer;
  bool _isPaused = false;
  bool _gameOver = false;
  bool _gameWin = false;
  String _deathReason = '';

  // 游戏状态
  Offset _fireboyPosition = const Offset(50, 300);
  Offset _watergirlPosition = const Offset(250, 300);
  Size _playerSize = const Size(30, 40);
  double _fireboyVelocityX = 0;
  double _fireboyVelocityY = 0;
  double _watergirlVelocityX = 0;
  double _watergirlVelocityY = 0;
  bool _fireboyOnGround = false;
  bool _watergirlOnGround = false;

  // 关卡元素
  List<Platform> _platforms = [];
  List<LavaPool> _lavaPools = [];
  List<WaterPool> _waterPools = [];
  List<Gem> _gems = [];

  // 门的位置
  late Offset _redDoorPosition;
  late Offset _blueDoorPosition;
  bool _fireboyAtDoor = false;
  bool _watergirlAtDoor = false;

  // 收集统计
  int _fireboyGems = 0;
  int _watergirlGems = 0;
  int _totalFireboyGems = 0;
  int _totalWatergirlGems = 0;

  // 按键状态
  bool _leftPressed = false;
  bool _rightPressed = false;
  bool _fireboyJumpPressed = false;
  bool _watergirlLeftPressed = false;
  bool _watergirlRightPressed = false;
  bool _watergirlJumpPressed = false;

  // 移动端虚拟按键
  bool _showControls = true;

  @override
  void initState() {
    super.initState();
    _loadLevel();
    _startGame();
  }

  void _loadLevel() {
    // 根据关卡号加载不同的关卡设计
    switch (widget.level) {
      case 1:
        _loadLevel1();
        break;
      case 2:
        _loadLevel2();
        break;
      case 3:
        _loadLevel3();
        break;
      default:
        _loadLevel1();
    }
  }

  void _loadLevel1() {
    _fireboyPosition = const Offset(50, 400);
    _watergirlPosition = const Offset(100, 400);

    // 平台
    _platforms = [
      const Platform(x: 0, y: 480, width: 400, height: 20), // 地面
      const Platform(x: 0, y: 350, width: 150, height: 20),
      const Platform(x: 200, y: 350, width: 200, height: 20),
      const Platform(x: 0, y: 220, width: 120, height: 20),
      const Platform(x: 180, y: 220, width: 220, height: 20),
      const Platform(x: 300, y: 100, width: 100, height: 20),
    ];

    // 岩浆和水池
    _lavaPools = [
      const LavaPool(x: 150, y: 460, width: 50, height: 20),
    ];

    _waterPools = [
      const WaterPool(x: 250, y: 460, width: 50, height: 20),
    ];

    // 宝石
    _gems = [
      const Gem(x: 75, y: 300, color: Colors.red),
      const Gem(x: 300, y: 300, color: Colors.blue),
      const Gem(x: 60, y: 170, color: Colors.red),
      const Gem(x: 300, y: 170, color: Colors.blue),
    ];

    _totalFireboyGems = 2;
    _totalWatergirlGems = 2;

    // 门
    _redDoorPosition = const Offset(320, 50);
    _blueDoorPosition = const Offset(250, 50);
  }

  void _loadLevel2() {
    _fireboyPosition = const Offset(30, 400);
    _watergirlPosition = const Offset(80, 400);

    _platforms = [
      const Platform(x: 0, y: 480, width: 400, height: 20),
      const Platform(x: 0, y: 380, width: 100, height: 20),
      const Platform(x: 150, y: 380, width: 100, height: 20),
      const Platform(x: 300, y: 380, width: 100, height: 20),
      const Platform(x: 50, y: 280, width: 100, height: 20),
      const Platform(x: 250, y: 280, width: 100, height: 20),
      const Platform(x: 150, y: 180, width: 100, height: 20),
      const Platform(x: 50, y: 100, width: 80, height: 20),
      const Platform(x: 270, y: 100, width: 80, height: 20),
    ];

    _lavaPools = [
      const LavaPool(x: 100, y: 460, width: 50, height: 20),
      const LavaPool(x: 250, y: 460, width: 50, height: 20),
    ];

    _waterPools = [
      const WaterPool(x: 200, y: 340, width: 50, height: 20),
    ];

    _gems = [
      const Gem(x: 50, y: 340, color: Colors.red),
      const Gem(x: 350, y: 340, color: Colors.blue),
      const Gem(x: 100, y: 240, color: Colors.red),
      const Gem(x: 300, y: 240, color: Colors.blue),
      const Gem(x: 200, y: 140, color: Colors.green),
    ];

    _totalFireboyGems = 3;
    _totalWatergirlGems = 2;

    _redDoorPosition = const Offset(60, 50);
    _blueDoorPosition = const Offset(290, 50);
  }

  void _loadLevel3() {
    _fireboyPosition = const Offset(30, 400);
    _watergirlPosition = const Offset(70, 400);

    _platforms = [
      const Platform(x: 0, y: 480, width: 400, height: 20),
      const Platform(x: 0, y: 380, width: 80, height: 20),
      const Platform(x: 120, y: 350, width: 80, height: 20),
      const Platform(x: 240, y: 320, width: 80, height: 20),
      const Platform(x: 320, y: 380, width: 80, height: 20),
      const Platform(x: 50, y: 260, width: 80, height: 20),
      const Platform(x: 170, y: 230, width: 80, height: 20),
      const Platform(x: 290, y: 260, width: 80, height: 20),
      const Platform(x: 120, y: 160, width: 80, height: 20),
      const Platform(x: 200, y: 100, width: 100, height: 20),
    ];

    _lavaPools = [
      const LavaPool(x: 80, y: 460, width: 60, height: 20),
      const LavaPool(x: 200, y: 460, width: 60, height: 20),
      const LavaPool(x: 300, y: 340, width: 50, height: 20),
    ];

    _waterPools = [
      const WaterPool(x: 280, y: 460, width: 70, height: 20),
      const WaterPool(x: 100, y: 220, width: 50, height: 20),
    ];

    _gems = [
      const Gem(x: 40, y: 340, color: Colors.red),
      const Gem(x: 160, y: 310, color: Colors.red),
      const Gem(x: 280, y: 280, color: Colors.blue),
      const Gem(x: 90, y: 220, color: Colors.red),
      const Gem(x: 210, y: 190, color: Colors.blue),
      const Gem(x: 330, y: 220, color: Colors.blue),
      const Gem(x: 160, y: 120, color: Colors.green),
      const Gem(x: 250, y: 60, color: Colors.green),
    ];

    _totalFireboyGems = 4;
    _totalWatergirlGems = 3;

    _redDoorPosition = const Offset(210, 50);
    _blueDoorPosition = const Offset(260, 50);
  }

  void _startGame() {
    _gameTimer = Timer.periodic(const Duration(milliseconds: 16), (timer) {
      if (!_isPaused && !_gameOver && !_gameWin) {
        _updateGame();
      }
    });
  }

  void _updateGame() {
    // 火焰人移动
    if (_leftPressed) {
      _fireboyVelocityX = -4;
    } else if (_rightPressed) {
      _fireboyVelocityX = 4;
    } else {
      _fireboyVelocityX = 0;
    }

    if (_fireboyJumpPressed && _fireboyOnGround) {
      _fireboyVelocityY = -12;
      _fireboyOnGround = false;
    }

    // 冰人移动
    if (_watergirlLeftPressed) {
      _watergirlVelocityX = -4;
    } else if (_watergirlRightPressed) {
      _watergirlVelocityX = 4;
    } else {
      _watergirlVelocityX = 0;
    }

    if (_watergirlJumpPressed && _watergirlOnGround) {
      _watergirlVelocityY = -12;
      _watergirlOnGround = false;
    }

    // 应用重力
    _fireboyVelocityY += 0.5;
    _watergirlVelocityY += 0.5;

    // 更新位置
    _fireboyPosition += Offset(_fireboyVelocityX, _fireboyVelocityY);
    _watergirlPosition += Offset(_watergirlVelocityX, _watergirlVelocityY);

    // 边界检测
    _fireboyPosition = Offset(
      _fireboyPosition.dx.clamp(0, 370),
      _fireboyPosition.dy.clamp(0, 440),
    );
    _watergirlPosition = Offset(
      _watergirlPosition.dx.clamp(0, 370),
      _watergirlPosition.dy.clamp(0, 440),
    );

    // 平台碰撞检测
    _fireboyOnGround = false;
    _watergirlOnGround = false;

    for (var platform in _platforms) {
      _checkPlatformCollision(_fireboyPosition, platform, true);
      _checkPlatformCollision(_watergirlPosition, platform, false);
    }

    // 危险区域检测
    _checkDangerZones();

    // 宝石收集
    _collectGems();

    // 检查是否到达门口
    _checkDoorCollision();

    setState(() {});
  }

  void _checkPlatformCollision(Offset position, Platform platform, bool isFireboy) {
    Rect playerRect = Rect.fromLTWH(
      position.dx,
      position.dy,
      _playerSize.width,
      _playerSize.height,
    );

    Rect platformRect = Rect.fromLTWH(
      platform.x.toDouble(),
      platform.y.toDouble(),
      platform.width.toDouble(),
      platform.height.toDouble(),
    );

    if (playerRect.overlaps(platformRect)) {
      // 从上方落下
      if ((isFireboy ? _fireboyVelocityY : _watergirlVelocityY) > 0 &&
          position.dy + _playerSize.height <= platformRect.top + 10) {
        position = Offset(
          position.dx,
          platformRect.top - _playerSize.height,
        );
        if (isFireboy) {
          _fireboyPosition = position;
          _fireboyOnGround = true;
          _fireboyVelocityY = 0;
        } else {
          _watergirlPosition = position;
          _watergirlOnGround = true;
          _watergirlVelocityY = 0;
        }
      }
      // 从下方碰撞
      else if ((isFireboy ? _fireboyVelocityY : _watergirlVelocityY) < 0 &&
          position.dy >= platformRect.bottom - 10) {
        position = Offset(position.dx, platformRect.bottom);
        if (isFireboy) {
          _fireboyPosition = position;
          _fireboyVelocityY = 0;
        } else {
          _watergirlPosition = position;
          _watergirlVelocityY = 0;
        }
      }
    }
  }

  void _checkDangerZones() {
    Rect fireboyRect = Rect.fromLTWH(
      _fireboyPosition.dx,
      _fireboyPosition.dy,
      _playerSize.width,
      _playerSize.height,
    );

    Rect watergirlRect = Rect.fromLTWH(
      _watergirlPosition.dx,
      _watergirlPosition.dy,
      _playerSize.width,
      _playerSize.height,
    );

    // 检查岩浆 - 火焰人安全,冰人危险
    for (var lava in _lavaPools) {
      Rect lavaRect = Rect.fromLTWH(
        lava.x.toDouble(),
        lava.y.toDouble(),
        lava.width.toDouble(),
        lava.height.toDouble(),
      );

      if (watergirlRect.overlaps(lavaRect)) {
        _gameOver = true;
        _deathReason = '冰人掉进了岩浆!';
        return;
      }
    }

    // 检查水池 - 冰人安全,火焰人危险
    for (var water in _waterPools) {
      Rect waterRect = Rect.fromLTWH(
        water.x.toDouble(),
        water.y.toDouble(),
        water.width.toDouble(),
        water.height.toDouble(),
      );

      if (fireboyRect.overlaps(waterRect)) {
        _gameOver = true;
        _deathReason = '火焰人掉进了水池!';
        return;
      }
    }
  }

  void _collectGems() {
    Rect fireboyRect = Rect.fromLTWH(
      _fireboyPosition.dx + 5,
      _fireboyPosition.dy + 5,
      _playerSize.width - 10,
      _playerSize.height - 10,
    );

    Rect watergirlRect = Rect.fromLTWH(
      _watergirlPosition.dx + 5,
      _watergirlPosition.dy + 5,
      _playerSize.width - 10,
      _playerSize.height - 10,
    );

    for (int i = _gems.length - 1; i >= 0; i--) {
      Gem gem = _gems[i];
      Rect gemRect = Rect.fromLTWH(gem.x.toDouble(), gem.y.toDouble(), 20, 20);

      if (fireboyRect.overlaps(gemRect) && gem.color == Colors.red) {
        _fireboyGems++;
        _gems.removeAt(i);
      } else if (watergirlRect.overlaps(gemRect) && gem.color == Colors.blue) {
        _watergirlGems++;
        _gems.removeAt(i);
      } else if ((fireboyRect.overlaps(gemRect) || watergirlRect.overlaps(gemRect)) && gem.color == Colors.green) {
        _fireboyGems++;
        _watergirlGems++;
        _gems.removeAt(i);
      }
    }
  }

  void _checkDoorCollision() {
    Rect fireboyRect = Rect.fromLTWH(
      _fireboyPosition.dx,
      _fireboyPosition.dy,
      _playerSize.width,
      _playerSize.height,
    );

    Rect watergirlRect = Rect.fromLTWH(
      _watergirlPosition.dx,
      _watergirlPosition.dy,
      _playerSize.width,
      _playerSize.height,
    );

    Rect redDoorRect = Rect.fromLTWH(_redDoorPosition.dx, _redDoorPosition.dy, 40, 60);
    Rect blueDoorRect = Rect.fromLTWH(_blueDoorPosition.dx, _blueDoorPosition.dy, 40, 60);

    _fireboyAtDoor = fireboyRect.overlaps(redDoorRect);
    _watergirlAtDoor = watergirlRect.overlaps(blueDoorRect);

    if (_fireboyAtDoor && _watergirlAtDoor) {
      _gameWin = true;
      _gameTimer.cancel();
    }
  }

  @override
  void dispose() {
    _gameTimer.cancel();
    super.dispose();
  }

  void _restartLevel() {
    setState(() {
      _gameOver = false;
      _gameWin = false;
      _deathReason = '';
      _fireboyGems = 0;
      _watergirlGems = 0;
      _gems.clear();
      _loadLevel();
    });
  }

  void _nextLevel() {
    if (widget.level < 9) {
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(
          builder: (context) => GameScreen(level: widget.level + 1),
        ),
      );
    } else {
      Navigator.pop(context);
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('恭喜通关!'),
          content: const Text('你已完成所有关卡!'),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.pop(context);
                Navigator.pop(context);
              },
              child: const Text('返回'),
            ),
          ],
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[900],
      body: Column(
        children: [
          // 顶部状态栏
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            color: Colors.black87,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Row(
                  children: [
                    IconButton(
                      icon: const Icon(Icons.arrow_back),
                      onPressed: () {
                        _gameTimer.cancel();
                        Navigator.pop(context);
                      },
                    ),
                    Text(
                      '关卡 ${widget.level}',
                      style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                  ],
                ),
                Row(
                  children: [
                    const Icon(Icons.diamond, color: Colors.red, size: 20),
                    Text(' $_fireboyGems/$_totalFireboyGems'),
                    const SizedBox(width: 16),
                    const Icon(Icons.diamond, color: Colors.blue, size: 20),
                    Text(' $_watergirlGems/$_totalWatergirlGems'),
                  ],
                ),
                IconButton(
                  icon: Icon(_isPaused ? Icons.play_arrow : Icons.pause),
                  onPressed: () {
                    setState(() {
                      _isPaused = !_isPaused;
                    });
                  },
                ),
              ],
            ),
          ),
          // 游戏区域
          Expanded(
            child: GestureDetector(
              onTap: () {
                setState(() {
                  _showControls = !_showControls;
                });
              },
              child: Container(
                color: Colors.grey[800],
                child: Stack(
                  children: [
                    // 平台
                    ..._platforms.map((platform) => Positioned(
                          left: platform.x.toDouble(),
                          top: platform.y.toDouble(),
                          child: Container(
                            width: platform.width.toDouble(),
                            height: platform.height.toDouble(),
                            decoration: BoxDecoration(
                              color: Colors.brown[700],
                              borderRadius: BorderRadius.circular(4),
                              border: Border.all(color: Colors.brown[900]!, width: 2),
                            ),
                          ),
                        )),
                    // 岩浆
                    ..._lavaPools.map((lava) => Positioned(
                          left: lava.x.toDouble(),
                          top: lava.y.toDouble(),
                          child: Container(
                            width: lava.width.toDouble(),
                            height: lava.height.toDouble(),
                            decoration: BoxDecoration(
                              gradient: LinearGradient(
                                colors: [Colors.red, Colors.orange, Colors.yellow],
                              ),
                              borderRadius: BorderRadius.circular(8),
                            ),
                          ),
                        )),
                    // 水池
                    ..._waterPools.map((water) => Positioned(
                          left: water.x.toDouble(),
                          top: water.y.toDouble(),
                          child: Container(
                            width: water.width.toDouble(),
                            height: water.height.toDouble(),
                            decoration: BoxDecoration(
                              gradient: LinearGradient(
                                colors: [Colors.blue.shade300, Colors.blue, Colors.blue.shade900],
                              ),
                              borderRadius: BorderRadius.circular(8),
                            ),
                          ),
                        )),
                    // 宝石
                    ..._gems.map((gem) => Positioned(
                          left: gem.x.toDouble(),
                          top: gem.y.toDouble(),
                          child: Container(
                            width: 20,
                            height: 20,
                            decoration: BoxDecoration(
                              color: gem.color,
                              shape: BoxShape.circle,
                              boxShadow: [
                                BoxShadow(
                                  color: gem.color.withOpacity(0.6),
                                  blurRadius: 10,
                                  spreadRadius: 2,
                                ),
                              ],
                            ),
                          ),
                        )),
                    // 门
                    Positioned(
                      left: _redDoorPosition.dx,
                      top: _redDoorPosition.dy,
                      child: Container(
                        width: 40,
                        height: 60,
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            colors: [Colors.red.shade400, Colors.red.shade900],
                          ),
                          borderRadius: BorderRadius.circular(4),
                          border: Border.all(color: Colors.yellow, width: 2),
                        ),
                        child: const Icon(Icons.exit_to_app, color: Colors.yellow),
                      ),
                    ),
                    Positioned(
                      left: _blueDoorPosition.dx,
                      top: _blueDoorPosition.dy,
                      child: Container(
                        width: 40,
                        height: 60,
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            colors: [Colors.blue.shade400, Colors.blue.shade900],
                          ),
                          borderRadius: BorderRadius.circular(4),
                          border: Border.all(color: Colors.yellow, width: 2),
                        ),
                        child: const Icon(Icons.exit_to_app, color: Colors.yellow),
                      ),
                    ),
                    // 火焰人
                    Positioned(
                      left: _fireboyPosition.dx,
                      top: _fireboyPosition.dy,
                      child: Container(
                        width: _playerSize.width,
                        height: _playerSize.height,
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            colors: [Colors.orange, Colors.red],
                          ),
                          borderRadius: BorderRadius.circular(8),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.orange.withOpacity(0.6),
                              blurRadius: 10,
                              spreadRadius: 2,
                            ),
                          ],
                        ),
                        child: const Icon(Icons.local_fire_department, color: Colors.white),
                      ),
                    ),
                    // 冰人
                    Positioned(
                      left: _watergirlPosition.dx,
                      top: _watergirlPosition.dy,
                      child: Container(
                        width: _playerSize.width,
                        height: _playerSize.height,
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            colors: [Colors.lightBlue, Colors.blue],
                          ),
                          borderRadius: BorderRadius.circular(8),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.blue.withOpacity(0.6),
                              blurRadius: 10,
                              spreadRadius: 2,
                            ),
                          ],
                        ),
                        child: const Icon(Icons.ac_unit, color: Colors.white),
                      ),
                    ),
                    // 暂停覆盖层
                    if (_isPaused)
                      Container(
                        color: Colors.black.withOpacity(0.7),
                        child: const Center(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              Icon(Icons.pause, size: 80, color: Colors.white),
                              SizedBox(height: 16),
                              Text('暂停', style: TextStyle(fontSize: 32, color: Colors.white)),
                            ],
                          ),
                        ),
                      ),
                    // 游戏结束覆盖层
                    if (_gameOver)
                      Container(
                        color: Colors.black.withOpacity(0.8),
                        child: Center(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              const Icon(Icons.close, size: 80, color: Colors.red),
                              const SizedBox(height: 16),
                              const Text('游戏结束', style: TextStyle(fontSize: 32, color: Colors.white)),
                              const SizedBox(height: 16),
                              Text(_deathReason, style: const TextStyle(fontSize: 18, color: Colors.white)),
                              const SizedBox(height: 32),
                              ElevatedButton(
                                onPressed: _restartLevel,
                                child: const Text('重新开始'),
                              ),
                            ],
                          ),
                        ),
                      ),
                    // 胜利覆盖层
                    if (_gameWin)
                      Container(
                        color: Colors.black.withOpacity(0.8),
                        child: Center(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              const Icon(Icons.emoji_events, size: 80, color: Colors.yellow),
                              const SizedBox(height: 16),
                              const Text('过关!', style: TextStyle(fontSize: 32, color: Colors.white)),
                              const SizedBox(height: 16),
                              Text(
                                '收集宝石: 火焰人 $_fireboyGems/$_totalFireboyGems, 冰人 $_watergirlGems/$_totalWatergirlGems',
                                style: const TextStyle(fontSize: 16, color: Colors.white),
                              ),
                              const SizedBox(height: 32),
                              Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  ElevatedButton(
                                    onPressed: _restartLevel,
                                    child: const Text('重新开始'),
                                  ),
                                  const SizedBox(width: 16),
                                  ElevatedButton(
                                    onPressed: _nextLevel,
                                    child: const Text('下一关'),
                                  ),
                                ],
                              ),
                            ],
                          ),
                        ),
                      ),
                    // 虚拟控制
                    if (_showControls && !_gameOver && !_gameWin && !_isPaused)
                      Positioned(
                        bottom: 20,
                        left: 20,
                        right: 20,
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            // 冰人控制
                            Column(
                              children: [
                                GestureDetector(
                                  onTapDown: (_) => setState(() => _watergirlJumpPressed = true),
                                  onTapUp: (_) => setState(() => _watergirlJumpPressed = false),
                                  onTapCancel: () => setState(() => _watergirlJumpPressed = false),
                                  child: Container(
                                    width: 60,
                                    height: 60,
                                    decoration: BoxDecoration(
                                      color: Colors.blue.withOpacity(0.7),
                                      shape: BoxShape.circle,
                                      border: Border.all(color: Colors.white, width: 2),
                                    ),
                                    child: const Icon(Icons.arrow_upward, color: Colors.white),
                                  ),
                                ),
                                const SizedBox(height: 10),
                                Row(
                                  children: [
                                    GestureDetector(
                                      onTapDown: (_) => setState(() => _watergirlLeftPressed = true),
                                      onTapUp: (_) => setState(() => _watergirlLeftPressed = false),
                                      onTapCancel: () => setState(() => _watergirlLeftPressed = false),
                                      child: Container(
                                        width: 60,
                                        height: 60,
                                        decoration: BoxDecoration(
                                          color: Colors.blue.withOpacity(0.7),
                                          shape: BoxShape.circle,
                                          border: Border.all(color: Colors.white, width: 2),
                                        ),
                                        child: const Icon(Icons.arrow_back, color: Colors.white),
                                      ),
                                    ),
                                    const SizedBox(width: 10),
                                    GestureDetector(
                                      onTapDown: (_) => setState(() => _watergirlRightPressed = true),
                                      onTapUp: (_) => setState(() => _watergirlRightPressed = false),
                                      onTapCancel: () => setState(() => _watergirlRightPressed = false),
                                      child: Container(
                                        width: 60,
                                        height: 60,
                                        decoration: BoxDecoration(
                                          color: Colors.blue.withOpacity(0.7),
                                          shape: BoxShape.circle,
                                          border: Border.all(color: Colors.white, width: 2),
                                        ),
                                        child: const Icon(Icons.arrow_forward, color: Colors.white),
                                      ),
                                    ),
                                  ],
                                ),
                                const SizedBox(height: 8),
                                const Text('冰人', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold)),
                              ],
                            ),
                            // 火焰人控制
                            Column(
                              children: [
                                GestureDetector(
                                  onTapDown: (_) => setState(() => _fireboyJumpPressed = true),
                                  onTapUp: (_) => setState(() => _fireboyJumpPressed = false),
                                  onTapCancel: () => setState(() => _fireboyJumpPressed = false),
                                  child: Container(
                                    width: 60,
                                    height: 60,
                                    decoration: BoxDecoration(
                                      color: Colors.orange.withOpacity(0.7),
                                      shape: BoxShape.circle,
                                      border: Border.all(color: Colors.white, width: 2),
                                    ),
                                    child: const Icon(Icons.arrow_upward, color: Colors.white),
                                  ),
                                ),
                                const SizedBox(height: 10),
                                Row(
                                  children: [
                                    GestureDetector(
                                      onTapDown: (_) => setState(() => _leftPressed = true),
                                      onTapUp: (_) => setState(() => _leftPressed = false),
                                      onTapCancel: () => setState(() => _leftPressed = false),
                                      child: Container(
                                        width: 60,
                                        height: 60,
                                        decoration: BoxDecoration(
                                          color: Colors.orange.withOpacity(0.7),
                                          shape: BoxShape.circle,
                                          border: Border.all(color: Colors.white, width: 2),
                                        ),
                                        child: const Icon(Icons.arrow_back, color: Colors.white),
                                      ),
                                    ),
                                    const SizedBox(width: 10),
                                    GestureDetector(
                                      onTapDown: (_) => setState(() => _rightPressed = true),
                                      onTapUp: (_) => setState(() => _rightPressed = false),
                                      onTapCancel: () => setState(() => _rightPressed = false),
                                      child: Container(
                                        width: 60,
                                        height: 60,
                                        decoration: BoxDecoration(
                                          color: Colors.orange.withOpacity(0.7),
                                          shape: BoxShape.circle,
                                          border: Border.all(color: Colors.white, width: 2),
                                        ),
                                        child: const Icon(Icons.arrow_forward, color: Colors.white),
                                      ),
                                    ),
                                  ],
                                ),
                                const SizedBox(height: 8),
                                const Text('火焰人', style: TextStyle(color: Colors.orange, fontWeight: FontWeight.bold)),
                              ],
                            ),
                          ],
                        ),
                      ),
                    // 隐藏控制提示
                    if (!_showControls)
                      Positioned(
                        top: 80,
                        left: 0,
                        right: 0,
                        child: Center(
                          child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                            decoration: BoxDecoration(
                              color: Colors.black.withOpacity(0.6),
                              borderRadius: BorderRadius.circular(20),
                            ),
                            child: const Text('点击屏幕显示/隐藏控制', style: TextStyle(color: Colors.white)),
                          ),
                        ),
                      ),
                  ],
                ),
              ),
            ),
          ),
          // 键盘操作说明
          Container(
            padding: const EdgeInsets.all(12),
            color: Colors.black87,
            child: Wrap(
              alignment: WrapAlignment.center,
              spacing: 16,
              children: [
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Text('火焰人: ', style: TextStyle(color: Colors.orange)),
                    _buildKeyBadge('A'),
                    _buildKeyBadge('D'),
                    _buildKeyBadge('W'),
                  ],
                ),
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Text('冰人: ', style: TextStyle(color: Colors.blue)),
                    _buildKeyBadge('J'),
                    _buildKeyBadge('L'),
                    _buildKeyBadge('I'),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildKeyBadge(String key) {
    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 2),
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      decoration: BoxDecoration(
        color: Colors.grey[700],
        borderRadius: BorderRadius.circular(6),
        border: Border.all(color: Colors.grey[500]!),
      ),
      child: Text(key, style: const TextStyle(fontSize: 14)),
    );
  }
}

// 游戏元素类
class Platform {
  final int x;
  final int y;
  final int width;
  final int height;

  const Platform({
    required this.x,
    required this.y,
    required this.width,
    required this.height,
  });
}

class LavaPool {
  final int x;
  final int y;
  final int width;
  final int height;

  const LavaPool({
    required this.x,
    required this.y,
    required this.width,
    required this.height,
  });
}

class WaterPool {
  final int x;
  final int y;
  final int width;
  final int height;

  const WaterPool({
    required this.x,
    required this.y,
    required this.width,
    required this.height,
  });
}

class Gem {
  final double x;
  final double y;
  final Color color;

  const Gem({
    required this.x,
    required this.y,
    required this.color,
  });
}

class Lever {
  final double x;
  final double y;
  bool isActive;

  Lever({
    required this.x,
    required this.y,
    this.isActive = false,
  });
}

class Gate {
  final double x;
  final double y;
  bool isOpen;

  Gate({
    required this.x,
    required this.y,
    this.isOpen = false,
  });
}
相关推荐
2601_949833392 小时前
flutter_for_openharmony口腔护理app实战+饮食记录实现
android·javascript·flutter
独自破碎E2 小时前
【滑动窗口+字符计数数组】LCR_014_字符串的排列
android·java·开发语言
stevenzqzq2 小时前
compose 中 align和Arrangement的区别
android·compose
VincentWei952 小时前
Compose:MutableState 和 mutableStateOf
android
jian110582 小时前
Android studio 调试flutter 运行自己的苹果手机上
flutter·智能手机·android studio
向哆哆2 小时前
高校四六级报名管理系统的考试信息模块实现:Flutter × OpenHarmony 跨端开发实践
flutter·开源·鸿蒙·openharmony·开源鸿蒙
jian110582 小时前
Android studio配置flutter,mac Android studio 发现苹果手机设备
android·flutter·android studio
ujainu3 小时前
Flutter + OpenHarmony 实战:《圆环跳跃》——完整游戏架构与视觉优化
flutter·游戏·架构·openharmony
2501_944448003 小时前
Flutter for OpenHarmony衣橱管家App实战:统计分析实现
flutter·信息可视化