Flutter for OpenHarmony 游戏中心App实战:单词接龙游戏实现
本文介绍了使用Flutter实现单词接龙游戏的关键技术。游戏核心玩法是玩家输入的新词语首字必须匹配上一词语的末字,且不能重复使用。通过StatefulWidget管理游戏状态,包括词语列表和得分记录。文本处理涉及字符串匹配验证,使用TextEditingController获取输入内容,通过SnackBar提供错误反馈。UI设计包含得分显示、当前词语提示、输入框和历史记录展示,实现了完整的游戏交互

在前面的文章中,我们实现了多个不同类型的游戏,学习了状态管理、数学计算、随机数生成等技能。这次我们要实现一个单词接龙游戏,这是一个考验词汇量和反应速度的文字游戏。通过实现这个游戏,你将学习到如何处理文本输入、实现字符串匹配、管理历史记录等技能。
单词接龙游戏的玩法
单词接龙是一个经典的文字游戏。游戏从一个初始词语开始,玩家需要输入一个新词语,这个词语的第一个字必须是上一个词语的最后一个字。比如上一个词语是"游戏",玩家可以输入"戏剧"、"戏曲"等以"戏"开头的词语。每输入一个正确的词语,得分增加。已经使用过的词语不能重复使用。
这种玩法简单但有趣,既考验玩家的词汇量,也考验反应速度。玩家需要快速想出符合规则的词语,同时要记住哪些词语已经使用过。游戏可以无限进行下去,直到玩家想不出新词语为止。这种开放式的玩法,让游戏有很高的可玩性。
页面结构的搭建
WordGamePage是一个有状态组件,需要管理词语列表和得分:
class WordGamePage extends StatefulWidget {
const WordGamePage({super.key});
State<WordGamePage> createState() => _WordGamePageState();
}
使用StatefulWidget是因为游戏状态会随着玩家的输入不断变化。每次玩家提交词语,词语列表就会增加,得分就会增加。这些变化都需要通过State类来管理,并通过setState方法触发UI更新。相比前面的游戏,单词接龙的状态管理涉及到文本处理和字符串匹配。
游戏状态的定义
在State类中,我们需要定义游戏的核心状态:
class _WordGamePageState extends State<WordGamePage> {
final TextEditingController _controller = TextEditingController();
final List<String> words = ['游戏'];
int score = 0;
_controller是TextEditingController对象,用于管理输入框的文本。TextEditingController是Flutter中处理文本输入的核心类,可以获取和设置输入框的内容,监听文本变化等。使用final修饰,因为controller在创建后不会改变。
words是词语列表,初始包含一个词语"游戏"。这是游戏的起点,玩家需要输入以"戏"开头的词语。使用final修饰,虽然列表内容会改变,但列表对象本身不会改变。score记录玩家的得分,初始值为0。
注意这里没有initState方法,因为所有状态都在声明时初始化了。TextEditingController需要在页面销毁时释放,所以应该添加dispose方法,但在我们的简单实现中省略了。在实际项目中,应该添加dispose方法调用_controller.dispose(),避免内存泄漏。
词语提交逻辑
当玩家提交词语时,需要验证词语是否符合规则:
void _submitWord() {
final word = _controller.text.trim();
if (word.isEmpty) return;
final lastWord = words.last;
if (word[0] == lastWord[lastWord.length - 1] && !words.contains(word)) {
setState(() {
words.add(word);
score += 10;
_controller.clear();
});
_submitWord方法首先获取输入框的文本,使用trim()去除首尾空格。如果文本为空,直接返回,不处理。然后获取词语列表的最后一个词语,这是上一个词语。
验证规则有两个:第一,新词语的第一个字必须等于上一个词语的最后一个字。word[0]获取新词语的第一个字,lastWord[lastWord.length - 1]获取上一个词语的最后一个字。第二,新词语不能在words列表中,即不能重复使用。
如果验证通过,将新词语添加到列表,增加得分,清空输入框。这里使用setState包裹状态更新,触发UI重新构建。玩家会看到新词语出现在列表中,得分增加,输入框清空,可以继续输入下一个词语。
错误提示的处理:
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('词语不符合规则或已使用过')),
);
}
}
如果验证失败,使用SnackBar显示错误提示。SnackBar是Flutter中显示临时消息的组件,会在屏幕底部显示一个小横幅,几秒后自动消失。ScaffoldMessenger是管理SnackBar的类,使用of(context)获取当前的ScaffoldMessenger,然后调用showSnackBar显示消息。
这种错误提示的方式简单有效,不会打断玩家的游戏流程。玩家看到提示后,可以立即修改输入,重新提交。相比使用Dialog,SnackBar更加轻量级,用户体验更好。
页面UI的构建
游戏页面的UI包括AppBar、得分显示、当前词语显示、输入框和历史记录:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('单词接龙'),
backgroundColor: const Color(0xFF16213e),
),
body: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
children: [
Text('得分: $score',
style: TextStyle(fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.amber)),
SizedBox(height: 20.h),
AppBar的标题显示"单词接龙",背景色使用深蓝色。Padding设置20个单位的内边距,让内容与屏幕边缘保持距离。Column垂直排列页面内容。最上面显示得分,使用24号粗体琥珀色字体,非常醒目。
当前词语的显示:
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: const Color(0xFF16213e),
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('当前词语:',
style: TextStyle(fontSize: 16.sp,
color: Colors.white60)),
SizedBox(height: 8.h),
Text(words.last,
style: TextStyle(fontSize: 32.sp,
fontWeight: FontWeight.bold)),
SizedBox(height: 8.h),
Text('下一个词语需要以 "${words.last[words.last.length - 1]}" 开头',
style: TextStyle(fontSize: 14.sp,
color: Colors.amber)),
],
),
),
SizedBox(height: 30.h),
Container使用深蓝色背景和圆角,创建一个卡片效果。Column垂直排列三行文字:标题"当前词语"、当前词语本身、提示信息。当前词语使用32号粗体字显示,非常醒目。提示信息使用琥珀色显示,告诉玩家下一个词语应该以什么字开头。
这种清晰的提示设计,让玩家不需要自己记住规则,可以专注于想词语。words.last获取列表的最后一个元素,words.last[words.last.length - 1]获取最后一个字。这种链式访问字符串的方式,在处理文本时很常用。
输入框的实现
输入框是玩家与游戏交互的主要方式:
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: '输入词语',
filled: true,
fillColor: const Color(0xFF16213e),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r)),
),
onSubmitted: (_) => _submitWord(),
),
SizedBox(height: 16.h),
TextField是Flutter中的文本输入组件。controller设置为_controller,这样可以获取和控制输入框的内容。decoration定义输入框的外观:hintText是占位符文本,filled和fillColor设置背景色,border设置边框样式。
onSubmitted是一个回调函数,当用户按下键盘的确认键时触发。这里我们调用_submitWord方法提交词语。参数_表示不使用回调函数的参数(输入的文本),因为我们直接从controller获取文本。
这种设计让玩家可以用两种方式提交词语:按键盘的确认键,或者点击下面的提交按钮。提供多种交互方式,可以提高用户体验,适应不同用户的习惯。
提交按钮的实现:
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _submitWord,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purpleAccent,
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r)),
),
child: Text('提交',
style: TextStyle(fontSize: 18.sp)),
),
),
SizedBox(height: 20.h),
SizedBox设置按钮宽度为double.infinity占满整个宽度。ElevatedButton的onPressed设置为_submitWord方法,点击时提交词语。backgroundColor设置为紫色,padding设置垂直方向16个单位的内边距,shape设置圆角。按钮文字"提交"使用18号字体。
这种全宽的大按钮设计,在移动应用中很常见,用户很容易点击,不会误触。紫色的按钮与应用的主题色保持一致,整体视觉效果协调统一。
历史记录的显示
历史记录显示所有已使用的词语,让玩家可以回顾游戏过程:
Expanded(
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: const Color(0xFF16213e),
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('已使用词语:',
style: TextStyle(fontSize: 16.sp,
fontWeight: FontWeight.bold)),
SizedBox(height: 10.h),
Expanded让Container占据剩余的所有空间。Container使用深蓝色背景和圆角,创建一个卡片效果。Column垂直排列标题和词语列表。标题"已使用词语"使用16号粗体字显示。
词语列表的实现:
Expanded(
child: ListView.builder(
itemCount: words.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Text('${index + 1}. ${words[index]}',
style: TextStyle(fontSize: 14.sp)),
);
},
),
),
],
),
),
),
],
),
),
);
}
Expanded让ListView占据剩余的所有空间。ListView.builder用于构建词语列表,itemCount设置为words.length。itemBuilder负责构建每个词语项,显示序号和词语。Padding设置底部边距8个单位,让词语之间有适当的间隔。
ListView.builder是懒加载的,只会渲染可见区域的内容。虽然在游戏初期词语不多,但随着游戏进行,词语列表可能会很长。使用ListView.builder可以确保性能,即使有几百个词语也不会卡顿。
TextEditingController的深入理解
TextEditingController是Flutter中处理文本输入的核心类,让我们深入了解一下它的用法:
final TextEditingController _controller = TextEditingController();
TextEditingController可以获取和设置输入框的内容:
String text = _controller.text; // 获取文本
_controller.text = 'Hello'; // 设置文本
_controller.clear(); // 清空文本
还可以监听文本变化:
_controller.addListener(() {
print('文本改变: ${_controller.text}');
});
这在实现实时搜索、输入验证等功能时很有用。比如可以在用户输入时实时检查词语是否符合规则,给出即时反馈。
TextEditingController还可以控制光标位置和选中文本:
_controller.selection = TextSelection(
baseOffset: 0,
extentOffset: _controller.text.length,
); // 选中所有文本
需要注意的是,TextEditingController需要在页面销毁时释放:
void dispose() {
_controller.dispose();
super.dispose();
}
如果不释放,会导致内存泄漏。这是Flutter开发中常见的错误,需要特别注意。
字符串操作的技巧
单词接龙游戏涉及到很多字符串操作,让我们深入了解一下Dart的字符串处理:
String word = '游戏';
String firstChar = word[0]; // 获取第一个字:'游'
String lastChar = word[word.length - 1]; // 获取最后一个字:'戏'
int length = word.length; // 获取长度:2
bool isEmpty = word.isEmpty; // 是否为空:false
String trimmed = word.trim(); // 去除首尾空格
字符串还支持很多其他操作:
bool contains = word.contains('游'); // 是否包含:true
bool startsWith = word.startsWith('游'); // 是否以...开头:true
bool endsWith = word.endsWith('戏'); // 是否以...结尾:true
String upper = word.toUpperCase(); // 转大写(对中文无效)
String lower = word.toLowerCase(); // 转小写(对中文无效)
String sub = word.substring(0, 1); // 截取子串:'游'
List<String> chars = word.split(''); // 分割成字符列表:['游', '戏']
在处理中文时,需要注意一些特殊情况。比如某些中文字符可能由多个Unicode码点组成,直接使用索引访问可能会出错。但对于常见的中文字符,使用索引访问是安全的。
字符串是不可变的,所有的操作都会返回新字符串,不会修改原字符串。这是函数式编程的特性,可以避免意外的副作用。
SnackBar的使用技巧
SnackBar是Flutter中显示临时消息的组件,让我们深入了解一下它的用法:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('词语不符合规则或已使用过')),
);
SnackBar可以自定义很多属性:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('提交成功'),
duration: Duration(seconds: 2), // 显示时长
backgroundColor: Colors.green, // 背景色
action: SnackBarAction( // 操作按钮
label: '撤销',
onPressed: () {
// 撤销操作
},
),
),
);
SnackBar会在屏幕底部显示,几秒后自动消失。如果在SnackBar显示期间又调用showSnackBar,新的SnackBar会排队等待,前一个消失后再显示。
可以手动隐藏SnackBar:
ScaffoldMessenger.of(context).hideCurrentSnackBar();
或者清空队列:
ScaffoldMessenger.of(context).clearSnackBars();
SnackBar是一种非侵入式的提示方式,不会打断用户的操作流程。相比Dialog,SnackBar更加轻量级,适合显示简短的提示信息。
游戏规则的扩展
当前的游戏规则比较简单,只要求首尾相接且不重复。可以添加更多规则增加游戏的趣味性:
词语长度限制:要求词语至少2个字,最多4个字。这可以避免玩家输入单字或过长的词语。
if (word.length < 2 || word.length > 4) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('词语长度必须在2-4个字之间')),
);
return;
}
时间限制:限制每个词语的思考时间,增加游戏的紧张感。
Timer? _timer;
int timeLeft = 30;
void _startTimer() {
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
if (timeLeft > 0) {
setState(() => timeLeft--);
} else {
timer.cancel();
// 游戏结束
}
});
}
词库验证:验证输入的词语是否在词库中,避免玩家随意输入。这需要维护一个词库,可以从文件或网络加载。
提示功能:当玩家想不出词语时,提供提示。可以显示以指定字开头的词语列表,或者给出词语的首字母。
这些扩展规则可以让游戏更加丰富有趣,但也会增加复杂度。需要根据目标用户群体来选择合适的规则。
数据持久化的实现
如果要保存游戏进度,可以使用shared_preferences:
import 'package:shared_preferences/shared_preferences.dart';
Future<void> _saveGame() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setStringList('words', words);
await prefs.setInt('score', score);
}
Future<void> _loadGame() async {
final prefs = await SharedPreferences.getInstance();
final savedWords = prefs.getStringList('words');
if (savedWords != null && savedWords.isNotEmpty) {
setState(() {
words.clear();
words.addAll(savedWords);
score = prefs.getInt('score') ?? 0;
});
}
}
在适当的时机调用这些方法,比如在词语提交后保存,在页面创建时加载。这样玩家就可以随时中断和继续游戏了。
还可以保存最高分记录:
Future<void> _saveHighScore() async {
final prefs = await SharedPreferences.getInstance();
final highScore = prefs.getInt('highScore') ?? 0;
if (score > highScore) {
await prefs.setInt('highScore', score);
}
}
这些持久化功能可以大大提升用户体验,让玩家的努力不会白费。
性能优化的考虑
单词接龙游戏的性能表现很好,因为游戏逻辑简单,UI更新频率不高。每次提交词语时,只有words列表和score会改变,Flutter会精确地重建受影响的Widget。
ListView.builder是懒加载的,只会渲染可见区域的词语。即使词语列表很长,也不会影响性能。这是Flutter的一个重要优化,让列表组件可以处理大量数据。
需要注意的是,字符串操作虽然简单,但如果频繁进行,也可能影响性能。比如在输入框的监听器中进行复杂的字符串处理,可能会导致卡顿。可以使用防抖(debounce)技术,延迟处理输入事件:
Timer? _debounce;
void _onTextChanged(String text) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(Duration(milliseconds: 500), () {
// 处理文本变化
});
}
这样可以避免在用户快速输入时频繁触发处理逻辑,提高性能。
总结
本文详细介绍了单词接龙游戏的实现。我们从游戏玩法设计开始,定义了游戏状态,实现了词语提交、验证和历史记录等核心功能,最后构建了游戏的UI。每个部分都有详细的代码和讲解,帮助你理解实现的原理。
单词接龙游戏涉及到文本处理和字符串匹配,通过实现这个游戏,你学习到了如何使用TextEditingController管理文本输入,如何使用SnackBar显示提示信息,如何使用ListView.builder显示历史记录。我们还深入探讨了字符串操作、游戏规则扩展、数据持久化等高级话题。
这些技能不仅适用于单词接龙游戏,也适用于其他需要文本输入和处理的应用。TextEditingController是Flutter中处理文本输入的核心类,掌握它是开发表单、搜索等功能的基础。字符串操作在很多场景中都会用到,了解Dart的字符串API很重要。
接下来的文章中,我们会继续实现其他游戏。每个游戏都有不同的玩法和实现方式,通过学习这些游戏,你将掌握更多的开发技巧,成为一名优秀的Flutter开发者。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)