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


前言

这是一套 张风捷特烈 出品的 Flutter&Flame 系列教程,发布于社区。如果你在其他平台看到本文,可以根据对于链接移步到中查看。因为文章可能会更新、修正,一切以文章版本为准。本系列文章一览:

  • 【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. Component 生命周期回调一览

所谓生命周期,就是一个对象从生到死的过程。在上一篇中介绍过 Component 的生命周期状态 (LifecycleState) 有如下六种。可能很多人分不清什么是生命周期,什么是生命周期回调。

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调

生命周期,本质上是一种 状态 ,也就是说它是一种数据;而生命周期回调是一个函数,或说方法,一般来说该函数会在状态切换时触发,从而让外界可以感知到对象的状态变化,以此实现某些特定的逻辑。 Component 中的生命周期回调方法如下:

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调

一般来说,常用的是如下六个回调,先简单认识一下:

  • onGameResize : 顶层画布尺寸变化时
  • onLoad:资源加载时
  • onMount:添加到父节点时
  • onRemove:从父节点移除时
  • update:跟随 Ticker 不断触发
  • render:新帧渲染时触发

2. onGameResize 和 onLoad

如下可以看出,在生命周期状态从 uninitialized 切换到 loading 时,会触发一次 onGameResize;紧接着触发 onLoad 异步方法。在 483 行所示,异步任完成后,生命周期状态将置为 loaded

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


如下通过断点查看一下自定义的 Ball 组件 onLoad 方法触发时,方法栈的情况。可见在 Flutter 程序的开始,BuildOwner#buildScope 构建组件时, _GameWidgetState 会触发 loaderFuture 的方法。在父构件执行 add 方法,会先触发该子构建的 onLoad 方法来加载资源。可就是说,通过这个回调,可以给构件准备资源的机会。

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


3. onMount 和 onRemove

这两个是一对反义词,onMount 方法在生命周期状态变为 mounted 之前触发。让使用者知道该构件节点添加到构件树的确切时机。

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


当某个组件被父节点踢出群聊时,会触发onRemove 方法,之后紧接着将生命周期状态置为 removed 。让使用者知道该构件节点添加到构件树的确切时机。

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


4. update 和 render

前面我们对这两个方法已经有所了解,这两者都是一个持续不断的回调,一般每隔 16.66ms 触发一次,也就是一秒钟触发 60 次 。update 方法本质上由 Ticker 触发,这点可以通过断点调试进行应证,如下所示:

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


render 方法本质上是在帧绘制期间被触发的,也就是 RendererBinding.drawFrame 方法。这个看过 《Flutter 渲染机制 – 聚沙成塔》 的朋友对这些应该比较熟悉,没看过也没有关系。 Ticker 触发新帧的申请,回调 update 方法,在新帧来临是触发 drawFrame 方法,回调 render 方法,所以这两者的先后关系是很明确的。

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


如下是着六个回调方法顺序的简单示意,其中 updaterender 方法是在 Ticker 循环中不断触发的,当 Ticker 停止时,这两个方法也会停止回调。另外当该组件被移除之后,也不会继续回调updaterender

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


5. 运动圆

下面通过一个小案例来梳理一下 Component 的生命周期回调。如下,小圆不停运动,在碰到桌面后反弹,代码详见 【10/01】

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


onLoad 方法中,可以对画笔、位置、速度、加速度等属性进行初始化

final Paint _paint = Paint()
  ..style = PaintingStyle.stroke
  ..strokeWidth = 1;
Vector2 v = Vector2.zero(); // 速度 px/s
Vector2 a = Vector2.zero(); // 加速度 px/s^2
@override
Future<void> onLoad() async {
  _paint.color = color;
  position = gameRef.size / 2;
  v = Vector2(80, 50);
}

render 方法中进行绘制圆:

@override
void render(Canvas canvas) {
  super.render(canvas);
  canvas.translate(size.x / 2, size.y / 2);
  canvas.drawCircle(Offset.zero, size.x / 2, _paint);
}

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


update 中,根据运动学格式,在 dt 的时间内,更新速度和位移的值,小球即可运动。另外小球的碰壁反弹可以通过位置校验来处理 ,Flame 中有对于碰撞的简单封装,但这里还是自己手动校验,体会一下简单的配置检测。

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调

@override
void update(double dt) {
  super.update(dt);
  v += a * dt;
  position += v * dt;
  Vector2 winSize = gameRef.size;
  //限定下边界
  if (position.y > winSize.y - size.y/2) {
    position.y = winSize.y - size.y/2;
    v.y = -v.y;
  }
  //限定上边界
  if (position.y < size.y/2) {
    position.y = size.y/2;
    v.y = -v.y;
  }
  //限定左边界
  if (position.x < size.x/2) {
    position.x = size.x/2;
    v.x = -v.x;
  }
  //限定右边界
  if (position.x > winSize.x - size.x/2) {
    position.x = winSize.x - size.x/2;
    v.x = -v.x;
  }
}

下面可以继续拓展,比如在点击屏幕时添加通过 Ball ,双击屏幕时移除 Ball 列表的第一个。效果如下,代码详见 【10/02】

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调

class TolyGame extends FlameGame with TapDetector,DoubleTapDetector{
  int _counter = 0;
  @override
  Future<void> onLoad() async {
    addABall();
  }
  void addABall(){
    Ball ball = Ball(tag: 'tag$_counter');
    add(ball);
    _counter++;
  }
  @override
  void onTap() {
    addABall();
  }
  @override
  void onDoubleTap() {
   List<Ball> balls = children.whereType<Ball>().toList();
   if(balls.isNotEmpty){
     balls.first.removeFromParent();
   }
  }
}

这样在移除时 Ball 自身可以通过 onRemove 监听到事件:

【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调


到这里,我们就对 FlameComponent 的生命周期回调有了较深的理解。这个知识点是非常重要的,希望大家可以好好消化吸收。那本文就到这里,明天见 ~

  • @张风捷特烈 2022.06.04 未允禁转
  • 我的 公众号: 编程之王
  • 我的 主页 : 张风捷特烈
  • 我的 B站主页 : 张风捷特烈
  • 我的 github 主页 : toly1994328