🎮

Flutter x Flame でブロック崩しゲームを作ってみた!

2022/10/15に公開約5,700字

Flutter と Flutterのゲームエンジンである Flame で、
ブロック崩しゲームを作ってみました。

https://youtu.be/IGZ2TnKKzIU

ソースコードは以下です。

https://github.com/Umigishi-Aoi/block_breaker

本記事では

  • Flameの特徴
  • 作成にあたり気をつけたこと(依存関係等)
  • 感想

を記載します。

最後にこのブロック崩しアプリを手元で作ることのできるチュートリアル記事を書きましたので、
そちらを紹介します。

ぜひ読んでみてください!

Flame の特徴

Flameの特徴として、以下について話していきます。

  • Flameの概要
  • ゲームの表示のさせ方
  • ゲームの構成
  • 機能追加の仕方

Flame の概要

Flameはゲームのための独創的な解決策を提供する、 Flutter のゲームエンジン です。
Flameを利用することにより、ゲームを作成するのに必要なコードの簡素化が行えます。

Flutterでは、こちらのパッケージをインストールするだけでFlameを利用することができます。

公式ドキュメントも用意されており、
かなり力の入っているパッケージとなっています。

ゲームの表示のさせ方

Flameでのゲーム作成の開始はとにかく簡単です。
Flameに用意されているGameWidgetを用いて、
作成したゲームを読み込むだけです。

void main() {
  final game = BlockBreaker();
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: GameWidget(
          game: game,
        ),
      ),
    ),
  );
}

(ここでBlockBreakerが作成したゲームを管理するクラス)

たったこれだけで、ゲームが画面表示されます。
めっちゃ簡単です。

ゲームの構成

Flameで作成するゲームの構成要素は大きく分けて2種類に分かれます。

  • FlameGame拡張クラス [1]
  • 各種Component拡張クラス

FlameGame拡張クラス

class BlockBreaker extends FlameGame
    with HasCollisionDetection, HasDraggableComponents, HasTappableComponents {
// ...
}

FlameGame拡張クラスはいわばゲームの土台です。

ここで後述の各Componentを配置(add)することで、ゲームを構築します。

また、FlameGame拡張クラスが前述のGameWidgetにインスタンスを渡すクラスとなります。

各種Component 拡張クラス

class Ball extends CircleComponent with CollisionCallbacks {
// ...
}

Flameには様々なComponentと呼ばれるゲーム構成要素が存在します。
これらを継承したクラスを作成することで、ゲームの様々な登場人物(物体)を作成することができます。

上のBallクラスの例で言うと、CircleComponent
円の形のComponentとなり、
半径や色を設定することで円の物体をゲームに登場させることができます。

他にも、

class Block extends RectangleComponent with CollisionCallbacks {
// ...
}

といった、Rectangle(長方形)のComponentだったり、

class MyTextButton extends TextBoxComponent with TapCallbacks {
// ...
}

といったTextBoxComponentだったりが存在します。

再掲になりますが、
これらをFlameGame拡張クラスに配置(add)することでゲームを構築するわけです。

機能追加の仕方

いざゲームを作ろう、と思って悩むのが、以下だと思います。
「物体のドラッグってどう実装したらいいんだろう?」
「物体同士の衝突ってどう実装したらいいんだろう」

これをFlameは簡単に解決します。

Flameにはドラッグ、タップ、衝突検知など様々な機能を持ったクラスが用意されており、
これらをComponent拡張クラスにmixinするだけで、
そのクラスでmixinしたクラスの機能を使うことができます。

class Ball extends CircleComponent with CollisionCallbacks {
// ...
}

例えば上記Ballクラスであれば、CollisionCallbacksmixinしているので、
衝突時に発火するメソッドを使えるようになります。

これらを利用することで、物体がぶつかった時に反射させたり、壊したりが実装できるわけです。

(やってみると、めっちゃ簡単です。)

ざっくりとした解説になりますが、以上がFlameの特徴となります。

作成にあたり気をつけたこと(依存関係等)

コードを書く上でアプリ作成にあたり気をつけたことを解説します。

依存関係の意識

HasGameRef<T>というクラスがあります。
これをmixinすることで、Component拡張クラス(以下Componentクラス)から、
自身が配置されているFlameGame拡張クラス(以下Gameクラス)を参照できるようになります。

これにより、Componentクラスからゲーム画面のサイズを取得できるので、
Componentクラスの配置がとても楽に書くことができます。

最初はこれを使用してブロック崩しゲームを作成していたのですが、
作成したコードをみて気になったのがクラスごとの依存関係です。

