序文
Component
衝突検出が実装され、衝突のフィードバック効果が追加された後、効果全体が一時的に閉じられます。この記事では、リファクタリング作業に焦点を当てます。敌机Component
この機会に、残りのジャンルを敌机Component
追加してください。
著者はこのシリーズの記事を次のコラムに含めており、興味のある学生は次の記事を読むことができます。
概要
前のクラス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つありますが、どの状態の再生が完了した後、コンポーネントが自動的に削除されることがわかります。SpriteAnimation
removeOnFinish
// 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許容として定義されていますSpriteAnimation
。hit
敌机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
,否则为down
。hit
播放完需要变更回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
リファクタリングは終了しており、フォローアップに若干の変更があります。現在の効果を見てみましょう
要約する
敌机Component
リファクタリングは完了しており、タイミング生成のルールは少し荒い可能性があり、このフォローアップでは最適化を検討することができます。敵機の属性については、現在は死ぬまで書かれており、将来的にはローカル構成と考えることができます。