第20章 タコと時間の微妙な関係
2006年12月13日
このあいだ発売されたP○3なるコンシューマゲーム機、実は中にP○2がまるまる実装されていることはもう既に周知の事実だが、これが物語っているのは互換性と技術革新の両立がいかに困難なことなのかと云う現実であると思う。
消費者サイドから見れば、新しい技術の集大成であるP○3そのものと、旧来機種であるP○2の抱き合わせ製品を購入しているようで、あまり良い気のする事実では無い。
しかし、完全なソフトウェアエミュレーションを実装したほうが安くなるのかと云うと、これも疑問である。
ソフトウェアの開発費と云うのは、現代社会においてそうそう安価に抑えられる部分と云うわけでもない。
ましてや、P○3のCe○lプロセッサの更にP○Eと呼ばれる部分は、P○2のEm○ti○nEngineより処理能力が低いと聞く(あくまで噂)。
シングルコアのエミュレートにマルチコアの優位性を持ち込むのは非常に困難であろうと思われるから、S○Eをいかに有効活用するかで最大処理能力を引き出すCe○lには、不利な要素ばかりにも感じられる。
そう、互換性とは再現性と処理時間を両立して、はじめて評価される性能なのだ。
PCだって例外ではない。
世間の流れでx86アーキテクチャを捨てられない現行のCPUは、デコードと呼ばれる機能を内包することによって、その互換性を維持している。
この機能だって、互換性を捨ててしまえば、本来不要なはずの機能であると思うのだが。
皆さんは昔、”ちまち○”と呼ばれるPCゲームがあったことをご存知だろうか?
このゲーム、発売当時の機種で速度を調整してあったらしいのだが、現実の時間で処理を調整していなかったらしく、後に出た、より高性能な機種では早すぎてゲームにならなかった。
現在のPCの処理性能は、それこそ千差万別で、同時期に発売されているPC間でも、処理能力と価格のトレードオフのおかげで、まったく同じ性能のものは無いと云っても過言ではないだろう。
つまり、ソフトウェアによる処理速度の調整は、ゲームと云うジャンルに限って云えば、必須項目となってしまったのである(←つまりソフトウェアの互換性)。
あー、今日も語ったなぁ、ってことで本題。
キャラクターの表示処理がほぼ完成したところで、次はキャラクターの内部処理を考察してみようか。
動きとか、当たり判定とか、アニメーションとかとか、ゲームにおけるキャラクター関連の処理は多岐にわたる。
さてその前に、これまで作ってきたものを都合よく作り変えて、今後の方針に備えることにした。
とりあえず、キャラクターの描画関数を改名(SetCharacter→DrawCharacter)。
いやなに、これには深い意味などなく気分。
次にこれを、関数BildClientArea()内で呼び出すよう変更し、関数MainTransaction()からはキャラクタの描画座標だけを指示することとした。
その為の関数SetCharacterPosition()を追加し、あわせてキャラクターのビットマップハンドルを指定するために、関数SetCharacterBitmap()も用意してみた。
この改造の目的は、以前から引っかかっていたデバイスコンテキストのハンドル取得に関わる排他処理問題の解決。
ま、同じスレッドに放り込んでやればアクセスが重なることも無いから、関数(SetCharacter改め)DrawCharacter()で行っていたハンドル取得のリトライ処理を削除した。
排他処理の後始末としてはお粗末な気もするが、まぁ、私のレベルではこんなものか。
次は動きの処理を外に吐き出そう。
今回、キャラクタ制御の関数郡として、”environment_control.cpp”を追加した。
今後、ブレゼンハムのアルゴリズムを含むキャラクターの管理をここにまとめていこうと思う。
移動目標の指示やビットマップのハンドルもここで管理することとして、中心となる関数MoveEnvironment()を用意した。
これを関数BildClientArea()で呼び出すことにする。
あわせて関数MainTransaction()を変更した。
さて、こうなると今度問題になるのはその速度。
関数BildClientArea()には関数Sleep()によるウェイトが掛かっているから、関数MoveEnvironment()もこの影響を受け、結果キャラクターの移動速度も遅くなってしまう。
実際ビルドして実行してみた結果、画面の左上から右下端までの移動に20秒ほど掛かってしまった(←私の現行機にて)。
では、PCの処理性能に依存しないキャラクターの移動速度ってものを考慮して、更なる改造を行ってみよう。
とりあえず、ソースファイルは次のとおり。
今回大幅に手を加えたのは、関数BildClientArea()。
まず、関数timeGetTime()を使って処理全体に掛かった時間の測定を試みている。
因みにこの関数timeGetTime()はWindowsOS起動時からどのくらいの時間がたったのかを受け取るためのもの。
処理の始めと終わりでこの時間を受け取り、その差を求めることで、処理に掛かった時間を求めている。
時間を求める計算で変数end_timeと変数start_timeの大小関係を比較しているのは、関数timeGetTime()から求めた値がオーバーフローを起こしていないか確認するため。
関数timeGetTime()が返してくる値はDWORD値なのだが、ウィンドウの起動時間が長いと値がリセットされてしまう為である。
また関数timeBeginPeriod()は関数timeGetTime()を使うための下準備(?)関数で、引数には扱う最小時間を指示することになっている。
今回は引数を1とし、1msec単位で扱うことにしてみた。
関数timeGetTime()の使用が終了したら、関数timeEndPeriod()でリソースの開放をしてやる必要があるとのこと。
この時の引数は関数timeBeginPeriod()と同じ数字を入れてやることになっているから、今回は1を渡してやろう。
あっと、これらtime関数郡(?)を利用する為に、”winmm.lib”のリンクを忘れてはいけない。
そこで、今回は#pragmaプリミティブを使って、このライブラリのリンクを行っている。
因みに#pragmaプリミティブとは、コンパイラ固有の拡張を行うものらしく、使用するコンパイラによって用法や挙動が異なるため、注意が必要だ。
つまり、今回(今後も)のソースはVisualC++でのみ正常にコンパイルされる、と認識していただきたい。
他のコンパイラが互換性を持っていればビルドも問題なく行われるとは思うが、意味を持たない#pragmaは無視されるようなので、気を付ける必要がありそうだ。
さて、ここまでで測定した時間を元に、関数MoveEnvironment()を繰り返し処理することにしてみた。
経過時間の分だけ繰り返しているわけだが、今回は1msecに付き1回の実行となるから、キャラクターは1msecで1ドット分移動することになるはずである。
また、関数BildClientArea()内の関数Sleep()に16msecではなく1msecに変更した。
関数Sleep()を削除しなかったのは、少しCPUへの負荷に余裕を持たせたかったのと、今後登場するであろう高性能なマシンがクライアントエリアの描画に1msec掛からなくても、測定時間が1msec以下にならないようにと(いらん)配慮をしたためである。
実際に実行したものが上図。
移動中のタコ助が黒い尾(?)を引いているのが見て取れるが、これはクライアントエリアが再描画されるまでの間にタコ助の描画が複数回行われていることを示している。
クライアントエリアの描画と、タコ助の描画に費やす時間が長ければ長いほど、この黒い尾は長くなっていくはずだ。
さぁ、次はキャラクターの自動運転(?)に挑戦してみようか。
|