[Notes de développement pour Aircraft Wars Based on Flutter&Flame] Refactorisation de l'avion ennemi

avant-propos

ComponentAprès la mise en œuvre de la détection de collision et l'ajout de l'effet de rétroaction de la collision, l'ensemble de l'effet est temporairement fermé. Cet article se concentrera sur 敌机Componentle travail de refactoring . Profitez-en pour ajouter le reste des genres敌机Component .

L'auteur a inclus cette série d'articles dans les colonnes suivantes, et les étudiants intéressés sont invités à lire :

Notes de développement pour Aircraft Wars basées sur Flutter & Flame

abstrait

La classe précédente Enemy1est un type 敌机Component, qui a les fonctions de base de l'avion ennemi dans la guerre aérienne :

  • En cas d'absence de collision , déplacez-vous du haut de l'écran vers le bas de l'écran à vitesse constante , et enfin retirez-vous de celui-ci.Component树
  • Il a la capacité de détecter les collisions , et 战机Component/子弹Componentlorsqu'une collision se produit, il y aura une réduction de la santé .
  • Lorsque la valeur de santé est 0 , l' effet de destruction/destruction est généré.

En fait, il n'y a pas non plus d' 战机Component/子弹Componenteffet de réduction de la santé en cas de collision. En combinant les points ci-dessus, nous devons ajouter une variété 敌机Componentà l'écran. Par conséquent 敌机Component, les caractéristiques doivent être davantage résumées .

Combiné avec les caractéristiques ci-dessus, défini 抽象类Enemy.

SpriteAnimationComponent

Parlons d'abord du schéma de lecture des images d'animation dans différents états. SpriteAnimationComponentLa capacité de lecture que nous avons utilisée précédemment 敌机Componenta été définie lorsqu'elle a été détruite playing = true. Mais à ce moment il y a au 敌机Componentmoins 3 états, qui sont normaux, attaqués et détruits ... Une énumération peut être définie.

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

Ici peut être utilisé à la SpriteAnimationGroupComponentplace SpriteAnimationComponent, en définissant des paramètres currentpour changer l'état actuel , commutant ainsi l'effet d'animation correspondant. Peut-être jeter un œil au code source

// 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;
复制代码

Ici 范型Tpeut être défini comme défini ci-dessus EnemyState. animationscorrespondant à différents étatsSpriteAnimation . Il y en a un autre removeOnFinish, on peut comprendre qu'une fois la lecture de quel état terminée, le composant est automatiquement supprimé .

// 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();
  }
}
复制代码

renderLa méthode obtiendra l'état correspondant SpriteAnimationà render . updateIl détectera si l'animation est terminée et si l'état doit être automatiquement supprimé. ps : renderLa méthode est un rappel pour dessiner chaque image.

Demande de statut

Dans la méthode de construction, l'état initial est idle, l' downétat de réglage est automatiquement supprimé une fois la lecture terminée .

// 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>{};
}
复制代码

Définissez trois méthodes abstraites pour charger différents états . SpriteAnimationÉtant donné hitque tous les états 敌机Componentn'en ont pas, ils sont définis comme nullables ici.

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

Future<SpriteAnimation?> hitSpriteAnimation();

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

Charger en onLoad. Notez qu'une hitfois la lecture de cet état terminée, l'état doit être réinitialisé à l' idleétat .

// 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();
  。。。
复制代码

en détection de collision

  • 如果已经是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实现,这里基本只需要实现抽象方法即可。

敌机生成器适配

Vous vous souvenez qu'il y en avait un avant 敌机生成器EnemyCreator, pour la création de timing 敌机Component? En raison de l'ajout de différents types d'avions ennemis, sa méthode de déclenchement de synchronisation _createEnemydoit être modifiée en conséquence. Avant cela, nous devons définir les attributs de chaque avion ennemi . 1, 2 et 3 représentent les classes Enemy1、Enemy2、Enemy3, à savoir les types petit, moyen et grand. Voir le texte pour la valeur de l'attribut.

// 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, nous contrôlons la probabilité de génération de chaque type par intervalle .

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);
}
复制代码

À ce stade, 敌机Componentla refactorisation est terminée et il y aura quelques petits changements dans le suivi. Jetons un coup d'œil à l'effet actuel

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

Résumer

敌机ComponentLa refactorisation est terminée, et les règles de génération de synchronisation peuvent être un peu approximatives, et une optimisation peut être envisagée dans ce suivi. Concernant les attributs de l'avion ennemi, il est actuellement écrit à mort, et il peut être considéré comme une configuration locale à l'avenir.

Guess you like

Origin juejin.im/post/7119038284608569352