Flutter for OpenHarmony:打造令人惊艳的《冰火人》双人合作闯关游戏主菜单
Flutter for OpenHarmony:打造令人惊艳的《冰火人》双人合作闯关游戏主菜单
·
Flutter for OpenHarmony:打造令人惊艳的《冰火人》双人合作闯关游戏主菜单
在移动应用和游戏中,第一印象至关重要。一个平庸的主菜单可能会让用户瞬间失去兴趣,而一个精心设计、充满活力的主菜单则能立刻抓住眼球,为整个游戏体验定下高水准的基调。本文将深入剖析一段来自《冰火人闯关》游戏的
高度优化版主菜单 代码,揭示如何利用 Flutter 的动画、渐变和自定义绘制能力,将一个简单的入口页面转变为一场视觉盛宴。
完整效果展示



一、从静态到动态:赋予菜单生命力
对比基础版本,优化后的 MainMenu 最显著的升级在于其 动态感。它不再是静止的图标和按钮,而是一个拥有呼吸和脉动的生命体。
核心机制:AnimationController 与循环动画
late AnimationController _animationController;
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
)..repeat(reverse: true); // 关键:无限循环往复
}

repeat(reverse: true)是魔法所在。它让动画在正向播放结束后自动反向播放,形成一个无缝的“呼吸”或“心跳”效果,持续时间为4秒(2秒正向 + 2秒反向)。vsync: this确保了动画与屏幕刷新率同步,避免了不必要的性能消耗。
多重动画协同
代码并非只使用了一个动画,而是通过 Tween 创建了三个相互关联的动画:
_scaleAnimation: 控制游戏图标的缩放(1.0x 到 1.08x),制造出轻微的“膨胀”感。_opacityAnimation: 控制游戏标题的透明度(0.5 到 1.0),使其产生淡入淡出的效果。_glowAnimation: 控制图标周围发光 (boxShadow) 的模糊半径(10 到 30),模拟光源强度的变化。
这些动画被 AnimatedBuilder 监听,并实时更新 UI,共同营造出丰富而和谐的动态效果。
二、视觉冲击力:高级图形与色彩运用
1. 背景:戏剧性的对角线渐变
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.red.shade900, Colors.orange.shade800, Colors.blue.shade800, Colors.blue.shade900],
stops: [0.0, 0.33, 0.66, 1.0],
)

- 这个四色渐变从左上角的深红,经过橙色,过渡到右下角的深蓝,完美呼应了“冰火”主题。
stops参数精确控制了每种颜色的分布区域,使得色彩过渡更加自然和富有层次感,远超简单的双色渐变。
2. 动态粒子背景
Positioned.fill(child: CustomPaint(painter: ParticlePainter()))
ParticlePainter是一个自定义的CustomPainter,它在画布上随机绘制了80个微小的白色圆点。- 虽然粒子本身是静态的,但作为深色渐变背景上的点缀,极大地增加了画面的细节和深度,营造出一种宇宙星空或微观世界的氛围,让背景不再单调。
3. 游戏图标:融合光效与质感
图标被包裹在一个圆形容器中,该容器同时应用了:
- 前景渐变 (
LinearGradient): 从橙色到红色,强化火焰意象。- 双重发光阴影 (
BoxShadow): 一个明亮的橙色外发光和一个稍暗的蓝色内发光(通过调整spreadRadius实现),创造出强烈的立体感和能量感。这个发光效果正是由_glowAnimation驱动的。
4. 游戏标题:炫彩的金属文字
ShaderMask(
shaderCallback: (bounds) => LinearGradient(colors: [Colors.yellow, Colors.orange, ...]).createShader(bounds),
child: Text('冰火人闯关', ...),
)

ShaderMask是实现高级文字效果的关键。它用一个从黄到紫的线性渐变着色器 (Shader) 来“切割”白色的文字,最终呈现出彩虹般的金属光泽。- 配合
shadows属性添加的黑色模糊阴影,文字仿佛悬浮于背景之上,极具视觉冲击力。
三、交互体验:精致的按钮设计
主菜单的四个功能按钮(开始游戏、游戏说明、设置、关于)也经过了精心打磨:
1. 悬浮感与深度
每个按钮都拥有一个精心调校的 BoxShadow:
BoxShadow(
color: color.withOpacity(0.4), // 使用按钮主色的半透明版本
blurRadius: 20,
offset: Offset(0, 10),
spreadRadius: -5, // 负值使阴影更集中,增强悬浮感
)

