持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 23 天,点击查看活动详情
前言
这是一套 张风捷特烈 出品的 Flutter&Flame
系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列源码于 【toly_game】 ,如果本系列对你有所帮助,希望点赞支持,本系列文章一览:
- 【Flutter&Flame 游戏 - 壹】开启新世界的大门
- 【Flutter&Flame 游戏 - 贰】操纵杆与角色移动
- 【Flutter&Flame 游戏 - 叁】键盘事件与手势操作
- 【Flutter&Flame 游戏 - 肆】精灵图片加载方式
- 【Flutter&Flame 游戏 - 伍】Canvas 参上 | 角色的血条
- 【Flutter&Flame 游戏 - 陆】暴击 Dash | 文字构件的使用
- 【Flutter&Flame 游戏 - 柒】人随指动 | 动画点触与移动
- 【Flutter&Flame 游戏 - 捌】装弹完毕 | 角色武器发射
- 【Flutter&Flame 游戏 - 玖】探索构件 | Component 是什么
- 【Flutter&Flame 游戏 - 拾】探索构件 | Component 生命周期回调
- 【Flutter&Flame 游戏 - 拾壹】探索构件 | Component 使用细节
- 【Flutter&Flame 游戏 - 拾贰】探索构件 | 角色管理
- 【Flutter&Flame 游戏 - 拾叁】碰撞检测 | CollisionCallbacks
- 【Flutter&Flame 游戏 - 拾肆】碰撞检测 | 之前代码优化
- 【Flutter&Flame 游戏 - 拾伍】粒子系统 | ParticleSystemComponent
- 【Flutter&Flame 游戏 - 拾陆】粒子系统 | 粒子的种类
- 【Flutter&Flame 游戏 - 拾柒】构件特效 | 了解 Effect 体系
- 【Flutter&Flame 游戏 - 拾捌】构件特效 | ComponentEffect 一族
- 【Flutter&Flame 游戏 - 拾玖】构件特效 | 了解 EffectController 体系
- 【Flutter&Flame 游戏 - 贰拾】构件特效 | 其他 EffectControler
- 【Flutter&Flame 游戏 - 贰壹】视差组件 | ParallaxComponent
- 【Flutter&Flame 游戏 - 贰贰】菜单、字体和浮层
未完待续
~
1.在游戏中添加菜单组件
一般来说,休闲游戏并不会打开时立即进入游戏。会有一个菜单界面,让用户选择开始游戏,或通过设置按钮来打开配置界面,对游戏进行设置。而我们知道,Flame
的 “世界”
是通过 Ticker
不断触发更新的,但往往菜单是 静态
的,不需要一直更新。所以可以使用 Flutter
原生的组件来做菜单,再加上界面跳转也需要原生的路由。
其实本质上来说,Flame
所呈现的游戏界面也只是一个 Widget
而已,我们可以一视同仁。比如下面定义两个 GameWorld
组件,来表示游戏世界: 【22/01】
class GameWorld extends StatelessWidget {
const GameWorld({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GameWidget(game: TolyGame());
}
}
复制代码
由于需要界面路由跳转,所以这里使用 MaterialApp
,其内部集成了路由体系。并且这里使用 navigatorKey
,便于在无上下文的情况下,获取导航状态。
class GameApp extends StatelessWidget {
const GameApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: Keys.navKey,
themeMode: ThemeMode.dark,
darkTheme: ThemeData(brightness: Brightness.dark),
home: const MainMenu(),
);
}
}
class Keys {
Keys._();
static GlobalKey<NavigatorState> navKey = GlobalKey(debugLabel: 'navKey');
static NavigatorState? get navigator => navKey.currentState;
}
复制代码
比如现在先给个简单的菜单界面,如下所示,一个名字文本,两个按钮:
如下所示,定义一个 Flutter
常规的 MainMenu
组件,对内容进行展示即可,代码如下。其中 开始
按钮通过 Keys
中的 navKey
获取导航栏状态,通过 pushReplacement
方法,跳转到 GameWorld
游戏界面,并将当前的 MainMenu
界面弹栈。
class MainMenu extends StatelessWidget {
const MainMenu({Key? key}) : super(key: key);
final TextStyle shadowStyle = const TextStyle(
fontSize: 30,
shadows: [Shadow(color: Colors.white,blurRadius: 10)]
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Wrap(
spacing: 20,
direction: Axis.vertical,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text('Adventurer', style: shadowStyle,),
ElevatedButton(onPressed: _doPlay, child: const Text('开 始')),
ElevatedButton(onPressed: _toOptions, child: const Text('设 置'))
],
),
),
);
}
void _doPlay() {
Keys.navigator?.pushReplacement(
MaterialPageRoute(builder: (ctx) => const GameWorld()),
);
}
void _toOptions() {}
}
复制代码
2. 字体下载
有人觉得默认字体可能并不是很好看,想要引入别的字体,但很多字体不可以商用。其实google_fonts
中提供了大量可以商用的字体,我们可以在 fonts.google.com/ 中进行挑选。
在某个字体的 License
中,可以瞄一眼,比如 Ma Shan Zheng
是允许在- 项目-印刷或数字,商业或其他场景使用的。
点击下载,在 OFL
中也可以看到,字体证书是 STL
,允许商用:
你可以通过 线上
和 本地
两种方式来加载字体。线上加载,可以使用 google_fonts
的字体库,所有的字体样式都可以通过 GoogleFonts
类通过静态方法获取,使用时会自动下载字体。
线上的缺点是必须依赖网络,而且需要下载时间,对于很大的字体,首次下载时间比较长,突然的字体改变,体验并不是很好。可以把字体下载到本地,这样就没有延迟的风险,而且在没有网络的情况下也能使用,缺点是会增加应用体积,大家可以酌情选择。本地字体使用也非常方便,只需要引入,在 pubspec.yaml
的 fonts
节点下引入即可:
如果想要指定全局字体,可以在主题数据 ThemeData
,指定对应的 fontFamily
:
class GameApp extends StatelessWidget {
const GameApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.dark,
darkTheme: ThemeData(
brightness: Brightness.dark,
fontFamily: 'ZCOOLKuaiLe' //<--- 指定字体
),
home: const MainMenu(),
);
}
}
复制代码
这样就可以对应用的 Text
组件的字体进行统一设置,效果如下:
3. 游戏的暂停和恢复
我们知道 Falme
中通过 GameLoop
维护一个持续触发的 Ticker
用于游戏的渲染更新。当然,游戏中也需要要有暂停和恢复的方法,如下案例中,通过按下空格键来切换游戏状态:
https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bf40900125e6480ca6334a7e09089adf~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
在 Game
类中提供了 resumeEngine
和 pauseEngine
两个方法,用于恢复和暂停游戏。此外 paused
属性可以得知游戏是否已经停止。由于 FlameGame
混入了 Game
,所以它有这些方法,如果在其他的构件中希望暂停或恢复游戏,可以通过混入 HasGameRef
,来得到 gameRef
对象触发这些方法。
void toggleGameState(){
if(paused){
resumeEngine();
}else{
pauseEngine();
}
}
复制代码
4. 在 Flame 中展示浮层
有时我们有显示浮层的需求,比如暂停游戏时,显示暂停面板。不然用户不小心碰到了暂停键,有可能不知所措,显示一个浮层界面可以更好的引导交互。如下所示,在点击空格键时,显示浮层:代码详见 【22/02】
使用浮层需要三步:
- 1.创建浮层中的
内容组件
这里和开始菜单类似,就不贴代码了,详见源码。在其中定义了 Game
成员,在构造方法中初始化,这是为了方便在 PauseMenu
的继续按钮触发时,调用引擎的相关方法,继续游戏。当然,你也可以把事件回调出去,让使用者处理,其实都差不多,酌情考量即可。
另外,定义了一个 menuId
的静态常量,为了方便标识这个菜单,而不是在每处使用时,都写一个死的字符串。
- 2.通过
GameWidget
的overlayBuilderMap
参数指定浮层id
和组件内容
的映射关系:
- 3.通过
浮层id
开启或隐藏浮层,其中overlays
是Game
中的公开成员:
本文介绍了,如何在 Flame
游戏中,让 Flutter
原生的组件发挥价值。其实 Flame
是在 Flutter
中的,你可以随时随地,使用 Flutter
中的任何知识。并没有必要把 Flame
和 Flutter
进行割裂,Flutter
的基础设施仍然可以使用,比如国际化、主题切换、状态管理等等。
@张风捷特烈 2022.06.17 未允禁转
我的 掘金主页
: 张风捷特烈我的 B站主页
: 张风捷特烈我的 github 主页
: toly1994328