【Flutter&Flame游戏 - 拾】探索构件 Co

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


前言

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

第一季完结,谢谢支持 ~


1. Component 生命周期回调一览

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

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

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

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

2. onGameResize 和 onLoad

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


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


3. onMount 和 onRemove

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


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


4. update 和 render

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


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


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


5. 运动圆

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
dart复制代码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 方法中进行绘制圆:

1
2
3
4
5
6
dart复制代码@override
void render(Canvas canvas) {
super.render(canvas);
canvas.translate(size.x / 2, size.y / 2);
canvas.drawCircle(Offset.zero, size.x / 2, _paint);
}


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

速度的合成.png

碰撞分析png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
dart复制代码@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】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
dart复制代码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 监听到事件:


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

\

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%