
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 10 天,点击查看活动详情
这是一套 张风捷特烈 出品的 Flutter&Flame 系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列文章一览:
未完待续 ~通过前面八篇的尝鲜,或说预热,我们可以感知到无论是主角、怪兽、文字、子弹、触点都是 Component 。它是游戏的基本构建模块,可以表示任何需要被渲染、更新的内容。

下面是 Component 类的部分结构,可以看出 Component 是一个普通类。其本身会持有父级构件,以及子级构件集合。也就是说 Component 本身是一个树形结构的节点类,认识到这一点至关重要。

正是由于树形结构的特点,Component 类有添加和 移除 构件的能力。如下所示,可以通过 add 方法添加子级构件,也可以通过 addToParent 方法,将自身添加到父级构件中。

前面介绍过子弹、怪物消失,使用的是 removeFromParent 方法。如下源码中可以看出,是调用父级构件 _parent 的移除方法,把当前构件对象从父级节点上移除:
---->[Component#removeFromParent]----
/// Remove the component from its parent in the next tick.
void removeFromParent() {
_parent?.remove(this);
}
复制代码Component 中有一个 _state 属性,其类型为 LifecycleState 枚举,用于表示构件的状态:

其中有如下 6 种状态,初始状态是 uninitialized ,表示未初始化,也就是构件实例化时的默认状态。前面知道构件中有个 onLoad 的异步方法用于加载资源,在执行异步方法的前一刻就是 loading 状态。该状态会持续到异步方法执行完毕,变成 loaded 状态。
enum LifecycleState {
uninitialized,
loading,
loaded,
mounted,
removing,
removed,
}
复制代码Component 是树形结构的节点,当某个 Component 添加到父节点上后,就会变成 mounted 状态。相关代码如下所示:

另外当父级执行 remove 方法时,入参的子构件非 removing 状态时,会被加入到 lifecycle._removals 列表中,等待下帧触发时移除。此时该子构件的状态为 removing 。当构件被从父节点上移除后,其状态为 removed ,就变成了孤魂野鬼,等待被 GC 回收。

如下图是六种状态的转换示意图,其实还是比较清晰的。了解这六种状态,在下篇介绍 Component 生命周期方法时,就会更好理解。

另外 Component 中关于生命周期状态有三个 get 方法,这里介绍一下:
uninitialized 且非 loading 状态,表示异步加载任务是否已经完成。mounted 或 removing 状态,表示构件依然在树上。removing 状态,表示构件已被收集到移除列表中,将在下一帧中被移除。---->[Component]----
bool get isLoaded {
return (_state != LifecycleState.uninitialized) &&
(_state != LifecycleState.loading);
}
bool get isMounted {
return (_state == LifecycleState.mounted) ||
(_state == LifecycleState.removing);
}
bool get shouldRemove => _state == LifecycleState.removing;
复制代码在 Flame 的 components 包中的文件,是对 Component 的衍生。其中一些 mixin ,比如 Draggable 、Hoverable 、Tappable 等都是依赖于 Component ,情理上来说也算是 Component 的衍生产物。

Component 大致可分为三大类,支持定位和变换的 PositionComponent 、附加效果的 Effect 、以及直接继承自 Component 的少数构建。

其中群体最庞大的是 PositionComponent ,这一族引入了 尺寸 、锚点、位置 、旋转 、缩放 等属性,决定了该族构件将非常实用:

我们之前用的 SpriteComponent 、TextComponent 、SpriteAnimationComponent 等都是 PositionComponent 一族的。另外,自定义的子弹、主角、怪兽,也都是 PositionComponent。
另外,Effect 一族定义在 effects 包中,我们在前面用到的 MoveEffect 就是这类的构件。在之后的学习中我们再深入认识其他的效果,或者自定义 Effects 。

前面的案例中我们也尝试过自定义 Component ,比如 Adventurer 、Monster 、TouchIndicator 等。其实自定义 Component 和 Flutter 中自定义 Widget 的功效类似,都是为了把一些通用的构成逻辑进行封装,以便复用和管理。
比如通过下面的 Monster 类,可以生成多个怪兽对象:可以定义不同的帧序列和生命值,它们对于玩家来说就是两个不同的怪兽。对于编程者而言它们都是通过 Monster 构建类实例化的对象,本质没有什么区别。代码详见 【09/01】

class Monster extends SpriteAnimationComponent with Liveable {
final double life;
Monster({
required SpriteAnimation animation,
required Vector2 size,
required Vector2 position,
this.life = 4000,
}) : super(
animation: animation,
size: size,
position: position,
anchor: Anchor.center,
);
@override
void onDied() {
removeFromParent();
}
@override
Future<void> onLoad() async {
initPaint(lifePoint: life);
}
void move(Vector2 ds) {
position.add(ds);
}
}
复制代码另外,通过自定义构件类,可以覆写 Component 的相关回调方法,监听相关状态,处理逻辑。这里先对 Component 认识到这里,下一章我们将信息探讨一下 Component 的生命周期回调。那时你就会对 Component 有一个更深的认知,那么本文就到这里,明天见 ~
@张风捷特烈 2022.06.03 未允禁转我的 掘金主页 : 张风捷特烈我的 B站主页 : 张风捷特烈我的 github 主页 : toly1994328