[Flutter用ゲームエンジン] Flutter & Flame 試してみた

## 2020/12/16 追記
この記事は Flutter #3 Advent Calendar 2020 - 17日目に登録した記事です(過去記事で恐縮ですが)。

当時はゲームエンジン使うならFlame最強じゃん！と思い試してみましたが、今のProvider + StateNotifier + freezed を使ったモダンな開発との相性を考えるとWidget単位で扱えるらしいSprite Widgetの方が良いのでは？という話も聞きます。
Flameはレンダリングが独自の枠組みで動作するので、State管理を統括するのが難しいのでしょうか？:thinking:

そもそも、もっといいエンジンが出てる可能性もありますね。

そのあたり、詳しい方がいらっしゃったら情報いただけるとありがたいです。
自分で試せって話ですが:relieved:

それではどうぞ:open_hands_tone2:

## はじめに
最近Flutterを触っているんですが、Flutterで使えるゲームエンジンみたいなのないの？:thinking:
という話になり、軽く調べてみましたので共有したいと思います。

また、いくつかあるエンジンの中でFlameというものを試してみたのですが、日本語のことはじめ的リファレンスがあまりなかったのでこれに関しても簡単にまとめます:kissing_heart:

ちなみにモバイルアプリ開発、Flutter開発、ゲーム開発すべてにおいて初心者ですので間違いがあればガンガンご指摘いただけると幸いです。

## まずFlutter用のゲームエンジンってあるの？
まあ当然といえば当然ですが、ありました。
ざっと紹介します。

### Flame
:fire:https://flame-engine.org/

他のエンジンと比べるとStarも多く、頻繁にアップデートされている。
機能も充実しており、とりあえずFlutterパッケージ使ってゲーム作りたいってなったときに一番最初に選択肢に挙がりやすいエンジンかなと思います。

ループBGMの実装をはじめゲーム実装周辺のUtilも使えるものがそれなりにあり、MaterialApp Widgetを使ったマテリアルデザインベースUIアプリでゲームアプリ作る場合の補助ツールとしても使えそうな感じではあります。

また、box2d(正確にはJavaのBox2dのDart移植版をフォークしたもの)が使えるのも魅力です。

### Sprite Widget
https://github.com/spritewidget/spritewidget

採用候補としてはこちらも有力でしたが、Star数と更新頻度の差でFlameを採用しました。
まだちゃんと試してないのでなんともいえませんが、Widget単位になっているため、局所的に埋め込んだり取り回しは効きやすいのかなと思います。

### feathers
https://github.com/Attict/feathers

上記2つと比べ超軽量のゲームエンジンのようです。
ComponentにFeatherクラスを継承することでゲームに必要な制御を扱えるようにするみたいです。

超軽量なだけあって機能としてはかなり限定的で、ゲーム制御に必要なライフサイクルやレンダリング機能を提供するだけのものに見えます。

### Quill
https://github.com/Attict/Quill

見たところ、Feathersとセットで使うっぽい感じ？
Feathers化したComponentを更に包括的に扱えるようにするためのエンジンって感じでしょうか。

README見る感じBGMや画像プリセット等の追加機能も検討中とのことですが、一年以上音沙汰がないため採用は難しそうです。

### flutter_unity_widget
https://github.com/snowballdigital/flutter-unity-view-widget/tree/master

FlutterにUnityを埋め込むためのウィジェットのようです。
ぶっちゃけ現状のFlutterパッケージではめちゃくちゃ凝ったゲームアプリを作るのは難しそうなので、そういう場合はリファレンスの充実度的な意味でもUnityを採用することになるのだろうと思います。

## Flameを使ってみよう:fire:
ということで、Flameを試してみたいと思います。
とりあえず公式Sampleを動かしてみます。

### Flutter Create
適当な新規アプリを作ります。

```bash
$ flutter create app-name
```

### 公式Sampleをパクってくる
https://github.com/flame-engine/flame
今回は **doc/examples/box2d/contact_csllbacks** を動かしてみます。

上記ディレクトリからlib, pubspec.yamlを作成したアプリにコピー。

### PackageをインストールしてRun
```bash
$ flutter pub get
$ flutter run
```

### 結果

