【基於Flutter&Flame 的飛機大戰開發筆記】子彈升級和補給

語言: CN / TW / HK

theme: cyanosis highlight: xcode


前言

敵機Component重構之後,飛機大戰的基礎能力就基本成型了。本文會對子彈Component作一次升級,新增遊戲中的子彈道具,以及它與戰機Component碰撞處理

筆者將這一系列文章收錄到以下專欄,歡迎有興趣的同學閱讀:

基於Flutter&Flame 的飛機大戰開發筆記

子彈特性

前面只是簡單的定義了類Bullet1作為子彈Component,根據已有的屬性可以定義一個屬於子彈的父類Bullet,這裡繼續使用抽象類。 ```dart abstract class Bullet extends SpriteAnimationComponent with CollisionCallbacks { Bullet({required this.speed, required this.attack}) : super(size: Vector2(5, 11));

double speed; int attack;

Future bulletAnimation();

@override Future onLoad() async { animation = await bulletAnimation();

add(MoveEffect.to(
    Vector2(position.x, -size.y), EffectController(speed: speed),
    onComplete: () {
  removeFromParent();
}));

add(RectangleHitbox());

}

void loss() { removeFromParent(); } } `` 定義比較簡單 - 子彈擁有屬於自己的速度speed,傷害attack。 - 固定size = 5 * 11。 - 由於**不同子彈的貼圖不同**,定義一個抽象方法bulletAnimation()由子類載入。 - 使用MoveEffect代替之前的s = v * t控制移動,這個前文有講到。這裡是**由初始位置(一般是戰機Component的頭部)移動到螢幕最上方**。 -loss()方法是與敵機Component`發生碰撞時的呼叫。

以上所列出的就是目前子彈的共有特性了。

升級子彈

遊戲中戰機Component會通過獲得道具的方式,短暫獲取強化版子彈。這裡用類Bullet2表示 ```dart class Bullet2 extends Bullet { Bullet2({required super.speed, required super.attack});

@override Future bulletAnimation() async { List sprites = []; sprites.add(await Sprite.load('bullet/bullet2.png')); final SpriteAnimation spriteAnimation = SpriteAnimation.spriteList(sprites, stepTime: 0.15); return spriteAnimation; } } ``` 這裡會載入另外一種貼圖作為子彈的樣式。

先來看看最終效果吧

VID_20220711191853_.gif

子彈補給

如上面的效果,遊戲中會隨機生成子彈補給。這裡定義一個抽象類Supply補給Component的效果是勻速向下移動並帶有小幅度的晃動。 - 勻速向下移動繼續採用MoveEffect的方式。 - 小幅度的晃動需要使用RotateEffect做一個小幅度的旋轉。由於需要一個吊起來來回晃動的效果,所以我們還需要將錨點設定為topCenter,令旋轉支點為上方居中的位置。

故最後的邏輯為 ```dart abstract class Supply extends SpriteComponent with HasGameRef { Supply({position, size}) : super(position: position, size: size, anchor: Anchor.topCenter);

@override Future onLoad() async { add(MoveEffect.to( Vector2(position.x, gameRef.size.y), EffectController(speed: 40.0), onComplete: () { removeFromParent(); }));

add(RotateEffect.by(15 / 180 * pi,
    EffectController(duration: 2, reverseDuration: 2, infinite: true)));

add(RectangleHitbox()..debugMode = true);

} } `` - 移動距離是**從螢幕最上方向螢幕最下方移動**。 - 旋轉角度是弧度,這裡是(15 / 180)pi,需要來回晃動,所以這裡**設定了正反方向的持續時間且效果是無限迴圈的**。 - 最後需要新增RectangleHitbox,作為與戰機Component的碰撞檢測。ps:碰撞檢測的邏輯在子類實現,這裡目前是類BulletSupply`。

子類的邏輯幾乎只有碰撞檢測,這個在下文會講到,先來看看這個實現的效果 Record_2022-07-11-17-25-45_13914082904e1b7ce2b619733dc8fcfe_.gif

升級與恢復

子彈補給Component中,碰撞檢測與戰機Component發生碰撞後 - 會觸發其upgradeBullet()方法,此時會變更bulletType,即子彈型別。 - 新增一個0.15sColorEffect,作為子彈升級的反饋。 - 開啟一個Timer,定時5s,作為子彈恢復回正常的計時。ps:_bulletUpgradeTimer設定了autoStart = false不允許自動開啟。當呼叫start()時,進度會重置,可理解為連續獲得補給計時會重置。 ```dart // class BulletSupply @override void onCollisionStart( Set intersectionPoints, PositionComponent other) { super.onCollisionStart(intersectionPoints, other); if (other is Player) { other.upgradeBullet(); removeFromParent(); } }

// class Player int bulletType = 1;

_bulletUpgradeTimer = Timer(5, onTick: _downgradeBullet, autoStart: false);

void upgradeBullet() { bulletType = 2; add(ColorEffect(Colors.blue.shade900, const Offset(0.3, 0.0), EffectController(duration: 0.15)));

_bulletUpgradeTimer.start(); }

void _downgradeBullet() { bulletType = 1; }

// timer.dart /// Start the timer from 0. void start() { reset(); resume(); } ```

由於戰機Component類Player本身擁有一個定時器用於發射子彈,故在子彈型別bulletType修改後,下一次發射子彈的邏輯就需要變更。 ```dart // class Player void _addBullet() { if (bulletType == 2) { final Bullet2 bullet2 = Bullet2(speed: 400, attack: 2); bullet2.priority = 1; bullet2.position = Vector2(position.x + size.x / 2 - 10, position.y + 10);

final Bullet2 bullet2a = Bullet2(speed: 400, attack: 2);
bullet2a.priority = 1;
bullet2a.position =
    Vector2(position.x + size.x / 2 + 10, position.y + 10);

gameRef.add(bullet2);
gameRef.add(bullet2a);

} else { final Bullet1 bullet1 = Bullet1(speed: 200, attack: 1); bullet1.priority = 1; bullet1.position = Vector2(position.x + size.x / 2, position.y); gameRef.add(bullet1); } } ```

補給生成

還記得那個敵機生成器EnemyCreator嗎?這裡暫時通過它的定時來生成補給 ```dart // class EnemyCreator void _createEnemy() { final width = gameRef.size.x; double x = _random.nextDouble() * width; final double random = _random.nextDouble(); if (random < 0.05) { final size = Vector2(60, 75); if (width - x < size.x / 2) { x = width - size.x / 2; } else if (x < size.x / 2) { x = size.x / 2; } final enemySupply = BulletSupply(position: Vector2(x, -size.y), size: size);

add(enemySupply);
return;

} 。。。 `` 需要注意的是:由於補給Component的**錨點被修改為topCenter**,所以position的位置也**被移動到了topCenter`**。這裡的邊界計算需要按此情況作適配。

最後

本文記錄子彈型別的擴充套件、子彈補給的效果和生成實現以及戰機Component的子彈升級的邏輯。至此,飛機大戰的功能基本上都實現了,剩下附加道具、計分系統以及生命值系統等功能。後續會考慮參考官方的bloc例子對整個專案的結構進行修改。