Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
[go: Go Back, main page]



11/4 ゲームプログラミングとオブジェクト指向



Game Program vs Generic Opelation Systems
ゲームプログラム VS 汎用OS

     今日はマジメなPrejudice。

     あまり知られているようで知られていない話だが(知らなくてもぜんぜんいいんだけど)おれって、へんな学校でへんな講師のバイトやってるのね。

     そんで、いまは二つの講座を開いてるんだけど、ひとつは「3Dプログラミング」ってやつで、こりゃもうお得意のアレだから今更ないようを説明するまでもないんだが、もうひとつが「ゲームプログラミング概論」っつー名前の厳めしい奴で、内容的には一般的なゲームプログラムの原理と仕組みを説明するって奴なんだが果たしておれがそんなこと教えていいのかとか思いつつ、お金欲しさに魂売ってエラソーにホザいてるワケよ。

     最初のころはテキトーにスケルトンを解説して遊んでいたんだが、さすがに二時間喋り通しはツライので、「卒業制作のため自習」とかテキトーに理由をデッチあげて寝てた。だけどまぁ寝るのにも飽きたし、最近はそっちの講座で、もういちど「ゲームプログラミング」について説明することに決め、まぁなんとかやっている。

     今日の話題は、その「ゲームプログラミング」について。

     DDJ(ドクター・ドブズ・ジャーナル/翔泳社刊)で中村正三郎氏が、岩谷宏氏の「パソコンを疑え!(講談社新書)」を徹底批判してたよね(笑)。

     この本には「ゲームプログラミングは原始的」なんていう部分が引用されていて、それを中村氏が批判しているんだけど、たしかにうなずける部分があったよね。

     僕も該当個所だけは一応読んだけど、岩谷氏の主張を要約すると「ゲームプログラムはハードウェアに依存するから原始的」「それまでのコンピュータの歴史的資産を無視している」ってことなんだが、おいおいちょっと待ってくれよって感じだ。

     ゲームプログラムがハードウェアに依存する、それはハードウェアの能力を極限まで引き出すためだ(中村氏もそう反論している)。そして、ここを勘違いしないでもらいたいんだけど、ゲームプログラムのすべてがハードウェアに極限まで依存しているわけではないってこと。

     岩谷氏が絶賛するLinuxにしても、Unixに極限まで依存しているでしょ。そしてUnixの、X-Window向けに作られたプログラムをWindowsに移植するのは一苦労だ。

     だけどじゃあ果たして「原始的」なDOOMはどうかというと、LinuxはおろかDOSでもWindows95上でも動く。無論移植したからだけど、ここにはOS特有の機能と関係なくプログラムが存在するという、ゲームプログラム固有の特徴が深く関係している。

     岩谷氏が必要性を訴え、そして大絶賛するUnix(Linux)は、それに依存するプログラムを書いたら、残念ながら他のOS、プラットフォームに移植するのはかなり厳しい。X-WindowとWindows95、MacOSはそれぞれパラダイムが似ているにしても、さまざまな違いがあるし、機能の差も、ファンクションコールの名前も、呼び方もいちいち違う。ほとんど最初から作り直しって言う感じの作業であることは間違いない。

     だがしかし、ゲームプログラムはどうか。DOOMをWinGに移植した時、わずか二日しかかからなかったという逸話があるよね。
     他にも、OpenGL用に書かれたQuakeをDirect3Dに移植するのに、素人のバイト学生がたった二週間でできてしまったという話もある。

     要するに、ゲームプログラムがハードウェアに依存するのは、実はほんの少しのうわべの部分だけなんだよね。それさえ移植できれば、あとはただ(アセンブリ言語なりC言語なりを)コンバートするだけの作業であり、ここはサルでもできる。

     もっとも、うわべだけの部分の移植が一番しんどいんだけど、それでもOSの機能に頼りすぎてゼロから作り直さなくてはならないようなゲームプログラムは極めて希で、普通はなにも考えずに移植できる。それが例えばC言語で書いてあったとしたら、一行も変更しないで使うことだってできる。

     たとえば、僕なんかはPC-9800シリーズ上で動くように作ったお手製の座標変換・回転計算ルーチン(懐かしの「Cで目指せ!びゅんびゅんポリゴン」で使った奴だ)を、98のGDC(グラフィックス・ディスプレイ・コントローラ)とEGC(エンハンスド・グラフィックス・チャージャー)で書いていたんだが、それをWindows3.1のGDIで動くようにするのに1日かからなかった(笑)し、そのルーチンをPlayStationのGTEとGPUを使うように改造するのに二日かかなんかったし、そのルーチンは数本の市販ゲームでいまでも元気に動いているし、たぶん必要になったら、そのルーチンをMMXとDirect3Dに対応させるのにも、二日かかんないだろう。

     これはなぜかといったら、このルーチンはハードウェアに依存しても、OSには依存していないからであり、さらにいえばこのルーチンが極めて原始的なものであるからだ。

     それはゲームプログラムそのものについても言えるし、逆に言えばゲームプログラマの能力とかキャリアとかは、それまで使ったことのあるハードウェアやOSには直接関係しない。いや、しないべきである

     というのは、世の中には信じがたいほどハードウェアに依存しまくるプログラムを書いてそれでよしというゲームプログラマもいるからだ。ビッグ/リトルエンディアンどころか、必要のないところに自己書換処理を入れたりとか(最新のプロセッサでは下手に自己書換なんぞしようものなら逆に効率が悪くなったり、黄泉の国へ旅立ったりしたとしても、文句は言えまい)、極端に読みにくいプログラムを書いて喜ぶ奴等だ。おれ自身が書いたプログラムもソースが奇麗で読みやすいとはお世辞にも言えないが、それでも移植性を確保するための最低限のマナーは守ってるつもりだ。自己書換レベルのカリカリの最適化を行うのは本当にギリギリの部分だけである。そして当然、そういう部分に関してはC言語で書いていたもとのコードをコメントとして残してある。こうすれば他のマシンに持っていっても、とりあえずはコメントをはずせば元どおりになる。

