【Flutter&Flame游戏 - 玖】探索构件 | Component 是什么

2022-06-19 16:13:44 浏览数 (1)

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


前言

这是一套 张风捷特烈 出品的 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 。它是游戏的基本构建模块,可以表示任何需要被渲染、更新的内容。


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


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


前面介绍过子弹、怪物消失,使用的是 removeFromParent 方法。如下源码中可以看出,是调用父级构件 _parent 的移除方法,把当前构件对象从父级节点上移除:

代码语言:javascript复制
---->[Component#removeFromParent]----
/// Remove the component from its parent in the next tick.
void removeFromParent() {
  _parent?.remove(this);
}
复制代码

2. Component 生命周期状态

Component 中有一个 _state 属性,其类型为 LifecycleState 枚举,用于表示构件的状态:

其中有如下 6 种状态,初始状态是 uninitialized ,表示未初始化,也就是构件实例化时的默认状态。前面知道构件中有个 onLoad 的异步方法用于加载资源,在执行异步方法的前一刻就是 loading 状态。该状态会持续到异步方法执行完毕,变成 loaded 状态。

代码语言:javascript复制
enum LifecycleState {
  uninitialized,
  loading,
  loaded,
  mounted,
  removing,
  removed,
}
复制代码

Component 是树形结构的节点,当某个 Component 添加到父节点上后,就会变成 mounted 状态。相关代码如下所示:


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

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


另外 Component 中关于生命周期状态有三个 get 方法,这里介绍一下:

  • isLoaded:非 uninitialized 且非 loading 状态,表示异步加载任务是否已经完成。
  • isMounted: mountedremoving 状态,表示构件依然在树上。
  • shouldRemove: removing 状态,表示构件已被收集到移除列表中,将在下一帧中被移除。
代码语言:javascript复制
---->[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;
复制代码

3. Component 的衍生类

Flamecomponents 包中的文件,是对 Component 的衍生。其中一些 mixin ,比如 DraggableHoverableTappable 等都是依赖于 Component ,情理上来说也算是 Component 的衍生产物。


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

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

我们之前用的 SpriteComponentTextComponentSpriteAnimationComponent 等都是 PositionComponent 一族的。另外,自定义的子弹、主角、怪兽,也都是 PositionComponent


另外,Effect 一族定义在 effects 包中,我们在前面用到的 MoveEffect 就是这类的构件。在之后的学习中我们再深入认识其他的效果,或者自定义 Effects


4. 自定义 Component

前面的案例中我们也尝试过自定义 Component ,比如 AdventurerMonsterTouchIndicator 等。其实自定义 ComponentFlutter 中自定义 Widget 的功效类似,都是为了把一些通用的构成逻辑进行封装,以便复用和管理。

比如通过下面的 Monster 类,可以生成多个怪兽对象:可以定义不同的帧序列和生命值,它们对于玩家来说就是两个不同的怪兽。对于编程者而言它们都是通过 Monster 构建类实例化的对象,本质没有什么区别。代码详见 【09/01】

https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/543581e81c244a9e9166f019da6665ab~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?

代码语言:javascript复制
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

0 人点赞