持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 28 天,点击查看活动详情
这是一套 张风捷特烈 出品的 Flutter&Flame
系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列源码于 【toly_game】和 【pinball】 ,如果本系列对你有所帮助,希望点赞支持,本系列文章一览:
本文
)未完待续
~上一篇介绍了主界面布局结构的源码实现,本篇继续来看。在点击 Play
按钮之后,背景的游戏界面会有一个移动和缩放的特效,让游戏主题面板填充屏幕。并且会展示角色选择的面板,效果如下:
*注*
:为了减少 gif
的大小,生成时采用 6fps
,所以实际效果要你下图流畅很多。
在选择对应的角色图标时,背景会进行对应的变化。很明显,在两个不同界面中的数据需要共享,很自然就会想到使用 状态管理
。
通过查看资源图片的位置,不难发现,这里四种角色主题是在 pinball_theme
中提供的。也就是说,pinball
项目中,对这个小模块进行了分包处理。可以思考一下:这个模块是相对独立的,而且有一定的拓展需求,比如增加其他的角色,可以在该包在进行处理。而不是全部塞在主项目中,这样有利于对项目结构的划分,也能让读者更容易理解。
不过仔细看一下这个包,会发现其中只是定义主题的数据,被抽象为 CharacterTheme
,四种主题便是 CharacterTheme
的实现类。
比如下面的 AndroidTheme
继承自 CharacterTheme
,实现相关资源的 get
方法,其他的主题也是类似。也就是说,如果需要增加一个主题角色,可以自定义 CharacterTheme
的实现类。
从上面可以看出,pinball_theme
包中,只是定义主题的数据类型,并未涉及业务逻辑
与 视图变化
。跟随 CharacterTheme
这条显示,很容易可以定位到它的使用场景。如下,在主项目的 select_character
文件夹下,盛放着角色选择的相关文件。cubit
是业务逻辑的处理,view
是视图的呈现。
可以看出 CharacterThemeCubit
非常简单,维护着 CharacterThemeState
状态数据,只有一个 characterSelected
事件,根据入参选择的角色,产出新的状态数据。其中 CharacterThemeState
状态数据也非常简单,只维护了一个 CharacterTheme
成员和四个 get
方法方便获取激活的主题。
默认情况是 DashTheme
,想要知道角色主题是何时切换的,也非常简单。因为使用了Bloc
,业务逻辑封装了,使用统一的事件接口触发。这里只需看一下何时触发 characterSelected
方法即可。这就是业务分层所带来的的好处之一。
如下,在 _Character
组件点击时,是唯一触发 characterSelected
的场景。这里的 _Character
组件就是右边可点击的圆形角色头像,其中需要传入 CharacterTheme
对象和 isSelected
是否被选中。从 build
逻辑中可以看出:选中时,透明度是 1
,否则是 0.4
,这和实际操作是吻合的。
角色选择的界面主体内容是中间的 Row
,包含 _CharacterPreview
和 _CharacterGrid
组件,且平分水平方向空间。
_CharacterPreview
组件是角色的预览,它会随着右侧角色的选择而发生变化。所以这里通过 BlocBuilder
让组件跟随 CharacterThemeState
的变化而重新构建。这里对 SelectedCharacter
组件进行抽离,因为其内部需要进行动画,封装之后独立性较好,表意也更明确。
这里通过 SelectedCharacter
组件进行展示某个角色,主要就是一个 Column
的上下结构。下方不断运动的序列帧通过 SpriteAnimationWidget
进行展示。这样选择角色的界面和处理逻辑就介绍地差不多了,至于背景的贴图如何变化的,在后面分析主场景再进行介绍。
其实如果不创建 _CharacterPreview
,直接在 SelectedCharacter
中使用 BlocBuilder
,或直接在 定义一个方法
返回 _CharacterPreview
中组件,在功能上是没有什么区别的。好处是少了一个类,坏处是看起来代码比较杂糅,表意性不是太好。这也没有什么定式,大家根据自己的喜好,斟酌选择即可。
程序的最终目的是实现需求,像先迈左脚,还是右脚;用袋子装鸡蛋,还是用篮子装鸡蛋;喝可乐开还喝绿茶;这并不会影响最终的目的。结合场景和个人的喜好即可,并没有必要强制必须如何如何。没必要画个圈,或让别人给你画个圈,把自己的行动范围定死,这点思考和选择的能力还是要有的。
在选择完角色之后,会弹出 How to Play
的面板,介绍玩法。可以看出这个对话框的整体结构和上面角色选择是一致的,这个对话框是源码中的 PinballDialog
组件。
玩法介绍地面板,是的 HowToPlayDialog
组件呈现的,他是一个 StatefulWidget
。因为其中有一个自动消失的需求,如红框所示,通过 closeTimer
开启一个 3 s
的延迟任务,来让对话框消失。
对话框界面的构建逻辑如下,显示的主体是 PinballDialog
对话框,对话框的内容会根据 是否是移动端
进行适配。原因很简单,移动端通过点击屏幕,桌面端通过按键触发事件 ,玩法是有区别的。
代码中对界面的分层处理是很值得借鉴的,而不是把所有的构建逻辑写在一块。抽离组件可以让整体结构更加清晰,比如下面的红框中,代码的组件和界面的呈现,两者的对应关系非常清晰。源码中的处理方式,特别是官方提供的源码,是非常值得学习和借鉴的。能将这些思想消化吸收,应用到实际开发中,是有益的。
最后,来看一下 HowToPlayDialog
是如何显示出来的,也就是触发的时机。查看一些 HowToPlayDialog
组件的使用情况,很容易可以定位到 start_game_listener.dart
中。
在 _onHowToPlay
私有方法中,进行展示 HowToPlayDialog
对话框。所以关键就是该方法的触发时机:
在 StartGameListener
中,会监听 StartGameState
状态的变化,如果是 howToPlay
状态,则会触发 _onHowToPlay
方法,显示玩法对话框。从这里可以看出 Bloc
处理可以根据状态来构建组件,也可以监听状态的变化,进行逻辑处理。
在 StartGameBloc
中,CharacterSelected
事件会将状态值变为 howToPlay
。另外在选中角色后,会触发 CharacterSelected
事件,这就是HowToPlayDialog
对话框显示的整体逻辑。
本文介绍了 pinball
游戏的角色选择
和玩法介绍
两个模块。从中可以看出 bloc
在状态数据共享,以及状态变化监听中的价值。下一篇,我们将进入最重要的游戏主界面,那本文就到这里,明天见 ~
@张风捷特烈 2022.06.23 未允禁转
我的 公众号: 编程之王
我的 掘金主页
: 张风捷特烈我的 B站主页
: 张风捷特烈我的 github 主页
: toly1994328