持续创作,加速成长!这是我参与「掘金日新计划 · 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
) 有如下六种。可能很多人分不清什么是生命周期,什么是生命周期回调。
生命周期,本质上是一种 状态
,也就是说它是一种数据;而生命周期回调是一个函数,或说方法,一般来说该函数会在状态切换时触发,从而让外界可以感知到对象的状态变化,以此实现某些特定的逻辑。 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
方法,所以这两者的先后关系是很明确的。
如下是着六个回调方法顺序的简单示意,其中 update
和 render
方法是在 Ticker
循环中不断触发的,当 Ticker
停止时,这两个方法也会停止回调。另外当该组件被移除之后,也不会继续回调update
和 render
。
5. 运动圆
下面通过一个小案例来梳理一下 Component
的生命周期回调。如下,小圆不停运动,在碰到桌面后反弹,代码详见 【10/01】
https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/684f1d0d56234ad4b37534b8ae1860a6~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
在 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);
}
复制代码
在 update
中,根据运动学格式,在 dt
的时间内,更新速度和位移的值,小球即可运动。另外小球的碰壁反弹可以通过位置校验来处理 ,Flame 中有对于碰撞的简单封装,但这里还是自己手动校验,体会一下简单的配置检测。
代码语言:javascript复制@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】
https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5aa673fc270143a7b1c4b6d6dd17ecfe~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
代码语言:javascript复制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
监听到事件:
到这里,我们就对 Flame
中 Component
的生命周期回调有了较深的理解。这个知识点是非常重要的,希望大家可以好好消化吸收。那本文就到这里,明天见 ~
@张风捷特烈 2022.06.04 未允禁转
我的 掘金主页
: 张风捷特烈我的 B站主页
: 张风捷特烈我的 github 主页
: toly1994328