【Flutter&Flame 游戏 - 贰柒】pinball 源码分析 - 角色选择与玩法面板

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 28 天,点击查看活动详情


前言

这是一套 张风捷特烈 出品的 Flutter&Flame 系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列源码于 【toly_game】【pinball】 ,如果本系列对你有所帮助,希望点赞支持,本系列文章一览:


1. 选择角色介绍

上一篇介绍了主界面布局结构的源码实现,本篇继续来看。在点击 Play 按钮之后,背景的游戏界面会有一个移动和缩放的特效,让游戏主题面板填充屏幕。并且会展示角色选择的面板,效果如下:
*注* :为了减少 gif 的大小,生成时采用 6fps ,所以实际效果要你下图流畅很多。


在选择对应的角色图标时,背景会进行对应的变化。很明显,在两个不同界面中的数据需要共享,很自然就会想到使用 状态管理


通过查看资源图片的位置,不难发现,这里四种角色主题是在 pinball_theme 中提供的。也就是说,pinball 项目中,对这个小模块进行了分包处理。可以思考一下:这个模块是相对独立的,而且有一定的拓展需求,比如增加其他的角色,可以在该包在进行处理。而不是全部塞在主项目中,这样有利于对项目结构的划分,也能让读者更容易理解。

不过仔细看一下这个包,会发现其中只是定义主题的数据,被抽象为 CharacterTheme,四种主题便是 CharacterTheme 的实现类。


比如下面的 AndroidTheme 继承自 CharacterTheme ,实现相关资源的 get 方法,其他的主题也是类似。也就是说,如果需要增加一个主题角色,可以自定义 CharacterTheme 的实现类。


2. 角色选择的业务逻辑

从上面可以看出,pinball_theme 包中,只是定义主题的数据类型,并未涉及业务逻辑 视图变化 。跟随 CharacterTheme 这条显示,很容易可以定位到它的使用场景。如下,在主项目的 select_character 文件夹下,盛放着角色选择的相关文件。cubit 是业务逻辑的处理,view 是视图的呈现。

可以看出 CharacterThemeCubit 非常简单,维护着 CharacterThemeState 状态数据,只有一个 characterSelected 事件,根据入参选择的角色,产出新的状态数据。其中 CharacterThemeState 状态数据也非常简单,只维护了一个 CharacterTheme 成员和四个 get 方法方便获取激活的主题。


默认情况是 DashTheme ,想要知道角色主题是何时切换的,也非常简单。因为使用了Bloc ,业务逻辑封装了,使用统一的事件接口触发。这里只需看一下何时触发 characterSelected 方法即可。这就是业务分层所带来的的好处之一。


3.角色选择的视图变化

如下,在 _Character 组件点击时,是唯一触发 characterSelected 的场景。这里的 _Character 组件就是右边可点击的圆形角色头像,其中需要传入 CharacterTheme 对象和 isSelected 是否被选中。从 build 逻辑中可以看出:选中时,透明度是 1 ,否则是 0.4 ,这和实际操作是吻合的。


角色选择的界面主体内容是中间的 Row ,包含 _CharacterPreview_CharacterGrid 组件,且平分水平方向空间。


_CharacterPreview 组件是角色的预览,它会随着右侧角色的选择而发生变化。所以这里通过 BlocBuilder 让组件跟随 CharacterThemeState 的变化而重新构建。这里对 SelectedCharacter 组件进行抽离,因为其内部需要进行动画,封装之后独立性较好,表意也更明确。


这里通过 SelectedCharacter 组件进行展示某个角色,主要就是一个 Column 的上下结构。下方不断运动的序列帧通过 SpriteAnimationWidget 进行展示。这样选择角色的界面和处理逻辑就介绍地差不多了,至于背景的贴图如何变化的,在后面分析主场景再进行介绍。

其实如果不创建 _CharacterPreview ,直接在 SelectedCharacter 中使用 BlocBuilder ,或直接在 定义一个方法 返回 _CharacterPreview 中组件,在功能上是没有什么区别的。好处是少了一个类,坏处是看起来代码比较杂糅,表意性不是太好。这也没有什么定式,大家根据自己的喜好,斟酌选择即可。

程序的最终目的是实现需求,像先迈左脚,还是右脚;用袋子装鸡蛋,还是用篮子装鸡蛋;喝可乐开还喝绿茶;这并不会影响最终的目的。结合场景和个人的喜好即可,并没有必要强制必须如何如何。没必要画个圈,或让别人给你画个圈,把自己的行动范围定死,这点思考和选择的能力还是要有的。


4. 玩法介绍面板

在选择完角色之后,会弹出 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 在状态数据共享,以及状态变化监听中的价值。下一篇,我们将进入最重要的游戏主界面,那本文就到这里,明天见 ~

\

猜你喜欢

转载自juejin.im/post/7112232793916047374