Flutterでクソゲを作ってみた話

 (Qiita初投稿です。)

Flutterはゲーム用エンジンではありませんが、僕の(個人的な)自由研究課題として、Flutterゲーム(ぽいもの)を作ってみた、という話です。

またPlayStoreにクソアプリを産み出してしまいました。。。
しかも検証は1端末でしかしてないので、動かないかも。
[Google Play Store](https://play.google.com/store/apps/details?id=com.ktx.ku.shadoweet)
(iOSのAppStoreには、審査に出す勇気がないのでやってません)

長くなりますが、制作過程でトライしたことを以下のように分けて書いていく予定(挫折しなければ)です。

 - アニメーション
 - Blocアーキテクチャについて
 - チャット機能

僕はFlutter初心者なので、間違ったことも書くかもしれませんが、ご容赦ください。

## アニメーション
Flutterでゲームを作る上で一番ネックになるのが、アニメーションです。
Flutterフレームワークには、[AnimationController](https://docs.flutter.io/flutter/animation/AnimationController-class.html)というクラスがあり、これでTweenAnimationとかを表現できたりするのですが、複雑なアニメーションをやろうとするとこれだけだと厳しいです。

そこで見つけたのが、[SpriteWidget](https://github.com/spritewidget/spritewidget)というライブラリです。

ドキュメントが少ないのと、最近更新がないのがちょっと気がかりでしたが、exampleにあるアプリがとても参考になりました。

### SpriteWidgetでできること
* SpriteSheetを利用できます。
  * 但し、TexturePackerで作成したものしか動作確認取れていません。
* ユーザーのTouchEventを拾ってアクションを起こせます。
* 各種アニメーションができます。
  * TweenAnimation(数値の変化によってオブジェクトの位置や状態を変化させるやつ、という説明で合ってる？)
  * 連続、グループ化したAnimationの合成
  * アニメーションの繰り返し
  * Particle systems (爆発とかのエフェクトを表現するやつ)

### SpriteWidgetの導入
```yaml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  spritewidget:
```

### SpriteWidgetを使う
SpriteWidgetはごく普通のWidgetの一種として使うことができます。
アニメーションの描写エリアをrootNodeとして引数で渡して、そのrootNodeの中でアニメーションを描画していきます。
Reactで、仮想DOMを展開するentrypointに似た感じですね。(Reactやったことない)
イメージとしてはこんな感じになります。

```
class MyWidgetState extends State<MyWidget> {
  NodeWithSize rootNode;
  @override
  void initState() {
    super.initState();
    // 描写エリアのサイズを定義する。
    rootNode = new NodeWithSize(const Size(1024.0, 1024.0));
  }
  @override
  Widget build(BuildContext context) {
    // SpriteWidgetに描写エリアを設定する。
    return new SpriteWidget(rootNode);
  }
}
```


## 画像を読み込む
SpriteWidgetでは、単一の画像の場合はImageクラス、SpriteSheetの場合はSpriteTextureクラスで扱います。
(FlutterのフレームワークではこのImageクラスをwrapしているので、普段目にすることがない？)
よって、以下のようにimportする必要があります。

```
import 'dart:ui' as ui show Image;
```

単一の画像を読み込みたい時は、

```
ui.Image image = await images.loadImage('assets/my_image.png');
```

複数の画像をまとめて読み込みたい時は、

```
ImageMap images = new ImageMap(bundle);
await images.load(<String>[
		'assets/image_0.png',
		'assets/image_1.png',
		'assets/image_2.png'
]);
// 画像を取得
final image = images['assets/image_0.png'];
```

読み込んだ画像をSpriteSheetとして扱いたい時は、

```
// 読み込んだ画像をSpriteSheetとする。
SpriteSheet sprites = new SpriteSheet(image, jsonCode);
// SpriteSheetからtextureを取得
SpriteTexture texture = sprites['texture.png'];
```

画像のloadは、initState()でやるといいと思います。

## オブジェクトを配置
描写エリアの設置と画像の読み込みができたら、描写エリア内にオブジェクトを設置しましょう。
オブジェクトはSpriteクラスで表現し、引数にImageかSpriteTextureを渡します。

```
  Sprite car = new Sprite.fromImage(carImage);
  car.position = Offset(rootNode.size.with / 2, rootNode.size.height / 2);
  rootNode.addChild(car);
```
こんな感じで描写エリアの中央に車のimageを配置できます。

## オブジェクトにアニメーションをつける

いよいよ、アニメーションをつけます。
アニメーションを実現するためにいろいろなクラスが用意されてますが、よく使うのはこの辺りでしょうか。

- ActionTween
- ActionSequence
- ActionRepeat
- ActionCallFunction

Animationは基本的に以下のような流れで実行します。

```
  Sprite car = new Sprite.fromImage(carImage);
  final action = ...// 後述のActionを定義
  car.actions.run(action);
```

### ActionTween
いわゆるTweenAnimationです。
数値の変化によって、オブジェクトのpositionを変えたり、transformしたりします。

```
ActionTween action = new ActionTween<Offset> (
		(a) => car.position = a,   // carのpositionを移動
		Offset.zero,               // 初期位置
		const Offset(100.0, 0.0),  // 終点位置
		1.0                        // 時間
);
```

### ActionSequence
連続したアクションを定義します。

```
ActionSequence sequenceAction = new ActionSequence([
		firstAction,
		middleAction,
		lastAction
]);
```

### ActionRepeat
同じアクションを指定回数繰り返します。

```
ActionRepeat repeatAction = new ActionRepeat(loopedAction, 5);
```

### ActionCallFunction
Actionの流れの中で、任意の処理を実行します。
これ、公式のREADMEにないんですが、アニメーションのコールバックを実現するためによく使います。

```
ActionSequence sequenceAction = new ActionSequence([
		new ActionCallFunction(() {
			print('beforeAction');
	  }),
		firstAction,
		middleAction,
		lastAction,
		new ActionCallFunction(() {
			print('afterAction');
	  }),
]);
```

## 所感
正直SpriteWidgetで複雑なアニメーションを実装するには、まだ色々不足しているなぁ、というか結構泥臭い実装しないといけないので、辛いです。
SpriteSheetに対応しているくせに、パラパラ漫画みたいなTextureの切り替えを簡単にやる方法が見つからないとか。
ゲームエンジンとして使う分にはまだ少し辛いかもですが、ちょっとしたアニメーションをやりたいっていう時にはとてもいいライブラリなのではないかと。


すごい雑でまとめきれていないですが、疲れたので一旦ここまで。
(READMEにあることほとんどなぞってるだけじゃねーか！と思った方、正解です。)
ただのSpriteWidgetの紹介記事になってしまった。

そういえばFlutter1.0 でましたね。
2DimensionsのFlareとか使ってみたいです。