2020/12/16 追記
この記事は Flutter #3 Advent Calendar 2020 - 17日目に登録した記事です(過去記事で恐縮ですが)。
当時はゲームエンジン使うならFlame最強じゃん!と思い試してみましたが、今のProvider + StateNotifier + freezed を使ったモダンな開発との相性を考えるとWidget単位で扱えるらしいSprite Widgetの方が良いのでは?という話も聞きます。
Flameはレンダリングが独自の枠組みで動作するので、State管理を統括するのが難しいのでしょうか?
そもそも、もっといいエンジンが出てる可能性もありますね。
そのあたり、詳しい方がいらっしゃったら情報いただけるとありがたいです。
自分で試せって話ですが
それではどうぞ
はじめに
最近Flutterを触っているんですが、Flutterで使えるゲームエンジンみたいなのないの?
という話になり、軽く調べてみましたので共有したいと思います。
また、いくつかあるエンジンの中でFlameというものを試してみたのですが、日本語のことはじめ的リファレンスがあまりなかったのでこれに関しても簡単にまとめます
ちなみにモバイルアプリ開発、Flutter開発、ゲーム開発すべてにおいて初心者ですので間違いがあればガンガンご指摘いただけると幸いです。
まずFlutter用のゲームエンジンってあるの?
まあ当然といえば当然ですが、ありました。
ざっと紹介します。
Flame
他のエンジンと比べるとStarも多く、頻繁にアップデートされている。
機能も充実しており、とりあえずFlutterパッケージ使ってゲーム作りたいってなったときに一番最初に選択肢に挙がりやすいエンジンかなと思います。
ループBGMの実装をはじめゲーム実装周辺のUtilも使えるものがそれなりにあり、MaterialApp Widgetを使ったマテリアルデザインベースUIアプリでゲームアプリ作る場合の補助ツールとしても使えそうな感じではあります。
また、box2d(正確にはJavaのBox2dのDart移植版をフォークしたもの)が使えるのも魅力です。
Sprite Widget
採用候補としてはこちらも有力でしたが、Star数と更新頻度の差でFlameを採用しました。
まだちゃんと試してないのでなんともいえませんが、Widget単位になっているため、局所的に埋め込んだり取り回しは効きやすいのかなと思います。
feathers
上記2つと比べ超軽量のゲームエンジンのようです。
ComponentにFeatherクラスを継承することでゲームに必要な制御を扱えるようにするみたいです。
超軽量なだけあって機能としてはかなり限定的で、ゲーム制御に必要なライフサイクルやレンダリング機能を提供するだけのものに見えます。
Quill
見たところ、Feathersとセットで使うっぽい感じ?
Feathers化したComponentを更に包括的に扱えるようにするためのエンジンって感じでしょうか。
README見る感じBGMや画像プリセット等の追加機能も検討中とのことですが、一年以上音沙汰がないため採用は難しそうです。
flutter_unity_widget
FlutterにUnityを埋め込むためのウィジェットのようです。
ぶっちゃけ現状のFlutterパッケージではめちゃくちゃ凝ったゲームアプリを作るのは難しそうなので、そういう場合はリファレンスの充実度的な意味でもUnityを採用することになるのだろうと思います。
Flameを使ってみよう
ということで、Flameを試してみたいと思います。
とりあえず公式Sampleを動かしてみます。
Flutter Create
適当な新規アプリを作ります。
$ flutter create app-name
公式Sampleをパクってくる
https://github.com/flame-engine/flame
今回は doc/examples/box2d/contact_csllbacks を動かしてみます。
上記ディレクトリからlib, pubspec.yamlを作成したアプリにコピー。
PackageをインストールしてRun
$ flutter pub get
$ flutter run
結果
画面上をタップするとBallが出現し、重力に従って落下 → 壁や他のボールに当たると反発するというだけの簡単なアプリです。
Ballの重力落下スピードを上げたい場合は、main.dart最下部のMyBox2Dのコンストラクタからgravityを変更します。
/* main.dart */
class MyBox2D extends Box2DComponent {
MyBox2D() : super(scale: 4.0, gravity: -100.0); // ここ
@override
void initializeWorld() {}
}
また、Sampleのままですと結構なスピードでBallが反発し跳ね続けますが、これはWhiteBall(ランダムに発生する白色ボール)に衝突時に反発するプロティが設定されているためです。
これをコメントアウトすることで、不自然な反発がなくなりBallはより自然な挙動で画面下部に溜まります。
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) {}
}
よくスマホいじってると水とかボールを計画的に落下させるようなパズルアプリの広告が出てきますが、これの応用で簡単に作れそうですね。
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
せっかくなのでちょっとだけ改良
そのまま動かすだけではつまらないので、スコアタイムを追加しました。
/* 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);
...
テキストの位置とかは超適当です。
動かしてみる
$ flutter pub get
$ flutter run
結果
スコアタイム表示、超被ってる。。
何はともあれ、Flameを動かしてみることができました