這還能叫Flutter?用它復原一個叫《是男人就堅持100秒》的遊戲|技術點評

大帥老猿發表於2021-03-13

前言

我說Flutter的跨端一致性並非首創

但凡你用過Unity3D,Unreal等遊戲引擎,你就會發現 這些不都是跨端的嗎?不都是開局一塊畫布,剩下隨便整麼?

既然和遊戲引擎作對比

那麼我們真的拿Flutter來做個遊戲吧

FLAME引擎

image.png 這裡我用了一款基於的Flutter的遊戲引擎,名為FLAME。使用它開發遊戲,基本用不到Flutter裡的那些Widget,所以就有了標題裡的提問

一個Flutter應用不使用Flutter的Widget,那還叫Flutter嗎?

不管了,先把這篇文章寫完,抽空再用widget重做一次這個遊戲

遊戲引擎基礎

引入Flame

dependencies:
  flame: ^1.0.0-rc5
複製程式碼

開發遊戲,最基礎就是這三件事

  • 重新整理機制(update)
  • 渲染畫布(renderer)
  • 輸入事件(events)

Flame中,一個基礎的遊戲框架程式碼如下

class MyGameSubClass extends Game {
  @override
  void render(Canvas canvas) {
    // TODO: implement render
  }

  @override
  void update(double t) {
    // TODO: implement update
  }
}
    
  
main() {
  runApp(
    GameWidget(
      game: MyGameSubClass(),
    )
  );
}
複製程式碼

重新整理,渲染都有了,事件呢,在本遊戲中我們加入拖動事件

class MyGameSubClass extends BaseGame with PanDetector
複製程式碼

渲染圖形元素

  • 飛機

image.png

飛機的圖形素材是一個精靈圖,其中前面兩幀是飛行狀態,後面三幀是爆炸狀態

playerSpriteSheet = SpriteSheet(
  image: await images.load('player.png'),
  srcSize: playerSize,
);

//執行狀態下的精靈圖動畫
player = SpriteAnimationComponent(
  size: playerSize,
  animation: playerSpriteSheet.createAnimation(row: 0, stepTime: .1, to: 2),
);
複製程式碼

然後我們通過add方法,將圖形元素展示到畫面中

add(player);

//放到x:100,y:100的座標,大家感受一下座標系
player.x=player.y=100;
複製程式碼

image.png

  • 子彈

image.png

bulletImage = await images.load('bullet.png');
複製程式碼

一顆子彈的出現邏輯以及生命週期是這樣的

  1. 從螢幕外生成
  2. 向著飛機當前座標的方向移動
  3. 移出螢幕即銷燬
//新增一顆子彈
void addBullet() {
final bullet = SpriteComponent.fromImage(bulletImage, size: bulletSize);

double bulletX;
double bulletY;

//隨機在顯示區四周的矩形邊上生成
if (Random().nextBool()) {
  bulletX = Random().nextDouble() * (screenSize.width + bulletSize.x) -
      bulletSize.x;
  bulletY = Random().nextBool() ? -bulletSize.y : screenSize.height;
} else {
  bulletX = Random().nextBool() ? -bulletSize.x : screenSize.width;
  bulletY = Random().nextDouble() * (screenSize.height + bulletSize.y) -
      bulletSize.y;
}

//給與子彈初始座標
bullet.x = bulletX;
bullet.y = bulletY;

//新增到場景中
add(bullet);

//加入bullet管理陣列,稍後用於更新子彈飛行座標以及碰撞檢測
bullets.add({
  "component": bullet,//子彈控制元件例項
  "speed": (1+gameTime/10) + Random().nextDouble()*3,//飛行速度
  "angle": atan2(((bulletY + bulletSize.y/2) - (player.y + playerSize.y / 2)),
      ((bulletX + bulletSize.x) - (player.x + playerSize.x / 2)))
});//向量角度
}
複製程式碼

