【Flutter&Flame 游戏 - 贰伍】pinball 源码分析 - 资源加载与 Loading

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


前言

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


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 参数就行了,不过也无伤大雅。

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() 方法获取的,其实这就是自动生成的代码给的一个形式语法糖。

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

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

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

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 首次进入时资源加载,以及进度的显示流程就介绍完毕了。那本文就到这里,明天见 ~

\

猜你喜欢

转载自juejin.im/post/7111492756870004772