HasGameRef<T>TにはGameクラスを指定します。
そのため、ComponentクラスはGameクラスに依存し、
GameクラスはComponentクラスを配置するためComponentクラスに依存する、
相互参照の関係になります。

感覚的な話になりますが、
これに対しあまり良くないコードだな、と思ってしまいました。

他でゲームを作成しようとした際に、クラスを使い回すことができない、
というのが後付の理由です。

特にComponentクラスがGameクラスに依存するのが違和感がありました。
例えばBallのようなどのゲームでも使いまわせそうな物体が、
Gameに依存しているがために使い回せない、というのが違和感でした。

そこで、現在のコードでは、HasGameRef<T>の使用を廃止し、
以下の図のような依存関係に修正しています。

これにより使いまわしの効くコードとすることができました。

役割の意識

上記修正にあたり意識したのが、各クラスの役割です。

Gameクラスの役割を、
「ゲームに関する設定や処理、物体の配置を管理するもの」とし、
Componentクラスの役割を、
Componentに関する機能や処理を管理するもの」として役割分担をしました。

たとえば物体の位置についてです。
これはGameクラスが役割を持つべきだと考えました。
Componentにはゲームの盤面の知識がないためです。

そのため、以下のコードのように、Gameクラスにて、位置(position)の設定を行うようにしました。

class BlockBreaker extends FlameGame
    with HasCollisionDetection, HasDraggableComponents, HasTappableComponents {

// ...
	
  
  Future<void> onLoad() async {
    final paddle = Paddle(draggingPaddle: draggingPaddle);
    final paddleSize = paddle.size;
    paddle
      ..position.x = size.x / 2 - paddleSize.x / 2
      ..position.y = size.y - paddleSize.y - kPaddleStartY;

    await addMyTextButton('Start!');

    await addAll(
      [
        ScreenHitbox(),
        paddle,
      ],
    );
    await resetBlocks();
  }
// ...
}
  

他にも、「Ballが『何回落ちたか』の知識を持っているのおかしいよな」等を考え、
各Componentが持つメソッドは、そのComponentに関わるものだけになるようコードの修正を行いました。

以上が作成に当たり気をつけたこととなります。

感想

Flameでゲームを作成してみた感想ですが、めっちゃ楽しかったです。
第1版は7時間くらいぶっ続けで作りましたが、楽しんで集中して作成できました。

開発体験も良かったと思います。
座標に対して位置を指定して配置する、というのがFlameの位置指定の仕方で、
好き嫌いが分かれる部分かと思います。
ただ、これは自分は得意な配置の仕方でした。
方眼紙に絵を描いているような感覚でゲームの物体を配置することができたと思います。

物体の反射のロジックだったり、個数変更に強いブロックの位置の計算式だったりを考えるのも楽しかったです。

以下のような図で計算式を考えていました。
反射のロジック

ブロックの配置のロジック

あと結構ニッチな分野だと思うのですが、GitHub Copilotが活躍してくれたのも
印象的でした。

今回作成したブロック崩しのロジックだったり実装だったりを利用すれば色々なゲームが作れそうだな、
と頭の中で考えています。

作成したら記事で紹介しようと思っているので、ぜひ、お楽しみにしていてください。

Flutter x Flame のチュートリアル記事

読むことで今回紹介したブロック崩しが作れるチュートリアル記事を作成しました。

https://blog.flutteruniv.com/flutter-flame-block-breaker-game/

ぜひこちらも併せて読んでみてください。

最後に

今回作成したアプリのリポジトリが良いな、と思ったらスターをいただけると嬉しいです。

https://github.com/Umigishi-Aoi/block_breaker

Flutter大学 というFlutterエンジニアに特化した学習コミュニティに参加しています。
現時点で300人以上が参加する、Flutterを学ぶ人、使っている人の集まるコミュニティです。
以下の招待リンクから参加することができます。
https://flutteruniv.com?invite_id=HgA4mDmN1IQS2xtIUvGLXzN1Erg1

また、週刊Flutter大学というブログの記事作成をしています。
前の週にあったFlutter界隈のニュースなどをまとめたり、アプリ作成の役に立つ知識の記事を書いたりしています。
Flutter情報のキャッチアップに役立つこと間違いなしですので、
ぜひこちらも確認してみてください!
https://blog.flutteruniv.com/

一緒にFlutterを楽しみましょう!

脚注
  1. 正確にはFlameGameComponentの拡張クラスです。
    ただ、役割としては分けたほうがわかりやすいため、分けています。 ↩︎

Discussion

ログインするとコメントできます