【Flutter&Flame 游戏 - 贰肆】pinball 源码分析 - 项目结构介绍.md

2022-08-31 14:52:34 浏览数 (1)

前言

这是一套 张风捷特烈 出品的 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 游戏 - 贰贰】菜单、字体和浮层
  • 【Flutter&Flame 游戏 - 贰叁】 资源管理与国际化
  • 【Flutter&Flame 游戏 - 贰肆】pinball 源码分析 - 项目结构介绍
  • 【Flutter&Flame 游戏 - 贰伍】pinball 源码分析 - 资源加载与 Loading(本文)
  • 未完待续 ~

1. 加载界面在哪里

pinball 在游戏开始时,会显示资源加载的界面,是一个加载的进度条,如下所示。那问题来了,如何定位这个界面在源码中的位置。这样才能有机会分析资源加载的代码:


从一些外在表征去定位源码,是一个非常有用手段,比如在资源文件中可以看出 loading_game 的文件夹,其中包含着 io_pinball.png 的图片。这就说明只要找到什么地方使用了 io_pinball.png,就可以发现相关视图处理的代码逻辑


全局搜索一下,就不难发现,该图片名称在 lib/gen/assets.gen.dart 中被使用:

点进去可以看到该文件是通过工具自动生成的资源管理代码,ioPinball 代表这个图片资源:


然后 顺藤摸瓜 ,就可以找到图片资源使用的场景,这就像根据线索来探查真相。现在知道一开始加载的界面的代码在 lib/assets_manager/views/assets_loading_page.dart 中。这样我们就能通过源码来分析一下界面实现的逻辑,包括界面如何布局,进度如何变化等。


2. 加载界面 - 背景与图片

加载中的布局主要右四个部分组成,分别是 背景图片Loading 文字以及 进度条

在上面可以看出,图片本身背景是透明的,所以背景中的横线条纹在源码中一定有其出处:从界面组件 AssetsLoadingPage 的实现中可以看出,背景是通过 CrtBackground 装饰进行绘制的。其实这里的 Container 没有使用其他属性完全可以换成 DecoratedBox 组件,更加轻便:

这里的选择是自定义子类集成自 BoxDecoration,感觉并没有太大的必要性。直接使用 BoxDecoration 指定 gradient 参数就行了,不过也无伤大雅。

代码语言:javascript复制
class CrtBackground extends BoxDecoration {
  /// {@macro crt_background}
  const CrtBackground()
      : super(
          gradient: const LinearGradient(
            begin: Alignment(1, 0.015),
            stops: [0.0, 0.5, 0.5, 1],
            colors: [
              PinballColors.darkBlue,
              PinballColors.darkBlue,
              PinballColors.crtBackground,
              PinballColors.crtBackground,
            ],
            tileMode: TileMode.repeated,
          ),
        );
}
复制代码

这里的图片组件是通过 ioPinball 对象调用 image() 方法获取的,其实这就是自动生成的代码给的一个形式语法糖。

代码语言:javascript复制
Padding(
  padding: const EdgeInsets.symmetric(horizontal: 20),
  child: Assets.images.loadingGame.ioPinball.image(),
),
复制代码

ioPinball 类型为 AssetGenImage ,是一个 ImageProvider ,也就是 Image 组件在需要传入的 image 参数类型。该类中的 image 方法,提供了构造 Image 所需的可选属性,返回 Image 组件。并将自身作为构造 Image 组件的 image 入参:

也就是说,下面的两种写法是等价的,只不过上面在的写法不需要嵌入在 Image 组件下而已。本质上没有什么区别,只是简化书写形式而已:

代码语言:javascript复制
Assets.images.loadingGame.ioPinball.image()
等价于
Image(image:Assets.images.loadingGame.ioPinball)
复制代码

3. 加载界面 - 加载中文字与指示器

如下所示: Loading 文字三个点会依次出现,是个循环动画。另外加载进度通过下面的指示器来显示,整个加载中界面的 业务逻辑 只有一个: 加载进度值的计算。


Loading... 的循环动画是通过 AnimatedEllipsisText 组件实现的,这个组件感觉挺实用。如果以后有需要,可以直接拷贝过去用,这就是 Flutter 组件化的好处。

简单瞄一眼源码,这里 ... 不断运动的动画,是通过 Timer.periodic 周期触发定时器实现的,每 500 ms 触发一次更新。由于这里是单独抽离的 AnimatedEllipsisText ,所以 setState 也只是局部的组件更新,不会影响触发外界组件的重新构建。


最后,是加载页最核心的业务逻辑,该项目是通过 flutter_bloc 来进行状态管理的。这里使用 AssetsManagerCubit 来维护加载资源的逻辑,其中状态数据是 AssetsManagerState ,该状态量可以获取加载的进度。这里通过 BlocBuilder 来监听状态的变化来构建组件。

从代码中可以看出,这个像素风格的进度条,通过 PinballLoadingIndicator 组件进行显示。构造中传入进度值,红色的区域就会占据相应的百分比。


PinballLoadingIndicator 组件的源码实现中可以看出,这个像素风格的进度条是通过六个 _InnerIndicator 组件进行显示的。仔细数一下上图,就会发现整体是由六个细条,从上到下排列的而构成的。


4. 资源加载的业务逻辑

上面我们知道,资源加载的核心逻辑以及过程中的进度状态数据,是由 AssetsManagerCubit 进行维护的。如下,在 lib/assets_manager 文件夹中管理着资源加载的 bloc 业务逻辑和 views 视图:

下面我们就进入 AssetsManagerCubit ,来看一下资源是如何加载的,以及进度状态的产出。AssetsManagerCubit 构造时需要传入如下两个对象,其中只有一个 load 异步方法,本身还是比较简单的。


这个 load 方法,会在 AssetsManagerCubit 构造时被立刻触发:


load 方法在有一个小细节,一开始延迟了一秒钟才开始真正加载,这是因为加载是个昂贵的操作,先给出 1s 的时间,让 UI 先展示出来,然后再真正进行加载资源。这里加载资源的异步任务通过 loadables 列表进行维护:


异步操加载资源的任务,被定义在个个模块中。比如 _game.preLoadAssets() 方法,会返回所有构件图片资源加载的异步方法,其他几个也是类似。当你看到源码的这么多资源加载的异步方法,就会明白为什么这个 load 会是昂贵的。


然后通过 _triggerLoad 局部函数对象,分三波依次触发这些异步任务。每次异步任务完成时,都会产出新的状态,让已加载的资源数加一。


这样状态数据中的进度值 progress 就会变化,整个加载的小体系就得以运转,从业务逻辑到视图更新展示,可以体会一下,bloc 在其中的角色,品味一下状态管理的价值。

到这里,pinball 首次进入时资源加载,以及进度的显示流程就介绍完毕了。那本文就到这里,明天见 ~

0 人点赞