簡単に1行で
Unity Game Engine(以下Unityと記す)におけるフラグ管理・状態管理ライブラリ
Unityにおけるフラグと状態の管理
基本的にユーザに任されているのが現状である。
Unityはシーンレベルまでの状態管理しか提供しない。そしてUnityのシーンローディングはそれなりに重量級の処理だ。
より小さなレベルでの状態管理、また、雑多になりやすいフラグ管理を適度な制御範囲(グローバル過ぎず、ローカル過ぎず)で提供することが必要だと考え、このライブラリを制作した。
愚直にフラグを管理した場合
シナリオ
Unityで、MonoBehaviourを継承したオリジナルな挙動をするクラスを作るとする。これがアタッチされたGameObjectをAlphaとする。
Alphaは単純に、Transformをz方向に進ませるだけのものである。
仕様変更があった。Alphaが一定以上進んだ際、別のGameObjectであるBetaを出現させることになった。
よって、Alphaに対し一定以上のzをfloatのインスタンスフィールドにて記述して、一定以上進んだら出現させ、出現済みフラグを立てる処理をした。
仕様変更があったAlphaがx方向にも動くようになった。GameObject Betaが出現済みで、かつx方向に一定以上進んだ際、別のGameObject Gammaを出現させるようにしたい。
同様にfloatにて一定以上のxをインスタンスフィールドにし、Betaの出現フラグを見つつxが一定以上かどうかを監視し、満たした際に新たにGammaを出現させ、出現済みフラグを立たせることにした。
問題点
処理が複雑に絡み合い過ぎている。
Alpha内部の毎フレームの処理が、ベタに、大量に、冗長に記述されている。
Betaの出現処理とGammmaの出現処理は、フラグのみによって関連しているだけなのに、1つのクラス内にあるのは不自然である。
今後も仕様変更が重なり、さらに複雑なGameObjectの出現手法が求められた際、対応が不可能になることが予測される。
シーングローバルなマネージャにてフラグを管理した場合
シナリオ
愚直なやり方はまずい、というわけで、フラグを、シーンレベルにグローバルな位置にあるManagerにて管理することにした。
このGameObjectのManagerクラスはbool値を大量に持ち、フラグ管理を任されている。
GameObject Alphaに対して、BetaEmergerというBetaを一定の条件で出現させるクラスが追加された。
同様にGammaEmergerも追加された。これらのEmergerクラスは、Managerへの参照を持ち、ここにあるフラグを読み込み・変更するものである。
仕様の追加があった。現在のAlphaの処理をそっくりそのまま、別のシーン(2つ目のシーン とする)でも動くようにしたいという要求が出た。
AlphaにはEmergerクラスがあり、これはManagerクラスを知る必要がある。
新たなシーンにAlphaとEmerger2つ、そしてManagerクラスを丸ごとPrefabでコピーした。
仕様の変更があった。最初にあったシーンとSecond SceneではAlphaは若干別の挙動をしたい。
Emergerを追加した。今までの処理にならい、Managerにフラグを追加した。
最初のシーンと2つ目のシーンにあるManagerは同じものだが、違う処理のために、不要なフラグを共有している状態になった。
問題点
このままいくとManagerクラスが肥大するだろう。
Managerクラスを分割したい場合、Emergerから、共通の型でManagerにアクセスできないため、ManagerごとにEmergerが必要になる。
Emergerを共通の処理にしたければ、interfaceでくくるほかないが、これではフラグ管理のためだけにやたらとコード数の肥大を招きかねない。
フラグはどの位置で管理されるべきか
上記した2つのシナリオから、インスタンスレベルでのフラグ管理も、シーングローバルレベルのフラグ管理も、どちらも問題点をはらんでいることがわかるだろう。
では、どの位置で管理されるべきか、だが、当然のごとくこの2つの中間層だ。
中間層とはどこか。Unityならば、Transformレベルでフラグは管理されるのが最も望ましい、となる。
Prefabレベルでのフラグ管理
シナリオ
基礎となるのはManagerクラスでの発想である。
問題点と、それに対してどのように対処するべきか、を以下に記す。
1.
Managerクラスは、すべてのフラグを一手に請け負おうとしたから破たんした。
特定のフラグのみを管理し、それ以外は管理しない、という姿勢でManagerクラスを変更するのが基本的な指針だろう。
2.
また、Managerクラスは内部にフラグの値(bool)を持っていた。
だが、本質的に見ればManagerは各クラスからのアクセスのためにフラグを保持しているのであって、保持する必要があるから保持しているのではない。各クラスからアクセスができれば何でもよい。
3.
さらに、Managerクラスは、別のシーンにクローンされ、シーンごとに細かなカスタムが加えられていったため、構造に破たんをきたした。
よって、Managerが管理するフラグは、十分に柔軟に変更されるデータ構造を持ち、型としてのフラグ一覧を持たないほうが良い。
まず1,2より、非常にシンプルなフラグのクラスを作ることが望ましいだろう。
なぜか。2.からわかる通り、フラグは、共有したい他の挙動の各所からアクセス可能であればそれで良い。
フラグクラスをMonoBehaviourから派生するものとし、1つのboolとそれへのアクセサを持つものとする。
これで、このMonoBehaviourの参照を各挙動に追加することで、フラグへの共有アクセスが可能になった。
さらに、3.の条件を満たしたい。これがなければフラグ管理部は肥大する。だがこれも、MonoBehaviourから派生したことで解決できる。
Unityには高度なオブジェクト階層システムがある。フラグひとつひとつをGameObjectとし、これらをTransform(自動で追加される)によるGame Object階層にて管理すればよい。これは十分に柔軟なデータ構造として働く。
S3 Engineにおける実装
上記した 非常にシンプルなフラグのクラス はSignalクラスとして実装されている。
また、フラグは、時として状態としても振る舞う。ゆえに、状態を示すクラスと、現在の状態をホールドするクラスが必要だと考えた。
これらを、StateクラスととSlotクラスとして実装した。
また、状態間を移動することを容易に、かつ安全にするためのFilterクラスを用意した。
Filterは、特定のSignalの条件を満たした際に、Slotのホールドする現在の状態を、目的の状態に移動させる。
余談だが、これらのSlot,State,Signalの頭文字を取ってS3 Engineと名付けた。
0 件のコメント:
コメントを投稿