【基於Flutter&Flame 的飛機大戰開發筆記】子彈發射及碰撞檢測

語言: CN / TW / HK

theme: cyanosis highlight: xcode


前言

有了前面的基礎,戰機和敵機的環境已經搭建好了。故本文著重記錄基於Flame實現飛機大戰的戰機子彈發射以及基於碰撞檢測實現的子彈設計敵機和戰機與敵機相遇場景

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

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

子彈Component

建立一個繼承自SpriteAnimationComponent的類Bullet1作為子彈的實現類。這個和之前的一樣都當是一個序列幀處理。 ```dart class Bullet1 extends SpriteAnimationComponent with CollisionCallbacks { double speed = 200;

Bullet1() : super();

@override Future onLoad() async { List sprites = []; sprites.add(await Sprite.load('bullet/bullet1.png')); final SpriteAnimation spriteAnimation = SpriteAnimation.spriteList(sprites, stepTime: 0.15); animation = spriteAnimation; }
為了讓`子彈Component`運動起來,沿用之前`敵機Component`的做法。在**其`update`回撥後更新`position`**。dart @override void update(double dt) { super.update(dt); Vector2 ds = Vector2(0, -1) * speed * dt;

position.add(ds); if (position.y < 0) { removeFromParent(); } } `` 這個與之前類似,只是子彈是**往y軸負方向移動至螢幕外**的,所以這裡是Vector2(0, -1)。同樣也是s = v * t`的體現。

子彈發射

這裡需要令戰機Component定時發射出子彈。還是使用之前講到的Timer,間隔0.5s發射一次。 ```dart class Player extends SpriteAnimationComponent with HasGameRef, Draggable { ... late Timer _shootingTimer;

@override
Future<void> onLoad() async {
  ...
  _shootingTimer = Timer(0.5, onTick: _addBullet, repeat: true);
}

- 定義`_shootingTimer`作為`子彈Component`的生成定時,與前面類似。 - 定時觸發方法`_addBullet`。dart void _addBullet() { final Bullet1 bullet1 = Bullet1(); bullet1.size = Vector2(5, 11); bullet1.priority = 1; priority = 2; bullet1.position = Vector2(position.x + size.x / 2, position.y); gameRef.add(bullet1); } ``_addBullet方法新建一個子彈Component,這是上文建立的實體。需要注意的是 - 由於子彈是**相對於螢幕運動**的,而不是戰機Component。所以這裡需要新增到Game也就是**最外層**。 - **後加入**的Component會之於前面的Component之**上**(類似幀佈局),所以這裡為了使效果更像是從戰機發射出來的,需要**調整兩者的優先順序**。 -子彈Component的初始位置會在戰機Component上方的**中心點**上Vector2(position.x + size.x / 2, position.y)。ps:這是預設戰機Component`的錨點在右上角的計算結果。

這部分邏輯參考:【Flutter&Flame遊戲 - 捌】裝彈完畢 | 角色武器發射

擊中敵機及戰機與敵機碰撞

要實現子彈擊中敵機的效果,我們需要知道子彈Component敵機Component的相遇或者說是碰撞的時機。這裡我們運用Flame提供的碰撞檢測機制CollisionCallbacks)實現。

我們分別對類Enemy1Bullet1新增CollisionCallbacks的混入 ```dart class Bullet1 extends SpriteAnimationComponent with CollisionCallbacks {

class Enemy1 extends SpriteAnimationComponent with HasGameRef, CollisionCallbacks { ```

同時我們需要在兩個Component上新增一個RectangleHitbox,這個在之前除錯位置的時候有用到。碰撞檢測的預設機制是通過兩個RectangleHitbox進行的。如果玩過我的世界的同學應該對碰撞箱有所耳聞,這個其實也是類似。 dart @override Future<void> onLoad() async { add(RectangleHitbox()); }

實現CollisionCallbacks中的onCollisionCallback,這個回撥的是碰撞發生時的事件。以敵機Component為例: dart // class Enemy1 @override CollisionCallback<PositionComponent>? get onCollisionCallback => (Set<Vector2> intersectionPoints, PositionComponent other) { if (other is Bullet1 || other is Player) { removeFromParent(); } }; 回調了一個PositionComponent物件,即為與其發生碰撞的Component。這裡可以判斷其型別是子彈Component戰機Component時將敵機Component移除。ps:這裡先預設敵機Component的生命值為1

ps:CollisionCallbacks還可以回撥碰撞開始和碰撞結束事件: ```dart // collision_callbacks.dart /// [onCollision] is called in every tick when this object is colliding with /// [other]. @mustCallSuper void onCollision(Set intersectionPoints, T other) { onCollisionCallback?.call(intersectionPoints, other); }

/// [onCollisionStart] is called in the first tick when this object starts /// colliding with [other]. @mustCallSuper void onCollisionStart(Set intersectionPoints, T other) { activeCollisions.add(other); onCollisionStartCallback?.call(intersectionPoints, other); }

/// [onCollisionEnd] is called once when this object has stopped colliding /// with [other]. @mustCallSuper void onCollisionEnd(T other) { activeCollisions.remove(other); onCollisionEndCallback?.call(other); } ```

Game這一層還需要新增HasCollisionDetection混入,這樣碰撞檢測才能開啟並往下傳遞dart class Game extends FlameGame with HasDraggables, HasCollisionDetection {

這部分邏輯參考:【Flutter&Flame遊戲 - 拾叄】碰撞檢測 | CollisionCallbacks

這樣就能實現擊中敵機及戰機與敵機碰撞的效果了,其他地方的碰撞檢測同理,這裡就不一一贅述了。來看看效果吧

Record_2022-07-07-18-52-46_13914082904e1b7ce2b619733dc8fcfe_.gif