Unityのゲーム開発で大抵頭を悩ませるのがメッセージングだ。
メッセージングとは、オブジェクト・コンポーネント間のメッセージのやりとりだ。プレイヤーの持つHPやパラメータ、ゲームのステート、ゲームオーバーの判定等、値が変更された時に他のオブジェクトと連携する機能を指す。
Unityはコンポーネント指向であるため各コンポーネントに保たせた場合は参照が複雑になりやすく、一元管理すると変更通知に面倒な処理を記述するか各アップデート毎にパラメータをチェックするなどの面倒な処理が必要になる。
例えば下のコード。scoreの値が変化した際に影響するオブジェクト(label1~4)がある場合、値の変更に呼応してlabel1~4に変更を加える処理を何処かに書かなければならない。当然この記述をする場合はlabel1~4の参照を設定するか、参照が無くても動作する事を保証するコードも必要だ。上手くいけば後光の輝く素晴らしいゴッドクラスが出来上がり、誰も触れなくなる。
GameObject label1;
GameObject label2;
GameObject label3;
GameObject label4;int Score
{
get{ return score}
set{
score = value;
// label1の変更処理
// 以下略
// ...
// label2の変更処理
// ...
// ...
}
}
もしくは下のコードのように各Updateで値を確認し、変更があれば処理を実行する手もある。当然毎フレーム処理が走るため重い処理は出来ない。
int preScore;
void Update()
{
if( preScore != GameController.Instance.score)
{
// label1の変更処理
// 以下略
}
}
特に面倒なのが参照の確保だ。規模が小さければ問題ないが、規模が増えてくると制御が困難になり、GUIでの配置を捨て全てスクリプトで制御するなどの面倒な方向に走り始めてしまう場合がある。勿論それも方法の一つではあるのだが、addComponentを連打しているようなソースコードは正直見たくない。
NotificationCenterとObserveパターンを参考に変更を通知するプロパティを試作した所、
これがかなり使い勝手が良かった。さすが先人の知恵は格が違った。
これは単純に、値の変更があった際に呼ばれるイベントを登録しておき、
値の変更があれば通知してくれるギミックだ。
上記の例でいけば、例えばlabel1を制御しているコンポーネントにて以下のコードを記述すれば、
変更があった際に自動で通知してくれるため、以下のような処理を記述するのみで変更通知が可能だ。
// GameControllerクラス(singleton)
public NotificationObject<int> score = new NotificationObject<int>(0);
// ... label1の制御クラス
void Start()
{
// 変更通知の登録
GameController.Instance.score.action += ChangeScore;
}void ChangeScore(int score)
{
// label1の変更処理
}
最大の利点は、コンポーネントの参照がそこそこ疎結合になる点だ。これのお陰で、要素が全て揃っていなくても動作するし、要素の追加・削除がかなりシンプルになる。
もし超汎用プロパティが必要な場合、NotificationObjectをDictionaryに突っ込むと良い。Dictionaryに突っ込むと毎フレームアクセスするような構造は避けたくなるので注意が必要。もしstringで参照する場合、メモリを食い散らかして後で酷いgcを受けることがある。
メッセージングのギミックは他にも幾つか存在する。
Unity Community wiki参照の事
NotificationObject使用に当たり3つ注意点がある。
- 呼び出し元のパラメータを変更すると無限ループになる
- 登録したメソッドはコンポーネントを破棄するときにちゃんと解除する
(action -= メソッド名 で解除) - 使える型はプリミティブ型のみ
特にメソッドの解除を忘れるとメモリリークを起こしたり変な挙動したりするので注意が必要。NotificationObjectのプロパティを持つクラスは自身が削除されるタイミングでDisposeを打っておくと吉。
NotificationObjectが正解な環境はかなり少ないと思うが、「手段の一つ」と思えてもらえれば嬉しい。どの手段が最適かは作りたいものに依存する。