Opelation System for Games
ゲームのためのOS

     実はこっからが本題なんだけど、実はゲームって独自のOSを持っているんだっていう話。

     ゲーム、特にアクションゲームって、自機と敵機と弾がそれぞれ同時に動くでしょ。他にも、ドラクエタイプのゲームなら、キャラクターがそれぞれ同時に動くし、アドベンチャーゲームでも、画面の一部がアニメーションしながら、コマンド選択できるでしょ。

     これって、プログラムの視点で見ると、一種のマルチタスクなんだよね。自機、敵機、弾、キャラクター、それぞれにタスクを割り当てていて、画面には見えないけど、音楽も鳴って・・・というように、ゲームはそれ単体で複数同時並行に処理する必要性が非情に高いわけだ。

     翻って、他のプログラム、たとえばアプリケーションとかを見てみると、マルチタスクがないと絶対ダメっつー処理はあまり想像できない。でも依然としてマルチタスクは必須の存在になっている。
     マルチタスクが有功に働いているのは、たとえば複数のソフトを同時に起動する時だ。Word95とPhotoshopとVisualC++を同時に起動できなければ、あの本は完成しなかった(笑)。OSの内部では、マルチタスクをうまく使っているし、DLLという仕組みも、マルチタスクがあればこそだ。というわけで高度なOSほどマルチタスクを切望するのである。

     しかし、ゲームプログラムほど、シビアなマルチタスク処理が要求される場面は少ない。そしてWindows95はDirectXやDIBセクションなど一部の機能を除けば完全にアプリケーション・ソフト向けのOSだし、Unix、MS-DOSも汎用OSと呼ばれているものは全てそう。

     ということは、汎用マルチタスクOSで用意されているマルチタスク機能というのは、一般にアプリケーションのような「重い」環境専用のものであることが多い。
     事実、汎用プロセッサであるR3000(PlaystationのCPU)は、マルチタスク機能をCPUレベルで実装しているが、コンテキストスイッチ命令(タスク切替え命令)は最も遅くなっている。
     ペンティアムなどのプロテクト・モードも然りで、タスクひとつひとつについて、詳細なタスク・コントロール・ブロックを用意し、全てのセグメントをタスク毎に切り替え、メモリ空間もタスクによって変わるという高度なものである。

     しかし、ゲームにとっては、メモリ空間がタスクによって切り替わっては、かえって不便な重いをすることが多いのでないだろうか。敵キャラ一体、弾一発につき、それぞれ膨大なタスク・コントロール・ブロックが作られると思うとゾッとする。
     Windows95のタスク機能は、そういう意味では信用できない。というか、得体が知れず、恐い。
     もしかしたら効率をよくするための何らかの工夫がしてあるのかもしれないが、それにしても内部で一部APIが使えたり使えなかったりという不気味な仕様は変わらない。それならば、やはりイチから自分で作ったほうが、結果として便利である。

     また、メッセージの処理などに関しても、OSを自作したほうが融通が利く。
     そうすることによって、ゲームプログラマは独自の、そして固有の住みよい環境を自分で構築することができるのである。

     話をぶりかえすようだが、OSがタコならなるべくタコな部分を使わないようなプログラムを自分で書けばいいのである。それをWindows95はクソ。Linux最高!などとミーハー根性丸出しで否定しても、まったく説得力が感じられない。
     というか、そんな部分までもを他人に頼るのでは、そのほうがよほど原始的で幼稚ではないか。少なくともX-WindowよりもWindows95は使いやすいGUIを持っているし、X-Windowが動かなかったら自分の責任だが、Windows95はメーカーがサポートしてくれる。他力本願のコンピュータライフがお望みなら、Windows95のほうがヨッポドいいですぜ!(笑)。