这种阴影设计让按钮看起来像是漂浮在背景之上,点击时的反馈会更加明显。
2. 统一而有辨识度的风格
- 所有按钮采用统一的圆角矩形 (
borderRadius: 16) 和内边距。- 每个按钮左侧都有一个与功能匹配的图标(如
play_arrow、menu_book),并使用对应的主题色(绿色、蓝色、灰色、紫色),在保持整体一致性的同时,提供了清晰的功能指引。
3. 弹性导航与对话框
- 页面切换:使用
PageRouteBuilder自定义了从主菜单到关卡选择的过渡动画,结合了淡入 (FadeTransition) 和从底部滑入 (SlideTransition) 的效果,流畅且富有动感。- 信息展示:游戏说明、设置和关于页面均以 全屏模态对话框 (
Dialog) 的形式弹出。这些对话框自身也带有深灰色渐变背景、圆角和半透明白色边框,风格与主菜单完全统一,提供了一致的沉浸式体验。
四、代码结构:优雅与高效
_buildMenuButton方法:将重复的按钮 UI 逻辑抽象成一个私有方法,遵循了 DRY (Don’t Repeat Yourself) 原则,使build方法极其简洁易读。_buildInstructionItem/_buildSettingItem方法:同样地,将对话框内部的列表项进行了组件化,极大地提升了代码的可维护性和扩展性。
总结
这段优化后的主菜单代码,充分展示了 Flutter 在构建高端 UI 方面的强大实力。它不仅仅是一个功能入口,更是一个集 动态美学、品牌传达和用户体验 于一体的微型艺术品。
🌐 加入社区
欢迎加入 开源鸿蒙跨平台开发者社区,获取最新资源与技术支持:
👉 开源鸿蒙跨平台开发者社区
完整代码展示
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 StatefulWidget {
const MainMenu({super.key});
@override
State<MainMenu> createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
late Animation<double> _glowAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
)..repeat(reverse: true);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.08).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
_opacityAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
_glowAnimation = Tween<double>(begin: 10, end: 30).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.red.shade900,
Colors.orange.shade800,
Colors.blue.shade800,
Colors.blue.shade900,
],
stops: const [0.0, 0.33, 0.66, 1.0],
),
),
child: Stack(
children: [
// 动态背景粒子
Positioned.fill(
child: CustomPaint(
painter: ParticlePainter(),
),
),
// 主内容
Center(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: screenWidth * 0.08,
vertical: screenHeight * 0.05,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 游戏图标 - 带动画
AnimatedBuilder(
animation: Listenable.merge([_scaleAnimation, _glowAnimation]),
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.orange, Colors.red],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.orange.withOpacity(0.4),
blurRadius: _glowAnimation.value,
spreadRadius: 8,
),
BoxShadow(
color: Colors.blue.withOpacity(0.3),
blurRadius: _glowAnimation.value * 0.8,
spreadRadius: 5,
),
],
),
child: const Icon(
Icons.extension,
size: 100,
color: Colors.white,
),
),
);
},
),
SizedBox(height: screenHeight * 0.03),
// 游戏标题 - 渐变文字
AnimatedBuilder(
animation: _opacityAnimation,
builder: (context, child) {
return Opacity(
opacity: _opacityAnimation.value,
child: ShaderMask(
shaderCallback: (bounds) => const LinearGradient(
colors: [
Colors.yellow,
Colors.orange,
Colors.red,
Colors.purple,
],
tileMode: TileMode.mirror,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
).createShader(bounds),
child: const Text(
'冰火人闯关',
style: TextStyle(
fontSize: 56,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 4,
shadows: [
Shadow(
color: Colors.black54,
blurRadius: 20,
offset: Offset(0, 8),
),
],
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
);
},
),
SizedBox(height: screenHeight * 0.015),
// 副标题
Text(
'Fireboy & Watergirl Adventure',
style: TextStyle(
fontSize: 18,
color: Colors.white.withOpacity(0.8),
letterSpacing: 6,
fontWeight: FontWeight.w300,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: screenHeight * 0.08),
// 开始游戏按钮
_buildMenuButton(
icon: Icons.play_arrow_rounded,
label: '开始游戏',
color: Colors.green,
onPressed: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const LevelSelectScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
)),
child: child,
),
);
},
transitionDuration: const Duration(milliseconds: 400),
),
);
},
),
SizedBox(height: screenHeight * 0.025),
// 游戏说明按钮
_buildMenuButton(
icon: Icons.menu_book_rounded,
label: '游戏说明',
color: Colors.blue,
onPressed: () => _showInstructions(context),
),
SizedBox(height: screenHeight * 0.025),
// 设置按钮
_buildMenuButton(
icon: Icons.settings_rounded,
label: '设置',
color: Colors.grey.shade700,
onPressed: () => _showSettings(context),
),
SizedBox(height: screenHeight * 0.025),
// 关于按钮
_buildMenuButton(
icon: Icons.info_outline_rounded,
label: '关于',
color: Colors.purple.shade600,
onPressed: () => _showAbout(context),
),
SizedBox(height: screenHeight * 0.04),
// 版本信息
Text(
'Version 1.0.0',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.5),
letterSpacing: 1,
),
),
],
),
),
),
),
],
),
),
);
}
Widget _buildMenuButton({
required IconData icon,
required String label,
required Color color,
required VoidCallback onPressed,
}) {
return Container(
width: double.infinity,
height: 68,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.4),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: -5,
),
],
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: color,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
),
onPressed: onPressed,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 28),
const SizedBox(width: 12),
Flexible(
child: Text(
label,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
}
void _showInstructions(BuildContext context) {
showDialog(
context: context,
builder: (context) => Dialog(
backgroundColor: Colors.transparent,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.85,
),
child: Container(
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.grey.shade900, Colors.grey.shade800],
),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Colors.white.withOpacity(0.2)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.info_outline, color: Colors.blue, size: 32),
),
const SizedBox(width: 16),
const Flexible(
child: Text(
'游戏说明',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Spacer(),
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close, color: Colors.white),
),
],
),
const Divider(height: 32, color: Colors.white24),
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildInstructionItem(
icon: Icons.local_fire_department,
iconColor: Colors.orange,
title: '火焰人操作',
items: ['A/D 或 ←/→: 左右移动', 'W 或 ↑: 跳跃'],
),
const SizedBox(height: 20),
_buildInstructionItem(
icon: Icons.ac_unit,
iconColor: Colors.blue,
title: '冰人操作',
items: ['J/L: 左右移动', 'I: 跳跃'],
),
const SizedBox(height: 20),
_buildInstructionItem(
icon: Icons.flag,
iconColor: Colors.yellow,
title: '游戏目标',
items: [
'火焰人到达红色门',
'冰人到达蓝色门',
'两人必须同时到达才能过关',
],
),
const SizedBox(height: 20),
_buildInstructionItem(
icon: Icons.warning,
iconColor: Colors.red,
title: '注意事项',
items: [
'火焰人碰到水会死亡',
'冰人碰到岩浆会死亡',
'收集宝石可提高评分',
],
),
],
),
),
),
],
),
),
),
),
);
}
Widget _buildInstructionItem({
required IconData icon,
required Color iconColor,
required String title,
required List<String> items,
}) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: iconColor, size: 24),
),
const SizedBox(width: 12),
Expanded(
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 16),
...items.map((item) => Padding(
padding: const EdgeInsets.only(left: 16, bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 6),
width: 6,
height: 6,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.6),
shape: BoxShape.circle,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
item,
style: const TextStyle(
fontSize: 14,
color: Colors.white70,
height: 1.6,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
],
),
)),
],
),
);
}
void _showSettings(BuildContext context) {
showDialog(
context: context,
builder: (context) => Dialog(
backgroundColor: Colors.transparent,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.7,
),
child: Container(
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.grey.shade900, Colors.grey.shade800],
),
borderRadius: BorderRadius.circular(24),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.settings, color: Colors.grey, size: 32),
),
const SizedBox(width: 16),
const Flexible(
child: Text(
'设置',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Spacer(),
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close, color: Colors.white),
),
],
),
const Divider(height: 32, color: Colors.white24),
Expanded(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildSettingItem(
icon: Icons.volume_up,
title: '音效',
subtitle: '开启/关闭游戏音效',
value: true,
),
const SizedBox(height: 16),
_buildSettingItem(
icon: Icons.music_note,
title: '背景音乐',
subtitle: '开启/关闭背景音乐',
value: true,
),
const SizedBox(height: 16),
_buildSettingItem(
icon: Icons.vibration,
title: '震动反馈',
subtitle: '开启/关闭震动反馈',
value: false,
),
],
),
),
),
],
),
),
),
),
);
}
Widget _buildSettingItem({
required IconData icon,
required String title,
required String subtitle,
required bool value,
}) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: Colors.white70, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.6),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
Switch(
value: value,
onChanged: (val) {},
activeColor: Colors.green,
),
],
),
);
}
void _showAbout(BuildContext context) {
showDialog(
context: context,
builder: (context) => Dialog(
backgroundColor: Colors.transparent,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.6,
),
child: Container(
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.grey.shade900, Colors.grey.shade800],
),
borderRadius: BorderRadius.circular(24),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.purple.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.info_outline, color: Colors.purple, size: 32),
),
const SizedBox(width: 16),
const Flexible(
child: Text(
'关于',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Spacer(),
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close, color: Colors.white),
),
],
),
const Divider(height: 32, color: Colors.white24),
Expanded(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.orange, Colors.blue],
),
shape: BoxShape.circle,
),
child: const Icon(
Icons.extension,
size: 80,
color: Colors.white,
),
),
const SizedBox(height: 24),
const Text(
'冰火人闯关',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 12),
const Text(
'Fireboy & Watergirl Adventure',
style: TextStyle(
fontSize: 16,
color: Colors.white70,
),
),
const SizedBox(height: 24),
const Text(
'一款经典的双人协作闯关游戏,\n控制火焰人和冰人配合,\n完成各种挑战!',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.white70,
height: 1.6,
),
),
const SizedBox(height: 32),
const Text(
'Version 1.0.0',
style: TextStyle(
fontSize: 14,
// ignore: deprecated_member_use
color: Colors.white54,
),
),
],
),
),
),
],
),
),
),
),
);
}
}
// 背景粒子绘制器
class ParticlePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.white.withOpacity(0.08);
for (int i = 0; i < 80; i++) {
final x = (i * 97) % size.width;
final y = (i * 53) % size.height;
final radius = (i % 4) + 1.0;
canvas.drawCircle(
Offset(x.toDouble(), y.toDouble()),
radius,
paint,
);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
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,
});
}
更多推荐



所有评论(0)