Flutter for OpenHarmony 游戏中心App实战:颜色匹配游戏实现
摘要 本文介绍了一个基于斯特鲁普效应的颜色匹配游戏实现。游戏玩法要求玩家快速判断屏幕上显示的颜色词语与其实际颜色是否匹配,在30秒内尽可能多地做出正确判断。游戏使用Flutter框架开发,主要特点包括: 游戏机制:利用颜色词语和显示颜色的随机组合(如蓝色的"红色"文字),考验玩家反应速度和注意力 技术实现: 使用StatefulWidget管理游戏状态 Timer.period

在前面的文章中,我们实现了多个不同类型的游戏,从益智类到文字类,涵盖了各种游戏机制。这次我们要实现一个颜色匹配游戏,这是一个考验反应速度和注意力的游戏。通过实现这个游戏,你将学习到如何使用周期性定时器、处理颜色数据、实现倒计时等技能。
颜色匹配游戏的玩法
颜色匹配游戏的玩法非常有趣:屏幕上会显示一个词语,比如"红色",但这个词语的颜色可能是红色,也可能是其他颜色,比如蓝色。玩家需要快速判断词语的文字内容和显示颜色是否匹配。如果匹配,点击"匹配"按钮;如果不匹配,点击"不匹配"按钮。
这个游戏利用了斯特鲁普效应(Stroop Effect),即当词语的含义和颜色不一致时,人的反应会变慢。比如看到蓝色的"红色"两个字,大脑需要更长时间来判断。游戏有30秒的时间限制,玩家需要在时间内尽可能多地做出正确判断。答对得分,答错扣分,考验玩家的反应速度和注意力。
页面结构的搭建
ColorMatchPage是一个有状态组件,需要管理颜色、文字和计时器:
class ColorMatchPage extends StatefulWidget {
const ColorMatchPage({super.key});
State<ColorMatchPage> createState() => _ColorMatchPageState();
}
使用StatefulWidget是因为游戏状态会随着时间和玩家的操作不断变化。每秒钟倒计时会减少,每次玩家做出判断后颜色和文字会改变。这些变化都需要通过State类来管理,并通过setState方法触发UI更新。相比前面的游戏,颜色匹配涉及到周期性定时器和生命周期管理。
游戏状态的定义
在State类中,我们需要定义游戏的核心状态和数据:
class _ColorMatchPageState extends State<ColorMatchPage> {
final List<Color> colors = [Colors.red, Colors.blue, Colors.green,
Colors.yellow, Colors.purple, Colors.orange];
final List<String> colorNames = ['红色', '蓝色', '绿色',
'黄色', '紫色', '橙色'];
Color currentColor = Colors.red;
String currentText = '红色';
int score = 0;
int timeLeft = 30;
Timer? timer;
colors是颜色列表,包含6种常见颜色。colorNames是对应的颜色名称列表。这两个列表的索引是对应的,比如colors[0]是红色,colorNames[0]是"红色"。使用final修饰,因为这些数据在游戏过程中不会改变。
currentColor是当前显示的颜色,currentText是当前显示的文字。score记录得分,timeLeft记录剩余时间,初始为30秒。timer是Timer对象的引用,用于管理倒计时。使用可空类型Timer?,因为timer可能为null。
这种将颜色和名称分开存储的设计,让我们可以灵活地组合它们。比如可以显示红色的"蓝色",或者蓝色的"红色"。通过随机选择颜色和名称的索引,可以生成各种组合。
初始化和清理
页面创建和销毁时需要进行初始化和清理:
void initState() {
super.initState();
_generateNew();
_startTimer();
}
void dispose() {
timer?.cancel();
super.dispose();
}
initState方法在页面创建时调用一次,我们在这里调用_generateNew生成初始的颜色和文字,调用_startTimer启动倒计时。这种在initState中初始化游戏的做法,确保用户看到页面时游戏已经开始了。
dispose方法在页面销毁时调用,我们在这里取消定时器。timer?.cancel()使用了空安全的调用语法,如果timer为null就不调用cancel。这是非常重要的清理操作,如果不取消定时器,会导致内存泄漏,定时器会继续运行并尝试更新已销毁的页面。
这种生命周期管理是Flutter开发的基础。initState用于初始化,dispose用于清理。所有需要清理的资源,比如定时器、动画控制器、流订阅等,都应该在dispose中释放。
倒计时的实现
倒计时是游戏的核心机制,使用Timer.periodic实现:
void _startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (timeLeft > 0) {
setState(() => timeLeft--);
} else {
timer.cancel();
}
});
}
Timer.periodic创建一个周期性定时器,每秒执行一次回调函数。回调函数接收timer参数,可以用来取消定时器。如果剩余时间大于0,减少时间并更新UI。如果时间到了,取消定时器,游戏结束。
这里使用setState(() => timeLeft–)这种简洁的写法,等价于setState(() { timeLeft–; })。Dart支持这种箭头函数的语法,当函数体只有一条语句时,可以省略花括号。
Timer.periodic与Timer的区别是:Timer是一次性的,执行一次后就结束。Timer.periodic是周期性的,会重复执行,直到被取消。在实现倒计时、轮询、动画等功能时,Timer.periodic非常有用。
需要注意的是,定时器的精度不是绝对的。由于系统调度和其他因素,实际的执行间隔可能略有偏差。但对于游戏来说,这种偏差是可以接受的。
颜色和文字的生成
每次玩家做出判断后,需要生成新的颜色和文字:
void _generateNew() {
final random = Random();
setState(() {
currentColor = colors[random.nextInt(colors.length)];
currentText = colorNames[random.nextInt(colorNames.length)];
});
}
使用Random类生成随机索引。random.nextInt(colors.length)生成0到colors.length-1的随机整数,用作颜色索引。random.nextInt(colorNames.length)生成颜色名称索引。这两个索引是独立随机的,所以颜色和文字可能匹配,也可能不匹配。
这种独立随机的设计,让游戏有大约1/6的概率生成匹配的组合(因为有6种颜色)。这个概率既不会太高让游戏太简单,也不会太低让游戏太难。可以通过调整颜色数量来改变难度,颜色越多,匹配概率越低,游戏越难。
setState包裹状态更新,触发UI重新构建。玩家会看到新的颜色和文字立即显示在屏幕上,可以继续做出判断。这种快速的反馈循环,让游戏节奏紧凑,保持玩家的注意力。
答案判断逻辑
当玩家做出判断时,需要验证答案是否正确:
void _answer(bool match) {
if (timeLeft == 0) return;
final isMatch = colors.indexOf(currentColor) ==
colorNames.indexOf(currentText);
if (match == isMatch) {
setState(() => score++);
} else {
setState(() => score = max(0, score - 1));
}
_generateNew();
}
_answer方法接收一个布尔值参数,表示玩家认为是否匹配。首先检查时间是否已经用完,如果用完了就不处理。然后计算实际是否匹配:使用indexOf找到当前颜色和文字在列表中的索引,如果索引相同,说明匹配。
比较玩家的判断和实际情况,如果相同说明答对了,增加得分。如果不同说明答错了,减少得分。使用max(0, score - 1)确保得分不会变成负数。最后调用_generateNew生成新的颜色和文字,继续游戏。
这里使用indexOf方法来判断匹配,而不是直接比较currentColor和colors[colorNames.indexOf(currentText)]。这种方式更加清晰,表达了"找到颜色的索引和找到文字的索引,比较它们是否相同"这个逻辑。
页面UI的构建
游戏页面的UI包括AppBar、时间和得分显示、颜色文字显示和判断按钮:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('颜色匹配'),
backgroundColor: const Color(0xFF16213e),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('时间: $timeLeft秒',
style: TextStyle(fontSize: 20.sp)),
SizedBox(height: 10.h),
Text('得分: $score',
style: TextStyle(fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.amber)),
SizedBox(height: 50.h),
AppBar的标题显示"颜色匹配",背景色使用深蓝色。Center让内容居中显示,Column垂直排列页面内容。最上面显示剩余时间,使用20号字体。下面显示得分,使用24号粗体琥珀色字体。SizedBox添加50个单位的间距,将得分和游戏内容分隔开。
游戏进行中的显示:
if (timeLeft > 0) ...[
Text('颜色和文字是否匹配?',
style: TextStyle(fontSize: 18.sp)),
SizedBox(height: 30.h),
Text(currentText,
style: TextStyle(fontSize: 48.sp,
fontWeight: FontWeight.bold,
color: currentColor)),
SizedBox(height: 50.h),
如果时间还没用完,显示提示文字和当前的颜色文字。currentText使用48号粗体字显示,颜色设置为currentColor。这是游戏的核心:文字内容和显示颜色可能不一致,玩家需要快速判断它们是否匹配。
这里使用了Dart的集合展开运算符…和if语句的组合。if (timeLeft > 0) …[…]表示如果时间还没用完,就将方括号中的Widget添加到children列表中。这种语法让条件渲染的代码非常简洁。
判断按钮的实现
两个判断按钮是玩家与游戏交互的主要方式:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _answer(true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
padding: EdgeInsets.symmetric(
horizontal: 40.w, vertical: 20.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r)),
),
child: Text('匹配',
style: TextStyle(fontSize: 18.sp)),
),
SizedBox(width: 20.w),
Row横向排列两个按钮,mainAxisAlignment设置为center让按钮居中。第一个按钮是"匹配"按钮,背景色设置为绿色,表示肯定的选择。padding设置水平40个单位、垂直20个单位的内边距,让按钮有足够的点击区域。shape设置圆角为12个单位。
第二个按钮的实现:
ElevatedButton(
onPressed: () => _answer(false),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: EdgeInsets.symmetric(
horizontal: 40.w, vertical: 20.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r)),
),
child: Text('不匹配',
style: TextStyle(fontSize: 18.sp)),
),
],
),
"不匹配"按钮的背景色设置为红色,表示否定的选择。其他样式与"匹配"按钮相同。这种使用颜色区分按钮功能的设计,让玩家可以快速识别,不需要仔细阅读文字。绿色表示肯定,红色表示否定,这是通用的视觉语言。
游戏结束的显示
当时间用完后,显示游戏结束画面:
] else ...[
Text('⏰ 时间到!',
style: TextStyle(fontSize: 32.sp,
fontWeight: FontWeight.bold)),
SizedBox(height: 20.h),
Text('最终得分: $score',
style: TextStyle(fontSize: 28.sp,
color: Colors.amber)),
],
],
),
),
);
}
如果时间用完,显示"时间到!"和最终得分。使用32号粗体字显示提示,配合时钟emoji。最终得分使用28号琥珀色字体显示,非常醒目。这种简洁的结束画面,让玩家清楚地知道游戏已经结束,可以查看自己的成绩。
这里使用了else分支,与前面的if (timeLeft > 0)对应。这种if-else的结构,让代码逻辑清晰,要么显示游戏内容,要么显示结束画面,不会同时显示两者。
斯特鲁普效应的应用
颜色匹配游戏利用了心理学中的斯特鲁普效应。这个效应是由心理学家John Ridley Stroop在1935年发现的,描述了当词语的含义和颜色不一致时,人的反应会变慢的现象。
比如看到红色的"红色"两个字,大脑可以快速判断它们匹配。但看到蓝色的"红色"两个字,大脑需要抑制对文字含义的自动反应,专注于颜色本身,这需要更长的时间。
这个效应在认知心理学中有重要意义,说明了人类信息处理的特点。在游戏中应用这个效应,可以创造出有趣的挑战。玩家需要克服大脑的自动反应,专注于颜色本身,这需要注意力和自控力。
可以通过调整匹配和不匹配的比例来改变难度。如果大部分都是不匹配的,玩家会习惯性地选择"不匹配",反而容易答错匹配的情况。如果大部分都是匹配的,玩家会习惯性地选择"匹配",反而容易答错不匹配的情况。保持大约50%的匹配率,可以让游戏难度适中。
Timer.periodic的深入理解
Timer.periodic是实现周期性任务的核心工具,让我们深入了解一下它的用法:
Timer.periodic(Duration(seconds: 1), (timer) {
// 每秒执行一次
});
Timer.periodic接收两个参数:执行间隔和回调函数。回调函数接收timer参数,可以用来取消定时器。定时器会一直运行,直到被取消或页面销毁。
可以在回调函数中根据条件取消定时器:
int count = 0;
Timer.periodic(Duration(seconds: 1), (timer) {
count++;
if (count >= 10) {
timer.cancel(); // 执行10次后取消
}
});
需要注意的是,Timer.periodic的回调函数是在主线程执行的。如果回调函数执行时间过长,会阻塞UI,导致卡顿。应该避免在回调函数中进行耗时操作,比如复杂计算、网络请求等。
如果需要在后台线程执行任务,可以使用Isolate。但对于简单的游戏逻辑,在主线程执行已经足够了。
Timer.periodic的精度受系统调度影响,实际执行间隔可能略有偏差。如果需要高精度的定时,可以使用Stopwatch记录实际经过的时间,根据实际时间来更新状态。
颜色的使用技巧
Flutter提供了丰富的颜色API,让我们深入了解一下颜色的使用:
Color red = Colors.red; // 预定义颜色
Color custom = Color(0xFFFF0000); // 自定义颜色(ARGB格式)
Color fromRGB = Color.fromRGBO(255, 0, 0, 1.0); // 从RGB创建
Color fromARGB = Color.fromARGB(255, 255, 0, 0); // 从ARGB创建
颜色可以进行各种操作:
Color lighter = red.withOpacity(0.5); // 调整透明度
Color darker = Color.lerp(red, Colors.black, 0.5); // 颜色插值
int alpha = red.alpha; // 获取alpha值
int redValue = red.red; // 获取红色分量
在我们的游戏中,使用了预定义的颜色Colors.red、Colors.blue等。这些颜色是Material Design规范中定义的,有统一的视觉效果。如果需要自定义颜色,可以使用Color构造函数。
颜色的选择也很重要。我们选择了红、蓝、绿、黄、紫、橙六种颜色,它们的色相差异明显,容易区分。如果选择相近的颜色,比如深红和浅红,玩家可能难以区分,影响游戏体验。
性能优化的考虑
颜色匹配游戏的性能表现很好,因为游戏逻辑简单,UI更新频率适中。每秒更新一次倒计时,每次判断后更新一次颜色和文字,这些更新都很轻量级。
需要注意的是,定时器的回调函数会频繁执行。虽然我们的回调函数很简单,只是减少时间变量,但如果在回调函数中进行复杂操作,可能会影响性能。应该保持回调函数简洁高效。
另一个需要注意的是,定时器必须在页面销毁时取消。如果忘记取消,定时器会继续运行,尝试更新已销毁的页面,导致错误和内存泄漏。这是Flutter开发中常见的错误,需要特别注意。
可以使用Flutter DevTools的性能分析工具,监控应用的帧率和内存使用。如果发现性能问题,可以使用Timeline工具查看具体的性能瓶颈。
扩展功能的思考
颜色匹配游戏还有很多可以扩展的功能。比如可以添加难度选择,简单模式有更多时间,困难模式时间更短。可以添加连击系统,连续答对可以获得额外奖励。可以添加生命值系统,答错扣除生命值,生命值用完游戏结束。
还可以添加更多的颜色,增加游戏难度。可以添加特殊模式,比如只显示匹配的组合,或者只显示不匹配的组合。可以添加排行榜,记录最高分和最快速度。
这些扩展功能的实现都不需要大幅修改现有代码。比如添加难度选择,只需要根据难度调整初始时间。添加连击系统,只需要添加一个连击计数器,连续答对时增加,答错时重置。良好的代码组织让扩展变得容易。
总结
本文详细介绍了颜色匹配游戏的实现。我们从游戏玩法设计开始,定义了游戏状态,实现了倒计时、颜色生成、答案判断等核心逻辑,最后构建了游戏的UI。每个部分都有详细的代码和讲解,帮助你理解实现的原理。
颜色匹配游戏利用了斯特鲁普效应,考验玩家的反应速度和注意力。通过实现这个游戏,你学习到了如何使用Timer.periodic实现周期性任务,如何在dispose中清理资源,如何使用颜色API。我们还深入探讨了斯特鲁普效应、定时器精度、性能优化等高级话题。
这些技能不仅适用于颜色匹配游戏,也适用于其他需要定时任务和生命周期管理的应用。Timer.periodic在实现倒计时、轮询、动画等功能时很有用。生命周期管理是Flutter开发的基础,正确地初始化和清理资源很重要。
接下来的文章中,我们会继续实现最后一个游戏。通过学习这些游戏,你已经掌握了Flutter游戏开发的各种技巧,可以开发出各种各样的游戏和应用。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)