オブジェクト指向ゲームプログラミングの悩みどころ

同じようなことを何度も書いてきた気がするが、簡単のため気がしないことにする。
C++(というかオブジェクト指向)でアクション系のゲームプログラミングをするときにいつも困るというか、うまくいかないことがあります。それはゲームに登場するオブジェクト(キャラクタなど)同士のやり取りです。
一般的なゲームプログラムのモデルとして、ゲームの基幹クラスが移動フェーズと描画フェーズにそれぞれキャラクタクラスインスタンスの移動関数move()と描画関数draw()を呼び出す、というのがあります。この場合、各キャラクタクラスはモジュールとして成立していて、新しいキャラクタクラスを作ったときにはそのmove()とdraw()を基幹クラスからの呼び出しに追加すればいい、というのがシンプルで見通しのよい設計と言えます。基幹クラスは各キャラクタクラスを知っているが、逆はなく、また各キャラクタクラス同士のつながりもない、ということなら大変簡単なのですが、ゲームとしてはそういうわけにいきません。一方で、できればコーディングはシンプルに見通しよくしたいものです。
で、ここからつらつらと問題点を書いていたのですが面倒な上にわかりにくそうなので箇条書きにしてみました。

  • キャラクタクラス間につながりがあるとそもそも#includeなどが面倒。シンプルから離れる。
  • 場合によっては循環参照になり、さらにシンプルから離れる。
  • 当たり判定などのために、各キャラクタクラスのmove()が異なる引数をとらなければならなくなり、「キャラクタ」というくくりの(オブジェクト指向的な)意味が薄れる。
  • 上記の理由によりキャラクタ基底クラスによるポリモーフィックな扱いができない(これはid:arCtan:20051119で以前書いた)。
  • キャラクタがキャラクタを生成する場合、一度上位の基幹クラスやキャラクタ管理クラスを介する必要がある。よってそのキャラクタクラスはキャラクタ管理クラスを知る必要がある。キャラクタ管理クラスは当然個々のキャラクタクラスを知っていなければならない。ここでどうしても循環参照が生じる。
  • 循環参照を回避するには、基幹クラスのほうからキャラクタに対して何らかの生成フラグを渡して、それによって新たなキャラクタの生成を通知してもらう必要がある。これでまたmove()の引数が汚くなる。

このように、複数のキャラクタ(自機、ショット、敵、敵弾、ボム、アイテム、地形、etc...)が絡むとどうしてもコードがスパゲッティ化してきてしまうのです。これはつまりキャラクタという相互依存性の高いオブジェクトに対してモジュール化という概念がマッチしないということでしょう。
具体的に今書いているプログラムでもこの問題が起きています。自機がアイテムを取るとボムが発動するようにしたいのですが、まずアイテム側が自機が触れたことを関知するために、アイテムのmove()に自機へのポインタを引数として指定します。さらにボムの生成を通知するために、キャラクタクラスのmove()の呼び出し元である基幹クラスからはボム生成フラグを参照、もしくはポインタとして渡します。アイテムのmove()呼出し後にこのフラグが立っていたらボムを生成する、という感じです。
なんていうか、なんでもない普通の方法に見えるんですが、他の部分がきれいにかけている分キャラクタ関係のところだけなんか汚く見えるんですよね・・・。すごい単純なゲームであってもここだけがスッキリと書けないのが悩みです。