はじめに
Flame公式ドキュメントを読み下していく第7弾の今回はエフェクトコントローラーです。
エフェクトコントローラーというのはエフェクトと同時に使ってエフェクト処理側に、エフェクト処理の1周を100%としたとき、0~100%のいまどこかを伝えるためのものです。
何秒かけてエフェクトを1周するかを与えると、その秒数をかけて0~100%にその進捗の数字を進展させます。
また逆回転の機能もあって、この場合は減らしていきます。
割と単純ですが、こういう風にエフェクトの効果と、時間の刻みを分離する設計というのはうまいですよね。
自前でオリジナルのゲームの演出を作っていく際にも、このエフェクトコントローラをうまく活用できると様々に再利用の効くモジュールになるので参考にしたいところです。
エフェクトの制御
EffectController
EffectControllerのベースクラスはファクトリコンストラクタを提供していて、様々な用途で使える共通のコントローラーを生成します。コンストラクタの記法は次の通りです:
EffectController({ required double duration, Curve curve = Curves.linear, double? reverseDuration, Curve? reverseCurve, bool alternate = false, double atMaxDuration = 0.0, double atMinDuration = 0.0, int? repeatCount, bool infinite = false, double startDelay = 0.0, });
- duration:エフェクトの主要パートの時間の長さ。すなわち進捗0から100%になるまでにかける時間。このパラメタは0以上の数でなければならない。この時間が指定されたら、エフェクトはその時間の間、時間に比例する形で進展していく。必須なのはこのパラメタのみ。
- curve:このパラメタが指定された場合、エフェクトが非線形に0から100%に向けて進展する。
- reverseDuration:このパラメタが指定された場合、コントローラは追加のステップを加えエフェクトが100%までdurationで与えられた時間をかけて進展した後、100%から0にreverseDurationで指定された時間をかけて戻す。通常は、進捗レベルは1で終わる。
- reverseCurve:逆回しのステップのときの進展のカーブを設定。もし設定されない場合、curveを反転させたものがデフォルト設定になる。
- alternate:これをtrueに設定すると、reverseDurationをdurationと同じ値に設定したのと同等になる。reverseDurationを別途設定した場合は、こちらのフラグは無効になる。
- atMaxDuration:もし非ゼロの値が設定された場合、エフェクトが最後まで進展したところで逆戻りの段階になるまでに一旦与えられた時間停止する。エフェクトは100%の状態を保つ。逆戻りの段階が設定されていない場合、シンプルに一旦停止したのち、エフェクトが完了したという扱いになる。
- atMinDuration:もし非ゼロの値が設定された場合、逆戻り段階を経て進展が0になったあと、与えられた時間停止する。逆戻りの段階が設定されていない場合、100%の段階の停止の後で、こちらで与えられた一時停止をさらに加える。エフェクトは進展レベル0で終わる。
- repeatCount:1より大きい値が設定された場合、エフェクトは与えられた数、繰り返される。毎回の繰り返しで前向き進展・進展マックスでの停止・逆戻り進展・進展ミニマムでの停止を繰り替えす。逆戻りや一旦停止の時間が設定されていなければ、その段階はスキップする。
- infinite:もしtrueに設定されれば、そのエフェクトは無限に繰り返される。これはrepeatCountを無限に設定することと同値。
- startDelay:エフェクトが開始する前にウェイトを入れる。ウェイト時間はエフェクトが繰り返されても1回のみ適用される。この期間、エフェクトの.startedプロパティはfalseを返す。onStart()コールバックはこのウェイト期間が終わったときに呼び出される。このパラメタはエフェクトを連続でつないでいくもっともシンプルな方法として使われる。
エフェクトコントローラーは、ファクトリコンストラクタを使うことで、次に説明する各種エフェクトよりシンプルなエフェクトを提供できます。もしこのコンストラクタが提供するコントローラで不足があるならば、あなた自身で作り出すこともできます。
ファクトリコンストラクタに加えて、EffectControllerは全てのエフェクトコントローラにおける数々の共通のプロパティを定義します。これらのプロパティは以下の通りです:
- .started:trueであるときは、そのエフェクトがすでに開始されていることを意味する。ほとんどのエフェクトにおいてこのプロパティは常にtrue。例外はDelayedEffectControllerがエフェクトの開始前のウェイト段階になっているときのみ。
- .completed:エフェクトコントローラの実行が完了したときにtrueになる。
- .progress:現在のエフェクトの進展状況を0から1の間の浮動小数で表す。この値がエフェクトコントローラーの主要なアウトプットとなる。
- .duration:全てのエフェクトの時間。あるいは時間が決められない場合はnullになる。例えば期間がランダムであったり無限である場合。
エフェクトコントローラーの機能はほぼすべて、このEffectControllerのコンストラクタにパラメタを設定することで実現されます。
次の節からは個々の具体的な機能を持つエフェクトコントローラーのクラスが紹介されていきますが、これらはEffectControllerのコンストラクタで適切なパラメタを設定すれば、そのファクトリコンストラクタが下に続くクラスのうち適切なエフェクトコントローラーの種別を判断して、そのインスタンスを返してくる形になります。
「ファクトリコンストラクタ」とは、ファクトリパターン(factory pattern)という有名なデザインパターンで使われる技法です。Dartでは公式にfactoryというキーワードがあって言語的にファクトリパターンを取り込んでいます。
どういうパターンかというと、与えられたパラメタによって、適切なクラスを選んでその実装を返す方法を指しています。ここでいうとEffectControllerというのは、これ自体は抽象クラスで、以下に続くLinearEffectControllerとかその下に続くクラスが実際に機能するクラスなのですが、これらを代表して入り口を一つにしているのがポイントです。どれか一つをえらぶというより組み合わせてパラメタに合わせたレシピを返してくる形になっていると思います。
ファクトリパターンはこのキーワードで検索するとたくさん出てきますので色々な記事をみてみるのをおすすめします。もしこのあたりをしっかり勉強されたいのであれば下記の書籍あたりがおすすめです:
Java言語でサンプルが書かれていますが、DartとJavaは非常に似ているのでそこは問題ないかと思います。オブジェクト指向の真髄を学ぶにはデザインパターンは必修です。そこまで気にしなくても動くプログラムは十分書けますけれども、しっかり理解するとエレガントで非常に再利用性の高いプログラムを書くことができますし、Dartなどモダンな言語の仕様がどうしてそうなっているのかをよく理解できたりします。
LinearEffectController
一番シンプルなエフェクトコントローラーで、与えられた時間をかけて線形に0から1に進展する。
final ec = LinearEffectController(3);
一直線に進捗が進む基本形。基本的には時間の間にどういう割合で進展割合の数字を進めていくかをコントロールするのがEffectControllerの仕事です。線形とはまっすぐ、ということです。均一なペースでピッタリ時間を使い切る形で進展を0から1(100%)にあげていきます。
ReverseLinearEffectController
LinearEffectControllerと似ているが逆向きに1から0に与えられた時間、線形に進展していく。
final ec = ReverseLinearEffectController(1);
LinearEffectControllerと逆に、一直線に1から0にカウントダウンしていきます。
CurvedEffectController
このエフェクトコントローラーは与えられた時間で非線形に0から1に進展させていきます。
final ec = CurvedEffectController(0.5, Curves.easeOut);
サンプルでは「Curves.easeOut」という曲がり方で0.5秒かけて進展、という風にパラメタが与えられています。
このCurves.easeOutとはどんな曲がり具合かというのは、下記のページの下の方にアニメーションとグラフでの曲がり具合の説明がありますのでご参照ください:
api.flutter.dev
グラフの一覧のなかにCurves.easeOutが見つかると思います:
ReverseCurvedEffectController
CurvedEffectControllerの逆進展版。1から0に与えられた時間で非線形に進展。
final ec = ReverseCurvedEffectController(0.5, Curves.bounceInOut);
Linearのリバースと同様ですね。Curves.bounceInOutも前節のリンクのなかからグラフアニメーションを見つける事ができると思います。バウンスしているので、跳ねていますね。
PauseEffectController
特定の進展値のとろで、与えられた時間、一時停止する。0や1の段階で使われるのが一般的。
final ec = PauseEffectController(1.5, progress: 0);
これを使ってEffectControllerのatMaxやatMinのところの一時停止を実際には動かしているということですね。
RepeatedEffectController
これはエフェクトコントローラーを組み合わせます。他のコントローラを子にもち、複数回これを反復します。
final ec = RepeatedEffectController(LinearEffectController(1), 10);子コントローラーは無限設定ができません。もし子コントローラーがランダムの期間をもつ場合、繰り返しのたびに新しくランダムな値が設定されます。
サンプルにあるのはLinearEffectController(1)を10回リピートということですね。
InfiniteEffectController
RepeatedEffectControllerと似ていますが、こちらは子コントローラーを無限にリピートします。
final ec = InfiniteEffectController(LinearEffectController(1));
際限なくリピートということですね。普通のゲームでは回数制限なく繰り返すものの方が多いですよね。
SequenceEffectController
子コントローラーの列を順に実行していく。
final ec = SequenceEffectController([ LinearEffectController(1), PauseEffectController(0.2), ReverseLinearEffectController(1), ]);
これを使うことでエフェクトを組み合わせて複雑な動きを一つのエフェクトに抱き合わせることができます。
冒頭で説明していたEffectControllerのファクトリメソッドは、与えられたパラメタをもとにここでズラッと紹介している具象クラスのインスタンスを選択したり、このSequenceEffectControllerで組み合わせたりしているわけです。
DelayedEffectController
与えられたEffectControllerを与えられた時間分遅らせて開始する。
final ec = DelayedEffectController(LinearEffectController(1), delay: 5);
開始前のディレイを作るEffectControllerです。
おわりに
前回述べたエフェクトと、今回のエフェクトコントローラを分離する設計がとてもいい具合で参考になります。
それ以外にも、抽象クラスにfactoryコンストラクタを備えてパラメタを受付け、そのなかで適切な具象クラスを組み合わせて複雑なエフェクトを構築するサービスをしているのもいい感じですね。
もちろん自前でエフェクトは構成できるのですが、かなり煩雑だったりします。完全に自由に構築するには自前で組み立てる必要がありますが、ほとんどのケースについてはこのfactoryコンストラクタでそのニーズを吸収しています。
ちなみにfactoryコンストラクタの部分がデザインパターンで言うファクトリパターンなのですが、このエフェクトコントローラーを組み合わせて複雑なエフェクトコントローラーを組み上げていくのは、デザインパターンのなかで「コンポジットパターン」と呼ばれるものです。(例えば下記を参照してください)
www.techscore.com
このあたりを使いこなせるかどうか、あるいはせめて他の人が作ったライブラリをみたときにその意図がわかるようになると、高度なソフトウェアを構築していけるようになります。逆にこういう知識と経験を積み重ねないと、ソフトウェアの規模が少し大きくなっただけで息切れしてしまうことになります。
もしプログラミング言語はわかった、APIの使い方ははわかる、でもどうしてもソフトウェアを作りきれないという方は、一度こうしたオブジェクト指向の原点を学んでみると良いと思います。