ゲーム開発のためのオブジェクト指向〜Mediatorによる衝突判定の分離
実は、kenmoが今作っているゲームのソースコードが結構大変なことになっちゃっていたりします。
というのは、ゲームのメインとなるシーンのクラス(SceneMain)に、
たくさんのTask(オブジェクト)がぶら下がっているからです。
…えーと、こんな感じ(´д`;
つ
まあ、ぶら下がっているTaskが2つぐらいなら、この方法でも良いのですが、
3つ以上になると、それぞれのTask同士の通信(主に当たり判定とか)が複雑になってしまい、
SceneMainクラスの仕事が増えたり、変なフラグが増えたりして、ブクブクと太ってしまいます。
…そんな感じで、
(あと、ほかにも5,6個Taskがあって)
おかげでSceneMainが1000行を突破しちゃっています。
(まあ、1000行くらいたいしたことないのかもしれませんが…)
そこで、
「何とかせねば…」
と考え、とりあえずリファクタリングした結果、こうなりました。
階層を1つ増やし、
Taskの生成・更新・削除を管理する、TaskManager(デザパタで言うFactoryクラス)
を導入しました。
これにより、SceneMainでnew/deleteを呼ぶ必要がなくなり、多少スッキリしました。
…しかし、これだけは単にオブジェクトの管理層を追加しただけです。
例えば「当たり判定」のような、Task同士(最下層)の通信をするためには、
あいかわらず、上位層のSceneMainまでTaskを引っ張り出す必要があります。
(最下層でお互いに通信することはできないので)
そこで、Mediatorパターンの登場です。
Mediatorとは「仲介人」の意味で、ここでは「最下層の通信を一元管理」する役割を持ちます。
つまり、
「Mediatorに当たり判定をやらせてしまおう!」
というわけです。
で、Mediatorを加えると、こんな感じです。
なんか線がたくさん増えて、キモイクラス図になってしまいましたが…(´д`;
これは何をしているかというと、各TaskManagerがMediatorを参照しています。
そして、TaskMediatorも各TaskManagerを参照しています。(相互参照ですね)
こうすることにより、
各TaskManagerに更新(update)があった場合、
それをTaskMediatorの「通知(update)」を呼び出すことにより、
TaskMediatorに当たり判定を任せてしまうことができます。
フローは以下の順番になります。
- SceneMainがTaskManagerのupdateを呼ぶ
- TaskManagerはTaskのupdateを呼び、Taskの座標を更新する
- その後、TaskManagerは自分を更新されたことをTaskMediatorに通知する。(TaskMediatorのupdateにthisポインタを渡して呼ぶ)
- TaskMediatorのupdateでは、渡されたTaskManagerのidを見て、それぞれの当たり判定を行う
コードの例としては、以下のようになります。
1.SceneMainクラス
SceneMain::update() { // TaskManagerを全て実行 Iterator it = taskManagerList.getIterator(); while(it.hasNext()) { ((TaskManager)it.next()).update(); // ->2へ } }
なんと、SceneMainは、TaskManagerのupdateを呼ぶだけでよくなりました。
2/3.TaskManagerクラス
TaskManager::update() { // Taskの座標を全て更新 Iterator it = taskList.getIterator(); while(it.hasNext()) { ((Task)it.next()).update(); } mediator.update(this); // ->4へ }
まず、保持しているTaskを全て更新します。
そして、最後のところがキモです。
TaskManagerがTaskMediatorを呼ぶ、という「制御の逆転」を行っています。
(従来の手続き型プログラムとは反対の考え方ですね)
4.TaskMediatorクラス
TaskMediator::update(TaskManager taskManager) { switch(taskManager.getId()) { case PLAYER: // プレイヤータスクだったとします。 Iterator it1 = taskManager.getTaskList().getIterator(); while(it1.hasNext()) { Task task1 = (Task)it1.next(); // 当たり判定の対象は、「敵」 TaskManager mgr2 = (TaskManager)taskManagerList.get(ENEMY) Iterator it2 = mgr2.getIterator(); while(it2.hasNext()) { Task task2 = (Task)it2.next(); // 「プレイヤー vs 敵」の当たり判定を行う if(isCollide(task1, task2)) { taskManager.remove(task1); mgr2.remove(task2); } } } break; case ENEMY: … break; … } … }
まあ、ちょっと香ばしい書き方ですが…(´∀`;
とりあえず、これで、当たり判定処理をSceneMainから分離できましたぜぇ!!
…でも実は、このままだと、例えば、
「プレイヤーの更新後の座標」と「敵の更新前の座標」を比較してしまうので、
変な当たり判定を行ってしまう可能性があります。
そこで、厳密にチェックを行いたい場合は、TaskManagerを全て更新した後、
TaskMediatorに通知を行う必要があります。