2011年6月19日日曜日

ゲームの設計を考えてみる

ABCのLTで話すネタを考えてます。何にしようかな…

今回はゲームの設計です。Game Coding Completeという本に触発されました。
といっても私はJava歴3ヶ月という新米も新米なので、はずしてたらすいません。

一般的な話として、継承は注意深く行うべきです。
というのも、継承をするとスーパークラスにサブクラスが強力に依存してしまうからです。
といっても、設計とはコードを整理して、見通しをよくすることですが、その整理において、分類というのは強力なツールです。よって、継承は設計において重要です。適切に使えるなら大いに使うべきです。

そして、継承は、フラットに浅く行うべきです。深い階層構造は、柔軟性を損ねる上、適切な設計を難しくします。
ですが、ここで言語の表現能力という壁にぶちあたります。単一継承言語という壁です。多重継承、もしくはMixinをサポートしている言語だと継承関係をフラットに保つことは難しくありません。ですが、単一継承の場合、本来並列の継承関係にすべき場面でも、直列にせざるを得ません。

ゲームにおいて、仕様がころころ変わることはよくあります。というのも、実際に動かしてみないと、おもしろいかどうか分からないからです。私は一人で開発したことしかありませんが、それですら仕様変更はよくあります。これが、デザイナーがプログラマを指示する形だった場合、さらに変更が多くなるのは想像に難くありません。

例えば、Actorクラスを継承する、動かない障害物Obstacleクラスと、パスに沿って動く敵、Enemyクラスがあったとしましょう。途中、仕様変更でObstacleクラスの一部はパスに沿って動くようにするという仕様変更があったとします。
何も考えない場合、Obstacleクラスに動くコードを追加して、Obstacleクラスにパスに沿って動く機能を追加するでしょう。
ですが、ちょっと待ってください。パスに沿って動くコードはすでにEnemyクラスに存在するわけです。そのコードを改めて書くのはもったいなくありませんか?
それならばと、えいやっとコピペ……?いえ、これもまずいです。コピペの害悪については話すまでもないと思います。
ちょっとマシな回答は、こうです。Enemyクラスのパスに沿って移動するコードをActorクラスに下ろします。リファクタリングのレシピにも載ってます。めでたしめでたしですね。

……実はあんまりめでたくありません。これを繰り返すと、Actorクラスがどんどん膨れ上がることになるのです。1クラスに責務を詰め込むのはよくありません。さらに、Actorクラスへの変更はActorクラスを継承するすべてのクラスに波及します。

よって、さらにマシな回答は、Actorクラスを継承するPathActorというスーパークラスを作成することです。そして、Enemyクラス、Obstacleクラスともに、PathActorクラスを継承するように変更します。かくして深い継承関係ができあがりました。

今回のケースでは、これが現実的な回答に思えます。ですが、さらに機能要件が増えたらどうでしょう?それが継承関係に合致する保障はありません。
例えばクラスXを継承するクラスA、B、Cがあり、A・CそしてB・Cにそれぞれ別の機能追加要件があった場合、単一継承言語ではお手上げです。

ならどうすればよいのか?答えのひとつに、コンポジションがあります。ベースとなるオブジェクトに、コンポーネントという形で、機能ごとに実装されたオブジェクトをプラグインするわけです。
ベースとなるオブジェクトには、onMessage(Message msg)のように、メッセージを受信する単一のインターフェイスを用意します。実装は各コンポーネントにメッセージを丸投げするだけです。Messageに応答するか否かは、各コンポーネントに任せます。

以上です。Game Coding Completeを読んで思いましたが、実際ここまでの仕掛けが必要なのは相当大規模なゲームになると思います。
そして、この手のテクニックを見て思うのが、静的言語の硬直性です。この仕掛けは、動的メタプログラミング可能な言語では必要ありません。かといって静的言語の型チェックは捨てがたいのですが……。
私がActionScriptを好きなのは、動的言語と静的言語のハイブリッド言語だからかな、とぼんやりと思います。

0 件のコメント:

コメントを投稿