[Flutter&Flameに基づく航空機戦争の開発ノート]敵航空機のリファクタリング

序文

Component衝突検出が実装され、衝突のフィードバック効果が追加された後、効果全体が一時的に閉じられます。この記事では、リファクタリング作業に焦点を当てます。敌机Componentこの機会に、残りのジャンルを敌机Component追加してください。

著者はこのシリーズの記事を次のコラムに含めており、興味のある学生は次の記事を読むことができます。

Flutter&Flameに基づく航空機戦争の開発ノート

概要

前のクラスEnemy1はタイプ敌机Componentであり、航空機戦争における敵航空機の基本的な機能を備えています。

  • 衝突がない場合は、画面上部から画面下部まで一定の速度で移動し、最後に取り外します。Component树
  • 衝突を検知する機能があり战机Component/子弹Component、衝突が発生すると体力が低下します
  • ヘルス値が0の場合、破壊/破壊効果が生成されます。

战机Component/子弹Component実際、衝突時に体力を低下させる効果もありません。上記の点を組み合わせて、画面にバラエティを加える必要があり敌机Componentます。したがって、特性をさらに抽象化敌机Componentする必要があります

上記の特性と組み合わせて、定義され抽象类Enemyます。

SpriteAnimationComponent

まず、さまざまな状態でのアニメーションフレームの再生スキームについて説明します。以前使用していたSpriteAnimationComponent再生能力敌机Componentは、破壊されたときに設定されましたplaying = trueただし、現時点では、正常、攻撃、破壊敌机Componentの3つの状態があります。列挙を定義できます。

enum EnemyState {
  idle,
  hit,
  down,
}
复制代码

SpriteAnimationGroupComponent代わりSpriteAnimationComponentに、パラメータcurrentを設定して現在の状態を切り替え、対応するアニメーション効果を切り替えることで、ここを使用できます。たぶんソースコードを見てください

