第21章 タコの自立
2006年12月20日
個人的な見解で云わせていただけば、コンシューマ機、PC機を問わず、コンピュータゲームとは2つの種類に大別できると思っている。
1つはコンピュータをライバルに見立てて勝負を申し込む(?)タイプ。
もうひとつは、あるロジックに対して解答を見出すパズル的タイプ。
特に前者は、コンピュータに能動的な機能を要求する場面が多々ある訳で、その出来具合がゲームに求められる面白みの大半を占めているのではなかろうか?
チェスのようなボードゲーム然り、FPSにおける敵キャラクター然り。
プログラミングの内容にしても、無機的なデバイス相手ではなく、感情を持った人間相手の入出力処理をコード化しなければならないわけだから、非常に高度な技術が要求される分野だと思う。
過去において、対戦格闘ゲームと呼ばれるジャンルが生まれ大ヒットを巻き起こし、現在ではネットワークを使った麻雀、RPG、FPSが高い人気を誇っているようだ。
これらの事実は、まだまだコンピュータが人間相手に役不足だと云う、一般的な判断の現われなのかもしれない。
・・・なんか昨日読んだ本が影響してるのかな?文章が変に堅苦しくてつまらないね。
いや、今までもつまらなかったと云われると、ヘコムけど。
さて、これまで我等が(?)タコ助君は実に聞き分けよく我々の要望(マウスでの指示ね)に答え続けてきてくれたわけだが、ここらでそろそろ自発的な行動を行えるように育ててみよう。
前章では触れていないが、実は、List003−002での変更を行った折にキャラクターに属性を持たせておいた。
environment.h内で宣言されているCHARACTER構造体がそれ。
内容はビットマップファイル、マスクビットマップファイルのハンドルと、現在座標、目的座標などの座標、そして、ブレゼンハムのアルゴリズムで用いるデータである。
この部分は今後の拡張を考え、管理しやすいように構造体にまとめた物だ。
今回、キャラクターの行動パターンをデザインするにあたり、この構造体に新しいファクターを追加してみることにした。
はてさて、今回CHARACTER構造体に追加したのは
BOOL (*control_procedure)( int);
である。
これは関数のポインタ渡しを行うための宣言で、このポインタで渡されたプロシージャ関数を製作することで、キャラクタの行動パターンを制御してやろうと企んでいるわけである。
プロシージャにはint型の引数を持たせてやり、ここにメッセージナンバーを放り込んで呼び出してやろうと云うわけだ。
ちょうど、Win32APIプログラムで云うところのメッセージプロシージャ(本稿での関数MainCallBack()である)の仕組みを模倣してみたつもりである。
まずはこのプロシージャに渡すメッセージをマクロ定義して、これにあわせた処理を関数MoveEnvironment()内にて製作してみた。
とりあえず用意したメッセージは
CSM_STOP:キャラクターが停止している時(目標座標と現在座標が同じ時)
CSM_LIMIT_TOP:キャラクターがクライアント領域の上限にいる時(現在座標の縦軸が0の時)
CSM_LIMIT_BOTTOM:キャラクターがクライアントエリアの下現にいる時(現在座標の縦軸が480の時)
CSM_LIMIT_LEFT:キャラクターがクライアントエリアの左限にいる時(現在座標の横軸が0の時)
CSM_LIMIT_RIGHT:キャラクターがクライアントエリアの右限にいる時(現在座標の横軸が640の時)
で、それぞれif命令で条件分岐を行い、条件がそろった時のみ、各メッセージを引数にコントロールプロシージャを呼び出すようにしている。
また、関数のポインタを利用した呼び出しの際は、必ずこのポインタに実行可能な関数のポインタを代入してやらなければならない。
これを怠ると、宣言時の適当な値をポインタとして関数を呼び出すため、思いっきり暴走してアプリケーションは止まるはめになる。
その為に関数DefaultControlProcedure()を用意して、構造体生成時にポインタとして渡すことで初期化することにした。
これを、とりあえずビルドしてテストをおこなってみよう。
見かけ上の動きはList003−003の時と時と変わりはないので、ひとまず動くものにはなっているようだ。
では、実際に中身のあるコントロールプロシージャを用意して、タコチューの動きに変化が起きるか試してみよう。
キャラクターコントロールプロシージャをデザインするにあたり、今回、character_control_procedure.hとcharacter_control_procedure.cppを用意した。
以後ポインタ渡しされる関数は、ここにまとめていくことにしよう。
あわせて、この関数EnemyControlProcedure()のポインタを構造体enemy.control_procedureに登録する関数SetControlPtocedure()を用意した。
これは関数SetupProcess()内で関数SetCharacterBitmap()の次に、関数EnemyControlProcedure()のポインタを引数に渡して呼び出している。
これで、関数MoveEnvironment()内で呼び出されるコントロールプロシージャは、関数EnemyControlProcedure()になったはずである。
では、用意されたコントロールプロシージャ関数EnemyControlProcedure()について少し語ろう。
中身のレイアウトは、その仕組みを模倣した関数MainCallBack()とよく似ていて、受け取ったメッセージをswitch文で判断し、実行しているだけである。
今回は、キャラクターの行動が終了すると(すなわちCSM_STOPメッセージを受け取ると)目標座標をクライアント領域の左上隅にセットするようコーディングしてみた。
では、早速ビルドして実行してみよう。
・・・しかして結果は・・・
マウスカーソルで指示した位置をぴょこぴょこと微振動(?)し、クライアント領域の左上隅に行き着くことが出来ない。
これは簡単(?)なバグ。
関数MainTransaction()内で直接目標座標を指示している為に起こっている現象だ。
無限ループを持つ独立したスレッド、関数MainTransaction()はループの度に変数new_position_x、new_position_yをキャラクターの目標座標としてセットしている。
対して、関数EnemyControlProcedure()は関数MoveEnvironment()にて呼び出されているわけだが、この関数MoveEnvironment()もまた一つの独立したスレッド、関数BildClientArea()の無限ループ内にて呼び出されている。
この二つのスレッドに同期的関連性は無いため、好き勝手(?)なタイミングによってタコ助の目的座標は交互にセットされることになる。
故に無力(?)なタコ助はこのわがままにしたがって、関数MainTransaction()による座標指示と関数EnemyControlProcedure()による座標指示の狭間で身悶えているのである。
かわいそうに。
そこで、タコ助に正しい指示を与えるべく修正を施したものが次のソース。
まず最初におこなったのは、関数MainTransaction()のスレッド生成の削除。
もともとキャラクターの移動処理を行っていたこのスレッドは、関数MoveEnvironment()にその役割を移すことでほとんど存在意義を喪失していた。
最後に残ったキャラクターの目的座標へのセットも、そもそも関数SetPosition()が関数MainCallBack()から受け取った座標を右から左へ受け渡していただけであるから、関数SetPosition()内で目標座標を指示してしまえば、スレッド内には何も残らない。
したがって、今後もしかしたら必要になるかもしれないその時まで、関連部位をコメントアウトしてスレッドの生成をやめてしまうことにする。
ついでに、このスレッドが利用していた変数new_position_x、new_position_yも不要なので、同様に削除してしまおう。
最後に、関数SetPosition()の中身を修正してやって出来上がりである。
ビルドもうまく成功し、いよいよ実行。
これまで同様、クライアントエリアの任意の場所を左クリックしてやると、タコ助はそこに向かって一直線に移動していき、到着したところでクライアントエリアの左上端へと帰っていく。
この繰り返しを一言で表現すると、まさにヨーヨー。
結構面白い。
なんだか関数関数と小うるさい文章になってしまったが、こうしてタコ助は独立行動の機能を有することに成功したわけで、コントロールプロシージャの中身次第ではより複雑な移動を行うことが出来るようになった。
次はその中身を組み立てて、少しでも魅力的なキャラクターとして自立していってもらうとしよう。
・・・教育次第なのは云うまでもないことだが・・・
|