シューティングゲーム>自機の移動と弾丸発射

シューティングゲームのプログラミングにおける移動や被弾などの判定処理には、いろいろな方法があります。スクリーン上の座標を利用したり、DIBやDirectXを使っているのならピクセルの「色」を使う手もあるでしょう。ただ、動きがある程度「粗い」ものでも良ければ、もっと簡単な方法があります。
それは、スクリーンを格子状のグリッド(マス目)に区切ってグリッドごとに移動したり判定する方法です。

今回は、この方法で自機の移動や弾丸の発射処理を行うプログラムを開発してみましょう。

スクリーンのグリッドと自機・弾の移動

今回は、ゲーム画面を512×512ピクセルにして画面を32×32のグリッドに分けます。つまり、グリッドの大きさは16×16ピクセルになるわけです。自機や弾の位置もこのグリッドであらわす事にして、自機の大きさは32×32ピクセルとしました(つまり、自機は2×2グリッドを占める)。
 このグリッドの上に自機を描くわけですが、自機の位置は左上のグリッド座標であらわす事にしましょう。例えば、自機がグリッド座標で(1,1)−(2,2)、つまり画面上の座標で(16,16)−(47,47)の位置にあるのなら、x=1, y=2とするわけです。プログラムでは、自機の座標を変数iX, iYであらわす事にします。

次に自機の弾丸ですが、こちらはグリッド一つ分16×16で十分でしょう。今回のプログラムでは、グリッドの右端に2ピクセル分の白い縦線を引いておきました。弾丸の移動は、グリッドの管理配列(BYTE map[32][33])を用意して処理します。この配列は、グリッドの情報を管理するものでmap[x][y]=2なら弾丸が(x, y)にある、という事にしました。発射時の処理は、弾丸が発射されたら自機のすぐ上の座標の管理配列に2を書きこんで、後はだんだんと上に移動します。後は、画面を描画する時に管理配列を見て弾丸がある場所に弾丸を表示すれば良いですね。

移動する時の移動量は、自機が一度に1グリッドで弾丸が2グリッドとしましょう。

画面表示とキー入力

ゲームの画面はDIBSectionにします。512×512のビットマップを作成し、描画の時はまず背景用のバッファ(512×512バイト)を描いて、その後に透過処理によるソフトウエアスプライトでキャラクタのパターンを描画しましょう。
キー入力は、「今」キーが押されているかを判定するGetAsyncKeyStateで取得します。ウインドウに送られてくるWM_KEYDOWNメッセージだと、同時に複数のキーが押された時などの対処などが困難ですし、欲しいのは「今」どのキーが押されているのか、という情報ですからね。キー入力の処理では、カーソルキーで自機の移動(変数iX, iYを変更)、スペースキーで弾丸発射(管理配列への書きこみ)を行っています。また、プログラムはメインループから移動・描画関数draw()を呼び出す形にしました。

・移動・描画処理関数
  void draw(void) {

  int i,j,dwCount;
  char lpszStr[64];
  RECT rec;

  if (GetTickCount()>dwTime+20)
      dwTime=GetTickCount();
  else /* 前回の処理から20ms 以上経ってなければ戻る */
      return;

  dwCount=dwTime-dwPreTime; /* 前回からの経過時間を計算 */

  CopyMemory(lpScreen,lpBG,512*512); /* 背景描画 */

  for (i=0;i<32;i++) /* 自機描画 */
      for (j=0;j<32;j++)
          if (lpChr[j+i*32]!=0)
              lpScreen[iX*16+j+(iY*16+i)*512]=lpChr[j+i*32];

  for (i=0;i<31;i++) /* 弾丸描画 */
      for (j=0;j<32;j++)
          if (map[j][i]==2) {

              lpScreen[j*16+15+(i*16+7)*512]=255;
              lpScreen[j*16+15+(i*16+8)*512]=255;

          }

  /* キー入力取得 */
  if (GetAsyncKeyState(VK_UP)<0 && iY<30) /* 上キー */
      iY++; /* 自機の位置を更新 */

  if (GetAsyncKeyState(VK_RIGHT)<0 && iX<30)
      iX++;

  if (GetAsyncKeyState(VK_DOWN)<0 && iY>0) 
      iY--;

  if (GetAsyncKeyState(VK_LEFT)<0 && iX>0)
      iX--;

  if (GetAsyncKeyState(VK_SPACE)<0) /* 弾丸発射 */
      map[iX][iY]=2; /* 管理配列に弾丸を書きこむ */

  for (i=31;i>=0;i--) /* 弾丸を2グリッド上に移動 */
      for (j=0;j<32;j++)
          if (map[j][i]==2) {

              map[j][i+2]=2;
              map[j][i]=0;

          }

  if (dwCount!=0) { /* フレームレートを表示 */

      wsprintf(lpszStr,"FrameRate=%3d",(DWORD)(1000/dwCount));

      SetTextColor(hdcMem,RGB(255,255,255));
      SetBkMode(hdcMem,TRANSPARENT);

      rec.top=224;
      rec.left=0;
      rec.bottom=288;
      rec.right=511;

      DrawText(hdcMem,lpszStr,lstrlen(lpszStr),&rec,
        DT_CENTER|DT_SINGLELINE|DT_VCENTER);

  }

  InvalidateRect(hwMain,NULL,FALSE);
  UpdateWindow (hwMain);             // 再描画

  dwPreTime=dwTime; /* 今回の時間を記録 */

  }

こうしてDIBSectionのビットマップに作成した画面は、WM_PAINTメッセージ処理で描画します。

プログラム

VC++などでプログラムをビルドし起動したら、カーソルキーで移動してスペースキーで弾丸を発射してみてください。フレームレートも表示していますから、メインループの周期を変えて反応やフレームレートがどうなるか試してみるのも良いでしょう。

プログラムソース表示

今回のプログラムをスケルトンにして、簡単なゲームを制作してみてください。


ゲーム制作研究室 > プログラミング資料庫