[Notas de desarrollo para Aircraft Wars basado en Flutter&Flame] Refactorización del avión enemigo

prefacio

ComponentDespués de implementar la detección de colisión y agregar el efecto de retroalimentación de la colisión, todo el efecto se cierra temporalmente. Este artículo se centrará en 敌机Componentel trabajo de refactorización . Aprovecha esta oportunidad para agregar el resto de los géneros敌机Component .

El autor ha incluido esta serie de artículos en las siguientes columnas, y los estudiantes interesados ​​pueden leer:

Notas de desarrollo para Aircraft Wars basadas en Flutter&Flame

resumen

La clase anterior Enemy1es un tipo 敌机Component, que tiene las funciones básicas de los aviones enemigos en la guerra de aviones:

  • En el caso de que no haya colisión , muévase desde la parte superior de la pantalla hasta la parte inferior de la pantalla a una velocidad constante y, finalmente, retírela.Component树
  • Tiene la capacidad de detectar colisiones , y 战机Component/子弹Componentcuando ocurre una colisión, habrá una reducción en la salud .
  • Cuando el valor de salud es 0 , se genera el efecto de destrucción/destrucción .

De hecho, también falta un 战机Component/子弹Componentefecto de reducción de la salud al chocar con él. Combinando los puntos anteriores, necesitamos agregar una variedad 敌机Componenta la pantalla. Por lo tanto 敌机Component, las características deben resumirse aún más .

Combinado con las características anteriores, definido 抽象类Enemy.

SpriteAnimationComponent

Primero hablemos sobre el esquema de reproducción de cuadros de animación en diferentes estados. SpriteAnimationComponentLa capacidad de reproducción que usamos anteriormente 敌机Componentse estableció cuando se destruyó playing = true. Pero en este momento hay al 敌机Componentmenos 3 estados, que son normal, atacado y destruido, se puede definir una enumeración.

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

Aquí se puede usar SpriteAnimationGroupComponenten su lugar SpriteAnimationComponent, configurando parámetros currentpara cambiar el estado actual , cambiando así el efecto de animación correspondiente. Tal vez eche un vistazo al código fuente.

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

Aquí 范型Tse puede configurar como se definió anteriormente EnemyState. animationscorrespondientes a diferentes estadosSpriteAnimation . Hay otro removeOnFinish, se puede entender que después de que se completa la reproducción de qué estado, el Componente se elimina automáticamente .

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

renderEl método obtendrá el estado correspondiente SpriteAnimationpara renderizar . Detectará si la animación está completaupdate y si el estado debe eliminarse automáticamente. pd: el método es una devolución de llamada para dibujar cada cuadro.render

Solicitud de estado

En el método de construcción, el estado inicial es idle, el downestado de configuración se elimina automáticamente una vez que se completa la reproducción .

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

Defina tres métodos abstractos para cargar diferentes estados . SpriteAnimationDado que hitno todos los estados los 敌机Componenttienen, aquí se definen como anulables.

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

Future<SpriteAnimation?> hitSpriteAnimation();

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

Cargar en onLoad. Tenga en cuenta que hituna vez completada la reproducción de este estado, el estado debe restablecerse al idleestado .

// 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 detección de colisión

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

敌机生成器适配

¿Recuerdas que hubo uno antes 敌机生成器EnemyCreator, para cronometrar la creación 敌机Component? Debido a la adición de diferentes tipos de aeronaves enemigas, su método de activación de tiempo _createEnemydebe modificarse en consecuencia. Antes de eso, necesitamos definir los atributos de cada avión enemigo . 1, 2 y 3 representan clases Enemy1、Enemy2、Enemy3, es decir, tipos pequeño, mediano y grande. Consulte el texto para el valor del atributo.

// 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, controlamos la probabilidad de generación de cada tipo por intervalo .

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

En este punto, 敌机Componentla refactorización ha llegado a su fin y habrá algunos pequeños cambios en el seguimiento. Echemos un vistazo al efecto actual.

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

Resumir

敌机ComponentLa refactorización está completa, y las reglas para la generación de tiempo pueden ser un poco toscas, y se puede considerar la optimización en este seguimiento. En cuanto a los atributos del avión enemigo, actualmente está escrito a muerte, y puede considerarse como una configuración local en el futuro.

Supongo que te gusta

Origin juejin.im/post/7119038284608569352
Recomendado
Clasificación