はじめに
Flutter #2 Advent Calendar 2019の5日目の記事です。
株式会社Tenxia のいまくるすです。
この記事では、10月半ばから転職を機にFlutterを書き始めた私がスマブラのキャラ対策メモ用アプリを作っている話を、採用している技術・アーキテクチャ・ライブラリなどに絡めて書きます。具体的には、Firestore, BLoC/RxDart, json_annotationによるモデルのシリアライズなどについて書きます。
アプリの作りとしては初学者が作るToDoアプリに毛が生えたようなものなので、これからFlutterを初めるような方達の参考になればと思っています。
まだリリースはしていません(最近はポケモンで忙しいので)
スマブラとは?
大乱闘スマッシュブラザーズ SPECIAL | Nintendo Switch | 任天堂 より
https://www.smashbros.com/ja_JP/
人気キャラクターをふっとばして遊ぶ愉快なアクションゲームです
格闘ゲームとして競技(いわゆるeSports)の面でも盛り上がっており、強くなるためにプレイするいわゆるガチプレイヤーも世界中にたくさんいます。私もその一人です(ピーチメイン)。
開発の動機
格闘ゲームではそれぞれのキャラクターのそれぞれの技に特徴があり、対戦で勝つためにはそれらを理解して闘うことが重要になります。その理解のことをキャラ対策と呼びます。(スマブラに限らないことですが)自分の考えをしっかりと言語化して意識しながらPDCAを回すことは上達のために重要です。
現状、多くのプレイヤーはキャラ対策を普通のメモアプリや手書きノートにまとめています。しかし、技の発生の速さ1・後隙2・ガード硬直差3などを確認しながらキャラ毎にまとめてメモを作成したほうが対策が深まるのではないかと思い、開発に着手しました。
作っているアプリの概要
相手キャラクターを選択し、それについてメモを作成できます。
メモの作成ページでは選んだキャラクターのフレームデータが閲覧できます。フレームデータは開発途中なので少しガバガバです。
採用技術
BLoC/RxDart
状態管理にはBLoCパターンを採用しています。採用理由は、見通しを良くするためにビューとロジックを分離したく、かつSwiftでのRx+MVVMの経験があるためすんなり受け入れられたためです。(てかBLoC以外の状態管理方法知らない)
異なるWidget間で同じBLoCを共有するためにbloc_providerを使いました。
モデルの保存、シリアライズ・デシリアラズ
json_annotationとjson_serializableを使って、モデルクラスのエンコード・デコードを行いました。
キャラクターのデータはFirestore上に保存し、アプリ起動時にフェッチしてFighter
という自前のクラスにデコードして使っています。
Firestoreの導入については、公式のまんまやるのが一番早いと思います。(バージョン最新にしようとしてつまづいた)
全体のコードは載せませんが、クラス定義の雰囲気は以下のようになっています。fighter.g.dart
はライブラリによって自動生成された実際のエンコード・デコード処理が書かれたファイルです。
import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart';
part 'fighter.g.dart';
@JsonSerializable()
class Fighter {
final String id;
@JsonKey(name: 'jp_name')
final String jpName;
const Fighter({
@required this.id,
@required this.jpName,
}) : super();
factory Fighter.fromJson(Map<String, dynamic> json) =>
_$FighterFromJson(json);
Map<String, dynamic> toJson() => _$FighterToJson(this);
}
Firestoreから受け取る部分は以下のようになります
final stream =
Firestore.instance.collection('fighters').snapshots().map((snapshot) {
final result = snapshot.documents
.map((document) => Fighter.fromJson(document.data)) // 先程定義したfromJsonでMap<String, dynamic>のdocument.dataをよしなに変換してくれる
.toList()
..sort((a, b) => a.id.compareTo(b.id));
return result;
});
キャラ対策メモについては、Note
というクラスを作成しshared_preferencesを使ってローカルでJson形式文字列として保存しています。
あるページでメモの作成・編集・削除などを行った際に他のページにもその更新が反映されてほしいため、シングルトンを作成してその中でのみメモすべてのデータのStreamを管理する方法を取りました。雰囲気としては以下のようになっています。
class NoteRepository {
factory NoteRepository() => _instance ??= NoteRepository._();
static NoteRepository _instance;
// メモの作成・編集・削除などがあれば、SharedPreferencesを更新するとともにここに変更後のデータを流す
final _notesSubject = BehaviorSubject<List<Note>>();
// Widget内ではこれorこれを変形したものを使う
ValObservable<List<Note>> get notes => _notesSubject;
// 以下にCRUD処理を定義
今後について
- リリースまではもう少しかかります(多分二週間くらい?)。キャラクターのデータを整形してFirestoreに保存する作業がボトルネックです
- 権利関係のことについても調べる必要あり
- リリースまでに追加する機能としては、自キャラの選択、反確4表示などを考えています