Game Programming vs Object Oriented
ゲームプログラミングと
オブジェクト指向

     さて、そんな感じの論展開で、講義の内容は次第にゲーム用OSの内容へと移っていくわけだ。

     ゲームOSの第一の役割は、タスク管理である。
     さっき言った「マルチタスクが云々」というやつだ。これは関数ポインタ・テーブルで実現する簡単なものでよい。
     次に、メッセージ処理これはタスク間でメッセージをやりとりするときに使う。

     他に、メモリ管理、BIOS(基本I/O制御)のようなものが揃えば一丁前のOSである。

     そして実際のゲーム開発では処理速度に著しく影響を与える一部の部分(頻繁に呼び出される部分やグラフィック処理部分)とこのOS部分を除けば、あとはどこにでも持っていける可搬性のあるコードとなるのである。

     それぞれのタスクは独自のデータを持ち、タスクとなる関数は呼び出される時に固有のデータとメッセージ・キューを渡されるというしくみだ。
     こうすることによって、同じ関数で複数の敵キャラの挙動を表現できる。

     ところでこのタスクとメッセージという構造、なにかに似ていなくはないか?
     そう、これはオブジェクト指向の考え方そのものなのである。

     ここにゲームプログラミングとオブジェクト指向プログラミングの共通点が見出せる。
     といっても、オブジェクト指向言語でゲームプログラムを書くってのはホビーとしては昔からやられていることだから、今更取りたててどうということはないのだけれど、CPUがこれほど高機能・高性能になってきた今、プロユースとしても、オブジェクト指向言語を積極的に取り入れては?というのが今日の提言です。

     そして、ホビーとしてはあまりやられていないのが、ゲーム専用のOSを作るということ。OSといっても、その範囲は様々で、ブートストラップの段階から作るものと、ライブラリの段階でリンクするものがある。ここではどちらかというと後者に近い意味あいでOSという言葉を使っている。

     国後のプログラムを設計するにあたって、まず、設計にとりかかっているのが、このOSにあたる部分だ。
     国後OSはC++のクラスライブラリとして実装される。実際に使う場合は、国後OSの提供する適切なクラスの継承クラスとしてタスクを作成しなくてはならないが、それ以外の決まりは特にない。

     基本的には、国後OSはC++さえ動作すれば、アプリケーション(OS以外のコードをこう呼ぼう)部分がハードウェアまたは基本OS固有の操作をしていなければなるべくどこでも動くように作る。

     そして、起動のしかたが通常のOSとは異なるのだが、たとえばWindows上であればWinMainで、MS-DOS、Unixであればmainで、それぞれ国後OSのカーネルを呼び出すようにする。

     国後OS内部では、CTask型の継承クラスであるタスクを内部のタスク・コントロール・ブロックに保持し、CTask::Run()仮想関数を呼び出すことにより、タスクを実行に移す。
     各タスクはできるだけ短時間でリターンしなくてはならない。(本当のOSなら大問題だが、ゲームプログラムのようなものはこの形式でだいたい問題ない)
     各タスクは、クラスとして実装されるので固有の変数は全てメンバ変数として持つだけで良い。あとはC++コンパイラがなんとかしてくれる。メッセージも、CMessage型の継承クラスで定義され、受け渡しはポインタで行う。メッセージの送受信は、CTaskのメソッドで行えるようにしておき、アプリケーションはCTaskの該当メソッド(SendMessage、GetMessage)を呼ぶだけで良い。

     これは当然、C言語でも実装することはできるが、本格的な実装は大変であるし、C言語はスコープが甘いので、ちょっと複雑なものを組むとバグで悩まされやすい。
     そしてC++言語はC言語の特質を残しつつこれらの欠点を補う手段を提供しているという点では優れた言語だが、逆に言えば使いようによっては余計バグバグな代物も造れてしまう危険な言語とも言える。

     まぁしかし、クラスを慎重に設計して一人で使うぶんには問題ないので、できるもんならC++言語でプログラムを組んだほうが便利だし、利口かもしれない。
     だけど、今度はC++言語はアセンブリ言語とかけ離れているという問題がある。もともとオブジェクト指向という考え方は、ノイマン型のハードウェア構造には合わないし、チューリングマシンの延長線上にある考え方でもないので、実装系は少し複雑になるのが実状だ。
     となると、今度はパフォーマンスの問題が出てくる。
     せっかく独自にOS的管理機構を用意しても、C++言語自体のオーバーヘッドによってスピードが著しく遅くなってしまったりするのはいただけない。

     そこで、適切なトレード・オフが必要となる。
     たとえば、国後のテストタイプとして「アステロイド・ファイト97」を作ったとき、Visual C++のnew演算子delete演算子の遅さに発狂寸前になった(ちなみにこのプログラムのソースは例の本のCD-ROMにこっそり入っている)ような記憶がある。

     これは、おそらくメモリ中に多数存在するガベージ・オブジェクト(不要なオブジェクト)をイチイチ削除するためであろう。
     C++言語が汎用言語であるためには、なくてはならない仕様には違いないのだが、弾一個打つたびにタスクの生成・削除を繰り返さなくてはならないのに、弾がひとつ消えるたびにガベージ・コレクションされてはたまらない。しかたないので、このときは独自の管理クラスを作り、初期化時に配列の中のすべてのオブジェクトをnewし、終了時にすべて開放するという方法を使ったが、この方法では、せっかくのC++言語の持つ「多態(ポリモーフィクス)」を有効に活用することはできない。

     そこで、new演算子とdelete演算子をイチイチ使わなくても、同様のことが高速に行えるしくみを作る必要がある。これは国後OSにとって、今後の課題となるだろう・・・というか、いま即座にはあまりコレだ!と自信をもって言える方法は思い付かない。

     そうさのぉ、CTaskクラスの仮想関数としてRun()メソッドを実装するのではなくて、関数ポインタを入れることにしようか。こうすれば、実行時に関数を変えることができる・・・が、せっかくC++を使うのにスコープ解決はできなくなってしまう。
     うーん・・・ムズい。やはりスコープ解決はあきらめるか・・・それとも自分でC++言語用のスコープ解決構文を作るか・・・。

     やはりCTaskクラスの仮想関数を関数ポインタにして、データ領域へのポインタと一緒くたに呼び出すのが現実的な選択のような気もする。

     たとえば以下のように・・

        class CTask{
        private:
        	void *(func)(void *lpData);
        	void *data;
        public:
        	void Run(){
        		func(data);
        	}
        }
        
     いや、おれC++詳しくないから文法的にこれでいいのかどうかわかんないけど。
     そんでもって、タスクの登録はあらかじめnewしておいたタスク配列から、タスクをひとつ取り出し、TCBに追加したあと、dataを確保し、funcとdataを設定すればいいのか。うーむ。これならできそうだ。dataの内容は適当な構造体としてまとめておいて、すべての構造体のサイズが同一になるように仕組んでおけば、ガベージ・レスなアクセスが可能になる。
     そのうえ、このfunc(これは一種のコールバック関数だが)の中で、外部変数になるべくアクセスしないようにすれば、暗黙のうちにスコープの問題も解消する。

     おお、これって意外といいアイディアじゃない?

     しかしOSは奥が深い。
     ということでこの話題はたぶんまた続くと思います。

     国後OSは、フリーとして公開するつもりなので、なにかアイディアがあったらください。「そんなのすでにあるよ」ってな情報でもいいです。

     それでわ
GAME PROGRAMMING
vs
OBJECT ORIENTED
Programming
Prejudice