Flutter + Flame + Riverpodで状態管理しながらゲームを作る


# Flutter Flameについて
## 公式サイト
- https://docs.flame-engine.org/latest/index.html
## 何をするためのものか
- Flutterでゲームを開発するためのエンジン
- 入力系やアニメーション、衝突検知などゲーム開発で頻繁に使用する仕組みを簡単に実装できる
## 何が嬉しいか
- ユーティリティ系のアプリ開発でよく用いられるFlutterを使ってゲームが作れる
    - 最初からスマホでゲームを開発するモチベーションで勉強をするのであればおそらくUnity一択だけど、すでにFlutterに慣れ親しんだ人が簡単なゲーム開発に挑戦してみたいときにちょうどいい
## どんなことができるか
- 2Dゲーム開発
    - https://fundev.jp/flutter-games/
    - https://note.com/welchi/n/n9796036236d6
- 3Dは現状サポートされていないが、サポートに向けて取り組みは継続されているっぽい
    - https://www.youtube.com/watch?v=Mznf-FnDzfM&ab_channel=FlameEngine
    - Flutter GPUという仕組みに依存していそう

# Flame Riverpodについて
## ライブラリ
- https://pub.dev/packages/flame_riverpod

## 何をするためのものか
-  Riverpodの基本的な機能をFlame上で使えるようになる

# デモについて
## どんなデモか
https://codelabs.developers.google.com/codelabs/flutter-flame-brick-breaker#0

- よくあるブロック崩しゲーム
- 画面上部に複数のブロックが整列して並べられ、そこにボールを当てることでブロックが消える
- ボールが画面最下部に落下するとゲームオーバーとなる
- ボールが落下しないように板を操作する

## どんな実装か
- Flutter + Flameでゲーム画面を構成する
- 状態管理はFlutter標準のStatefulWidgetとsetStateを使う

## この記事ではどこが重要になるか
- 状態管理を丸ごとRiverpodに置き換えるところ

# デモをRiverpod化
## Riverpod化することで何が嬉しいか
- 状態管理ロジックを切り出すことで単方向データフローを実現でき、コードの見通しが良くなる

## どこをどのように変更するか
**注意：ここから先はデモを一度実装または読んだことがある人向けに書かれているので、デモの前提知識の説明を省略しています。FlameでRiverpodを使うためのサンプルとして雰囲気で読むことはできますが、何が起こっているか正確に理解したい方は一度デモを読んでみることをおすすめします。**

### game_app.dart
まず、gameインスタンスをProviderで包むことで、インスタンスを取り替え可能にする。ウィジェットをStatefulWidgetからConsumerWidgetに変更し、gameインスタンスをDIで持ってくる。

![スクリーンショット 2024-07-17 23.00.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/72a1c8c0-7a18-d29c-7c55-0e59f44a89f5.png)


後述するが、スコアはFlameGameを継承したオブジェクトを切り出してRiverpodで状態管理するため、ScoreCardウィジェットには引数を渡さず、ウィジェット内でrefから呼び出す。
ウィジェット内でRiverpodを使えるようにするために、GameWidgetをRiverpodAwareGameWidgetへ置き換える。この際、GlobalKeyを指定する必要があるため、前の画像の14行目に定義したkeyを設定する。

![スクリーンショット 2024-07-17 23.01.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/afc097bd-0106-5beb-3d53-11c1907227ce.png)

### brick_breaker.dart
FlameGameを継承したオブジェクトで、ゲームの心臓部になる。ここで他のファイルで定義するボールやブロックなどのコンポーネントを呼び出す。

このファイルでの変更点としては、まずPlayStateというゲームの進行状況の状態をStateProviderに持たせるようにします。もともとBrickBreakerオブジェクトがゲームの処理と進行状況の状態管理という責務を持っていた状況から、状態管理の責務を切り出しています。これでゲームの進行状況の状態を呼び出したい時や、状態に変更を加えたい時は、わざわざBrickBreakerを呼び出さなくてもプロバイダを呼び出すだけでよくなりました。
もとのBrickBreakerのコードでは状態の更新とオーバーレイの更新が同時に行われていたので、変更後のコードではオーバーレイの更新を行うだけの通常のメソッドに変更しています。

![スクリーンショット 2024-07-17 23.18.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/189acd2d-f898-10f8-3976-4338e99133e1.png)
![スクリーンショット 2024-07-17 23.23.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/1d497a17-10b3-6690-5057-84bf4685305d.png)
![スクリーンショット 2024-07-17 23.23.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/776e48c1-f39f-f45c-0517-8f0902218139.png)


onLoadメソッドでは、元コードでは状態の初期化とオーバーレイの更新を同時に実行されていますが、変更後のコードでは状態はプロバイダインスタンス生成時に初期化されているので、それを呼び出してオーバーレイを更新しています。

![スクリーンショット 2024-07-17 23.38.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/9487f11c-f70e-e93b-0716-badd717375e3.png)
![スクリーンショット 2024-07-17 23.29.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/e0763051-ba71-049d-1345-cab338b25b6a.png)


元コードにはonMountメソッドは実装されていませんが、変更後のコードで追加しています。
気付いた方もいるかもしれませんが、元コードでは状態とオーバーレイが同時に更新されていますが、変更後のコードでは状態を更新した後に追加でupdateOverlayメソッドでオーバーレイを更新する必要があります。
通常Flutterのbuildメソッド内にref.listenのコードを記述しますが、FlameGameにはbuildメソッドが存在しないため、代わりにマウント時にaddToGameWidgetBuild関数を使ってビルド処理内にref.listenの処理を追加するコードを記述します。これで、PlayStateに変更があればそれに応じてオーバーレイも変更されるようになりました。

![スクリーンショット 2024-07-17 23.38.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/94df3ba7-3a90-adef-717e-9ae6def95acf.png)

状態の呼び出しや変更は通常のRiverpodの使用感で記述できます。

![スクリーンショット 2024-07-17 23.50.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243690/f00dc194-5cae-8cad-0796-3b790fa4719c.png)


# まとめ
コードには起こしきっているのですが、記事にするには少し量が多かったので一旦ここで切ろうと思います。後はコンポーネントでプロバイダを呼び出して状態に変更を加えたり、スコアのロジックをStateNotifierで切り出してあとはPlayStateと同じように実装すれば大体うまくいくと思いますが、
続きは記事を更新するなり別の記事で投稿します。

記事中にもあったようなaddToGameWidgetBuild内に処理を記述するなど細かいポイントに気をつければ、Riverpodを使って快適なゲーム開発ができるようになるので、ぜひ一度試してみてください。