<img width="300" alt="Simulator Screen Shot - iPhone 8 Plus - 2020-06-02 at 11.07.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136254/e932dd7f-6973-e22c-2f02-08d78dc5e3de.png">

画面上をタップするとBallが出現し、重力に従って落下 → 壁や他のボールに当たると反発するというだけの簡単なアプリです。
Ballの重力落下スピードを上げたい場合は、main.dart最下部のMyBox2Dのコンストラクタからgravityを変更します。

```dart
/* main.dart */
class MyBox2D extends Box2DComponent {
  MyBox2D() : super(scale: 4.0, gravity: -100.0); // ここ

  @override
  void initializeWorld() {}
}
```

また、Sampleのままですと結構なスピードでBallが反発し跳ね続けますが、これはWhiteBall(ランダムに発生する白色ボール)に衝突時に反発するプロティが設定されているためです。
これをコメントアウトすることで、不自然な反発がなくなりBallはより自然な挙動で画面下部に溜まります。

```dart
class WhiteBallContactCallback extends ContactCallback<Ball, WhiteBall> {
  @override
  void begin(Ball ball, WhiteBall whiteBall, Contact contact) {
    ball.giveNudge = true; // これ
  }

  @override
  void end(Ball ball, WhiteBall whiteBall, Contact contact) {}
}
```

<img width="300" alt="Simulator Screen Shot - iPhone 8 Plus - 2020-06-02 at 11.36.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136254/4a8a62bc-3727-0bfb-4bd1-4bed9889b6bf.png">

よくスマホいじってると水とかボールを計画的に落下させるようなパズルアプリの広告が出てきますが、これの応用で簡単に作れそうですね。

### T-Rex Game. 作ってみよう
GoogleのT-Rex Gameをご存知の方も多いと思いますが、Flutter Weakly #35にこれを再現した記事を見つけ、せっかくなので試してみました。
https://medium.com/dextra-digital/creating-the-t-rex-game-with-flutter-and-flame-6d01add1ad5b

### Clone
アプリはこちらからCloneできます。
https://github.com/flame-engine/trex-flame

### せっかくなのでちょっとだけ改良
そのまま動かすだけではつまらないので、スコアタイムを追加しました。

```dart
/* game.dart */
class TRexGame extends BaseGame {
  TRexGame({Image spriteImage}) {
    tRex = TRex(spriteImage);
    horizon = Horizon(spriteImage);
    gameOverPanel = GameOverPanel(spriteImage);

    // scoreTimeを定義
    scoreTime = TextComponent("Time: ",
        config: TextConfig(color: BasicPalette.black.color))
      ..anchor = Anchor.topCenter
      ..x = 180
      ..y = 50;

    // add
    this..add(Bg())..add(horizon)..add(tRex)..add(gameOverPanel)..add(scoreTime);
  }

  TRex tRex;
  Horizon horizon;
  GameOverPanel gameOverPanel;
  TRexGameStatus status = TRexGameStatus.waiting;

  // TextComponent宣言
  TextComponent scoreTime;

  double currentSpeed = GameConfig.speed;
  double timePlaying = 0.0;

  @override
  void onTap() {
    if (gameOver) {
      restart();
      return;
    }
    tRex.startJump(currentSpeed);
  }

  @override
  void update(double t) {
    tRex.update(t);
    // scoreTimeの更新
    scoreTime.text = "Time: " + timePlaying.toStringAsFixed(2);
    scoreTime.update(t);
    horizon.updateWithSpeed(0.0, currentSpeed);

    ...
```

テキストの位置とかは超適当です。

### 動かしてみる

```bash
$ flutter pub get
$ flutter run
```

### 結果
![Simulator Screen Shot - iPhone 11 Pro Max - 2020-06-02 at 11.52.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136254/767ee061-efab-0200-ba5b-7b0a1ef992c4.png)

スコアタイム表示、超被ってる。。

何はともあれ、Flameを動かしてみることができました:hugging:

## 参考
- [5 Best Flutter Game Engine](https://www.dunebook.com/best-flutter-game-engine/)
- [Recreating the famous T-Rex game with Flutter and Flame](https://medium.com/dextra-digital/creating-the-t-rex-game-with-flutter-and-flame-6d01add1ad5b)