// sprite_animation_group_component.dart
class SpriteAnimationGroupComponent<T> extends PositionComponent
    with HasPaint
    implements SizeProvider {
  /// Key with the current playing animation
  T? current;

  /// Map with the mapping each state to the flag removeOnFinish
  final Map<T, bool> removeOnFinish;

  /// Map with the available states for this animation group
  Map<T, SpriteAnimation>? animations;
复制代码

ここで范型Tは、上記のように設定できますEnemyStateさまざまな状態animationsに対応しますもう1つありますが、どの状態の再生が完了した後、コンポーネントが自動的に削除されることがわかりますSpriteAnimationremoveOnFinish

// sprite_animation_group_component.dart
SpriteAnimation? get animation => animations?[current];

@mustCallSuper
@override
void render(Canvas canvas) {
  animation?.getSprite().render(
        canvas,
        size: size,
        overridePaint: paint,
      );
}

@mustCallSuper
@override
void update(double dt) {
  animation?.update(dt);
  if ((removeOnFinish[current] ?? false) && (animation?.done() ?? false)) {
    removeFromParent();
  }
}
复制代码

renderメソッドは、レンダリングするための対応する状態SpriteAnimationを取得します。アニメーションが完了したかどうか、および状態を自動的に削除する必要があるかどうかupdateを検出します。ps:このメソッドは、各フレームを描画するためのコールバックです。render

ステータス申請

工法では、初期状態はidleであり、再生が完了すると設定down状態は自動的に解除されます。

// class Enemy
Enemy(
    {required Vector2 initPosition,
    required Vector2 size,
    required this.life,
    required this.speed})
    : super(
          position: initPosition,
          size: size,
          current: EnemyState.idle,
          removeOnFinish: {EnemyState.down: true}) {
  animations = <EnemyState, SpriteAnimation>{};
}
复制代码

異なる状態をロードするための3つの抽象メソッドを定義しますすべての状態にそれらがあるわけではないため、ここではnull許容として定義されていますSpriteAnimationhit敌机Component

// class Enemy
Future<SpriteAnimation> idleSpriteAnimation();

Future<SpriteAnimation?> hitSpriteAnimation();

Future<SpriteAnimation> downSpriteAnimation();
复制代码

をロードしonLoadます。hitこの状態の再生が完了したら、状態を状態にリセットする必要があることidleに注意してください

// abstract class Enemy
@override
Future<void> onLoad() async {
  animations?[EnemyState.idle] = await idleSpriteAnimation();
  final hit = await hitSpriteAnimation();
  hit?.onComplete = () {
    _enemyState = EnemyState.idle;
  };
  if (hit != null) animations?[EnemyState.hit] = hit;
  animations?[EnemyState.down] = await downSpriteAnimation();
  。。。
复制代码

衝突検出で

  • 如果已经是down状态了,就无需触发等待动画播放完自动移除。
  • 如果碰撞目标为Player/Bullect1,则需要处理,生命值未到达0前状态更改为hit,否则为downhit播放完需要变更回idle,与上述逻辑对应上。
// class Enemy
@override
void onCollisionStart(
    Set<Vector2> intersectionPoints, PositionComponent other) {
  super.onCollisionStart(intersectionPoints, other);
  if (current == EnemyState.down) return;
  if (other is Player || other is Bullet1) {
    if (current == EnemyState.idle) {
      if (life > 1) {
        _enemyState = EnemyState.hit;
        life--;
      } else {
        _enemyState = EnemyState.down;
        life = 0;
      }
    。。。
复制代码

状态变成前,需要重置将要变更状态的SpriteAnimation。这是为了保证每次变更都是从第一帧开始,不会造成画面异常。

// class Enemy
set _enemyState(EnemyState state) {
  if (state == EnemyState.hit) {
    animations?[state]?.reset();
  }
  current = state;
}
复制代码

Component的移动

之前是通过s = v * t,在update方法回调中更新position的方式实现移动的。这里改成使用MoveEffect实现

// class Enemy
add(MoveEffect.to(
    Vector2(position.x, gameRef.size.y), EffectController(speed: speed),
    onComplete: () {
  removeFromParent();
}));
复制代码

传入speed,会使用SpeedEffectController,默认是线性移动的。

// effect_contorller.dart
final isLinear = curve == Curves.linear;
if (isLinear) {
  items.add(
    duration != null
        ? LinearEffectController(duration)
        : SpeedEffectController(LinearEffectController(0), speed: speed!),
  );
}
复制代码

这部分可参考官方示例: flame/aseprite_example.dart at main · flame-engine/flame (github.com)

新一代敌机Component

简单说一下重构后的敌机Component,这里以第二个类型类Enemy2为例,因为它的生命值高可以触发hit状态。

// class Enemy2
@override
Future<SpriteAnimation?> hitSpriteAnimation() async {
  List<Sprite> sprites = [];
  sprites.add(await Sprite.load('enemy/enemy2_hit.png'));
  sprites.add(await Sprite.load('enemy/enemy2.png'));
  final spriteAnimation =
      SpriteAnimation.spriteList(sprites, stepTime: 0.15, loop: false);
  return spriteAnimation;
}

@override
RectangleHitbox rectangleHitbox() {
  return RectangleHitbox(
      size: Vector2(size.x, size.y * 0.9), position: Vector2(0, 0));
}
复制代码
  • 以重写hit状态的SpriteAnimation加载为例,这里有一帧的被击中效果。
  • 还需要输出一个RectangleHitbox,由于不同素材的尺寸有误差,所以这里单独作碰撞箱的修正。

大部分逻辑都在父类Enemy实现,这里基本只需要实现抽象方法即可。

敌机生成器适配

敌机生成器EnemyCreatorタイミング作成のために、前に1つあったことを覚えてい敌机Componentますか?さまざまな種類の敵機が追加されているため、それ_createEnemyに応じてタイミングトリガー方法を変更する必要があります。その前に、各敵航空機の属性を定義する必要があります。1、2 、および3は、クラスEnemy1、Enemy2、Enemy3、つまり、小、中、大のタイプを表します。属性値については、テキストを参照してください。

// class EnemyCreator
final enemyAttrMapping = {
  1: EnemyAttr(size: Vector2(45, 45), life: 1, speed: 50.0),
  2: EnemyAttr(size: Vector2(50, 60), life: 2, speed: 30.0),
  3: EnemyAttr(size: Vector2(100, 150), life: 4, speed: 20.0)
};
复制代码

_createEnemy、各タイプの生成確率を間隔で制御します。

void _createEnemy() {
  final width = gameRef.size.x;
  double x = _random.nextDouble() * width;
  final double random = _random.nextDouble();
  final EnemyAttr attr;
  final Enemy enemy;
  if (random < 0.5) {
    // load Enemy1
  } else if (random >= 0.5 && random < 0.8) {
    // load Enemy2
  } else {
    // load Enemy3
  }
  add(enemy);
}
复制代码

この時点で敌机Componentリファクタリングは終了しており、フォローアップに若干の変更があります。現在の効果を見てみましょう

Record_2022-07-11-16-39-13_13914082904e1b7ce2b619733dc8fcfe_.gif

要約する

敌机Componentリファクタリングは完了しており、タイミング生成のルールは少し荒い可能性があり、このフォローアップでは最適化を検討することができます。敵機の属性については、現在は死ぬまで書かれており、将来的にはローカル構成と考えることができます。

おすすめ

転載: juejin.im/post/7119038284608569352