[HMC]年間5250円で独自ドメイン付属、ウィルスチェックありのレンタルサーバ!
Last update : 1999/12/19
ゲーム制作基礎 第9回
キー入力によって弾を発射させる&構造化プログラミング基礎
サンプルコードのダウンロード
  では、まずサンプルコードをダウンロードし、解凍して下さい。 私はVisual C++6.0でコンパイルしているので、をお持ちの方は Visual C++でプロジェクトファイルを開いて下さい(「game_09.dsw」をダブルクリックすれば開けます)。 圧縮ファイルに含まれる「game_09.exe」をダブルクリックし、実行してみて下さい。どうでしょう?カーソルキーで自機が移動し、スペースキーを押すと、弾が 発射されると思います。
  というわけで今回は、弾処理について解説したいと思います。
構造化プログラミング基礎
  ここのプログラミング講座を見ている人は、ほとんどが初心者だと思います。 そして初心者にプログラムを組ませてみると、高い確率で、WinMain(またはmain)関数内に ひたすら書きまくるようです(爆。たしかに、それでも動くには動くのですが、プログラムが 複雑になってくると、どうしても管理がしにくくなってきますよね?例えば、どこで何の処理をしているのか等。 そこで、ある程度固まった処理(謎)、例えば「キー入力でキャラを動かす」だとか「キャラクターを表示する」 というような処理を区切って記述してやります。そうする事で、プログラムを管理しやすくなったり、バグを発見しやすくなります。
  ようするに仕事別に自作関数化するって事です。
弾処理のアルゴリズム基礎
  弾の発射から消えて無くなるまでの大まかな流れは以下の通りです。

(0).弾用の変数(構造体等)を複数、用意する

(1).キー入力があったら、弾データを作成する

(2).弾を移動させる

(3).弾が画面外に出たら、弾データを消す

(4).弾表示

(5).(1)へ


重要なポイントは、弾のデータを作ったら、その後、弾のデータを移動させたり削除したりする事でしょうかねぇ。
実際にプログラムを組む
  では、判りやすい(たぶん)ように、順番に説明していきたいと思います。

まず、構造化プログラムという事で、必要な変数等をグローバル変数として宣言してやります(ソースの出来るだけ上に書く。
こうする事で、どこでも(どの自作関数内でも)これらの変数を参照する事が出来るようになります。 今まで、プレイヤーの座標はWinMain関数内でcx,cyと宣言し、それらを使用していましたが、今回は構造体(_player)として 宣言します。_bullet構造体は、弾の処理に使用します。今回使う変数はとりあえず、使用・未使用を表すフラグ(enable)と 座標(x,y)と、進む方向(angle)だけです。

では、一番最初に実行するWinMain関数を見てみましょう。

いやぁ、今まではここ(↑)にひたすら書いていたので、今回のプログラムは結構すっきり見えますね。
InitWindow()はウィンドウを作成する処理を書いてあるだけの自作関数なので解説省略。

で、DirectDrawを初期化したらInitGame();を呼び出していますね?これはゲームに必要な処理を先にしておく自作関数です。中を見てみましょう。
まず、作業用サーフェイス(lpWork)にキャラの画像を読み込んでおきます(左の絵)。 で、_bullet構造体のメンバ変数「enable」を0に設定しておきます(全て)。enableが0だったら、 その構造体は使用していないという事にします。


で、ここの処理が終わると、処理はWinMainの方へ戻って、次はGameLoop();を呼び出していますよね? この関数はゲームのループ用の自作関数です。コードは次のようになっています。
whileの中の最初と最後のWaitSet()、Wait()については既にやっているので解説省略。
で、まずMovePlayer()関数を呼び出していますよね?こいつはキー入力により自機を動かす処理をする 自作関数です。

この関数内でやっていることは
第5回で説明したので、みりゃ判りますね?(爆
ここの処理が終わったら、GameLoopの方へ戻って、次にShoot()関数が呼ばれています。

Shoot()関数では、スペースキーが押されているか判定し、押されていれば弾を作成します。
ここでは、シューティングゲームで使うような、押しっぱなしでも連射できる処理をやります。

  ここでは、ちょっとした工夫が必要ですかね。スペースキーが押されていた場合に弾を作成するだけでは、 押している間、毎回弾が作成されてしまいますよね?シューティングゲームのように、一定間隔で弾を撃ちたい場合は、 一つ変数を用意し(今回はplayer.renda)、スペースキーが押されている間はこの変数に1を足し、10になったら0に戻してやります。 で、この変数が0の時だけ弾を作成すれば、処理的には10ループに1回弾を発射する事になりますよね?

  さて、ここで極端な例を挙げてみます。この弾を出す間隔が10では無く、1000だとしましょう。 普通、ゲームで発射キーを押しっぱなしにする場合、発射キーを押した瞬間に弾が発射され、一定間隔で弾がでますよね? で、もし変数「player.renda」が仮に500で止まっていたらどうでしょう。発射キーを押しても、player.rendaは0でないため、 しばらく発射されませんね?これではマズイ(と思う)ので、発射キーが押されていない時、player.rendaが0で無い場合には、 player.rendaに1を足し、ある数を越えたら0にする、といった処理を付け加えてやります。こうすれば、発射キーを離している間でも player.rendaの値が変化するので、発射キーを離し、しばらくして発射キーを押した瞬間に、弾を発射させる事が可能となります。

  ついでですが、発射キーが押されていない時にplayer.rendaを0にすると、いつでも発射キーを押した瞬間に弾が出せます(実際にやってみて下さい)。

ここの関数内では弾を作成する時にCreateBullet( x座標 , y座標 , 角度(0〜359) )自作関数を呼び出しています。 ではどういった処理をしているのか見てみましょう。

  さて、初めの方で呼び出したInitGame()内では_bullet構造体の全てのenableを0にし、未使用データとしましたね? ですから、ここ(CreateBullet)では、構造体の配列の0番からBMAX-1(今回は99)までを順に参照し、 enableが0(つまり未使用となっているデータ)を探します。
  そんでもって、空いているデータが見つかったら、その番号の構造体に情報を詰め込んでやります。 最後にenableを1とし、no番の構造体を使用データという事にしていますよね? ですから、次にCreateBullet関数内で空いているデータを順に探した時も、no番のenableは0では無いので 上から新しくデータが詰められる事はありません。

でShoot()関数へ戻り、GameLoop()の方へ戻り、次にMoveBullet()を呼び出しています。

  ここでは、構造体_bulletの配列を0から順番に参照し、enableが0でないデータ(つまり使用しているデータ)だけを処理します。 角度による移動自体は第4回で説明したので省略。
  そして、画面外に出たらenableを0にし、未使用データとしてやります。

GameLoop()に戻り、次にShow()が呼び出されます。これは、ディスプレイに一連の表示をする 自作関数です。 コードを見てみましょう。
まぁ、ここは見りゃ判りますね(爆
弾は使用しているものだけ表示してやります。

とりあえず、これで説明は終わりです(^^;
今回のプログラムの流れを図で表すと、こんな感じになるのかも

矢印は関数の呼び出しです。
おまけ
  今回は弾のデータ構造に構造体の配列を使用しましたが、この場合、配列中に使用しているデータと使用していないデータが両方含まれていたり、 弾数に制限があったりして少し不便ですよね?そんなときはリスト構造を使用すると結構スッキリ記述できます。Space Soldierは双方向リスト構造でやってました。