通過呼叫這個addBullet的方法,我們就能在螢幕中朝著飛機所在位置發射一顆子彈了

2021-03-13 16_36_49.gif

為了發射多顆,我們再新建一個函式addGroupBullet

//新增一組子彈
void addGroupBullet() {
    int groupCount = 10+Random().nextInt(gameTime+1);
    for (int i = 0; i < groupCount; i++) {
      addBullet();
    }
}
複製程式碼

重新整理機制

Flame提供的update方法中我們需要更新所有子彈的位置,因為子彈不是靜止的,需要一直移動呀!

@override
void update(double dt) {
    super.update(dt);
    
    //遍歷子彈陣列,對所有子彈進行更新
    for (int i = bullets.length - 1; i >= 0; i--) {
          var bulletItem = bullets[i];
          
          //獲得子彈例項
          SpriteComponent bullet = bulletItem["component"] as SpriteComponent;

          double angle = bulletItem["angle"];
          double speed = bulletItem["speed"];

          //讓子彈根據發射時的向量角度進行移動
          bullet.x -= cos(angle) * speed;
          bullet.y -= sin(angle) * speed;
          
          //當子彈移動到螢幕外時銷燬它
          if (isNotInScreen(bullet.x, bullet.y)) {
                print("bullet removed");
                remove(bullet);
                bullets.removeAt(i);
                continue;
          }
    }
}
複製程式碼

開始有點帶感了吧?

2021-03-13 16_47_52.gif

飛機的移動

void onPanUpdate(DragUpdateDetails details) {
    super.onPanUpdate(details);

    if (!isGameStart) return;

    //由於飛機物件的座標點在左上角,所以移動是要注意偏移一下
    player.x = details.globalPosition.dx - playerSize.x / 2;
    player.y = details.globalPosition.dy - playerSize.y / 2;
}
複製程式碼

2021-03-13 16_52_06.gif

碰撞檢測

在每次重新整理子彈座標的時候,去檢測一下是否和當前飛機的座標重合了,重合了遊戲就結束啦

if (isHitPlayer(bullet.x, bullet.y)) {
    gameOver();
}
複製程式碼

這裡用了一個簡單的判斷機制,並沒有精確到飛機的外輪廓。一是精確到外輪廓將帶來更大的運算量,也比較複雜。二是那樣的遊戲體驗不見得好,畢竟在手機玩的話,手指要擋住大部分飛機的圖形

//飛機和子彈碰撞判斷
bool isHitPlayer(double x, double y) {
    double _x = (x + bulletSize.x / 2 - (player.x + playerSize.x / 2)).abs();
    double _y = (y + bulletSize.y / 2 - (player.y + playerSize.y / 2)).abs();

    //求出子彈和飛機座標的直線距離
    double distance = sqrt(_x * _x + _y * _y);

    //當直線距離小於判定為碰撞的距離時則返回true/false
    if (distance <= hitDistance) return true;
    return false;
}
複製程式碼

遊戲結束

void gameOver() {
    isGameStart = false;
    
    //取消定時發射子彈的定時器
    if(timer!=null)timer.cancel();

    //播放飛機爆炸動畫
    player.animation = playerSpriteSheet.createAnimation(
        row: 0, stepTime: .1, loop: false, from: 2, to: 6);

    print("game over");
}
複製程式碼

程式碼倉庫

github.com/ezshine/flu…

後話

這麼寫,從程式碼層面看,和Flutter關係不大,但利用Flutter的跨端能力,我們可以很容易的開發一個橫跨Mac,Linux,Windows,Web,iOS,Android端的小遊戲。如果你不會用那些開發遊戲的IDE,比如Unity3D等,那Flutter也是一個不錯的選擇。

關注大帥

一個設計師出身的老程式猿,希望寫程式碼到70歲


近期文章(感謝掘友的鼓勵與支援???)


本文正在參與「掘金 2021 春招闖關活動」, 點選檢視 活動詳情

相關文章