MonoBehaviourを問い直す
あらまし
Unityを使ってソフトウェアを作る際に、ほぼ必ず使うであろうコンポーネントがMonoBehaviourである。MonoBehaviourはゲームスクリプトをコンポーネント指向の思想に基づいて、ゲームのシーンを構成する最小単位であるGameObjectにアタッチして利用する。その名前が表すとおり、MonoBehaviourはMono(C#)で記述されたゲームオブジェクトのBehaivour(振る舞い)である。スクリプタブルなゲームオブジェクトは、必ず個別にその振る舞いを持つ。ゲームはゲーム自体のロジックによってその進行は変わるものの、基本的にはフレームという単位でリニアに進行する。
MonoBehaviourは、Unityであらかじめ決められたイベントに基づいたイベント駆動のコンポーネントである。色々なイベントがあるが、実際に重要なのはUpdateである。Updateはfpsに関わらず毎フレーム実行される。Unityではグラフィックに関するプログラム的記述をMonoBehaiourにゴリゴリ書くことは基本的にはないので、実際はゲームオブジェクトの状態を変化させる記述を行う。具体的には、ゲームオブジェクト固有の数値やブーリアンである。transform.positionなどはよく使う典型的な状態である。
MonoBehaviourの問題点を指摘する前に、MonoBehaviourのよいところを考える。個人的にMonoBehaviourの一番の利点は、スクリプトが疎結合になりやすいという点だ。あるスクリプトはそれがどのゲームオブジェクトにアタッチされたとしても同じように動作する(べきである)。もしあるスクリプトが特定のゲームオブジェクトでしか動作しないのであれば、MonoBehaviourを使うメリットは、その基本的な機能以上はない。
コンポーネント同士が疎結合である、というのはソフトウェア設計の理想だが、あくまで理想でしか無い。ソフトウェアの中にはそれがどれだけシンプルなものに見えたとしても、数え切れないほどの状態とそのスパゲッティな遷移が存在する。FSMはコントローラブルなソフトウェアの理想形に思えるが、実際はひとつのオブジェクトがひとつのFSMのみを持つということは現実的ではない。そして多層的になったFSMは開発者の認知を容易に越えてしまう。
たとえば典型的なゲームオブジェクトであるプレイヤーと敵キャラを考えよう。プレイヤはー入力に従って移動するが、敵キャラはプレイヤーの位置を知っていて、寄ってきて追尾する。普通だ、よくある光景だ。
だがしかし、もうこの時点でMonoBehaviourは破綻している。PlayerとEnemyは同じシーンにある別々のオブジェクトである。別々のオブジェクトなので別々のMonoBehaviourで記述されている。だが、敵キャラはプレイヤーの位置やその状態をしる必要がある。つまり、EnemyはPlayerに依存しなくてはならない。毎フレーム、Playerの最新の状態を知る必要があり、実際にはゲームシーンからPlayerオブジェクトを探し出し、publicフィールドを参照して自身の振る舞いを変える必要がある。
だとすると、もはやPlayerとEnemyのスクリプトが独立している必然性やメリットはなく、ナントカManagerのような神クラスがPlayerの振る舞いもEnemyの振る舞いも記述したほうが(一見は)合理的に思えてくる。
この手の問題意識はゲーム以外のソフトウェア、とくにWebの世界では浸透していて、毎年のように新しいパターンが提唱されては廃れていく。銀の弾丸はない、だがいつまでも石を投げ続けるわけにもいかない。
Webの世界でいま注目されているのは、Fluxというアーキテクチャである。Fluxの特徴はいくつかあるのだが、基本的には古典的なイベント駆動パターンである。ただその特徴として重要なのは、アプリケーションにおけるデータストリームをただひとつにして、そのpublisherからsubscriberまでのデータの流れを単一にするということである。これをSingle Directional Data Flowと呼ぶ。
データストリームの流れが一方向というのは、subscriberはそのイベントがどこで誰が起こしたかを知る必要はない、知ってはいけないということである。(実際に知る必要があるのであれば、イベントのデータにそれを含める)
イベントのpublisherにsubscriberがアクセスできると、subscriberとpublisherが密結合になりうる。すると、