ブロック崩し>画面情報の配列

 アクションゲームでは、キャラクタや背景の「当たり判定」や移動・消滅などに伴う「画面描き換え」が必要になります。このような処理を行う方法はいくつかあるのですが、今回はその中でも最も単純な画面の情報を記憶する配列を作成する方法をブロック崩しを例に試してみましょう。

画面の構造

 画面の情報を配列に記憶するには、まず画面をキャラクタと同じ大きさ(32ピクセル前後が多い)でグリッド状に分割します。通常ゲーム画面は、16×16キャラクタ程度ですので配列も16×16程度の大きさになるでしょう。こうして画面をキャラクタ単位の座標に分けたら、各座標の情報を数値として2次元配列に記憶して行きます。これは、その座標に何があるかを表すもので、例えば何もなければ0、ブロックがあれば1、自機がいれば2、ボールがあれば3、などと適当に決めておきましょう。
 これで画面の情報を記憶する配列が出来たので、後はゲーム中に自機・ボールの移動やブロックの消滅という変化があればそれを画面情報の配列に描き込んで描画します。ボールと自機・ブロックとの当たり判定も、配列を見ればすぐに出来ますね。

 今回のブロック崩しでは、キャラクタを32×32ピクセル、画面の大きさを16×16キャラクタ(512×512ピクセル)としました。画面情報配列の値は、何もなければ0、壁があれば1、自機なら2、ブロックがあれば3とします。この辺りは、今後キャラクタを増やしたりすると変わってくるので適当に作っておけば良いでしょう。あるいは、キャラクタの番号を先頭のdefineで定義するようにしてプログラムでは直接数値を使わないようにすると修正が楽かもしれませんね。
 プログラムの初期化では、まずゲーム画面とキャラクタ用に256色DIBを作成し、キャラクタのビットマップへのポインタを設定します。キャラクタのポインタは、LPBYTE型の配列(lpChr[])に格納しておくとキャラクタ番号をインデックスにして参照できるので便利です。

  lpBuf=(LPBYTE)GlobalAlloc /* DIB用メモリ確保 */
        (GPTR,sizeof(BITMAPINFO)+255*sizeof(RGBQUAD)+512*512+32*32*4);

  lpDIB=(LPBITMAPINFO)lpBuf; /* DIB用メモリを分配 */
  lpRGB=(RGBQUAD*)(lpBuf+sizeof(BITMAPINFOHEADER));
  lpScreen=lpBuf+sizeof(BITMAPINFO)+255*sizeof(RGBQUAD); /* 画面ビットマップ */

  for (i=0;i<4;i++) /* キャラクタ用ビットマップ配列 */
      lpChr[i]=lpScreen+512*512+32*32*i;

 画面とキャラクタのビットマップを作成したら、画面情報配列を初期化します。

  /* 画面情報配列初期化 */

  FillMemory(map[0],256,1); /* 全体を壁で塗りつぶす */

  for (i=0;i<15;i++) /* ブロックとタイル配置 */
      for (j=1;j<15;j++)
          if (i<8)
              map[j][i]=0;
          else
              map[j][i]=3;

  map[iX][iY]=2; /* 自機の初期位置 */

 こうして、画面の用意が出来たらメインループに入ります。後は、このメインループ内で終了まで移動・描画処理を繰り返すわけです。

メインループでの移動と描画処理

 メインループでは、最初にゲーム画面のビットマップ全体を描き換えます。画面情報配列(map[][])を参照すれば、画面上のどこに何があるのか、という情報を取得できるので2重ループで配列を参照しながら一気に画面全体を描いてしまいましょう。

  for (i=0;i<16;i++) /* マップ情報で画面作成 */
      for (j=0;j<16;j++)
          for (k=0;k<32;k++)
              CopyMemory(lpScreen+j*32+(k+i*32)*512,lpChr[map[j][i]]+k*32,32);

 画面情報配列には、キャラクタ番号を入れてあるのでmap[x][y]とすれば座標(x、y)にあるキャラクタ番号を取得できます。後は、キャラクタ番号をキャラクタのビットマップ配列のインデックスにして描くべきキャラクタの先頭アドレスを取得すれば良いですね。

 画面を描き換えたら、キー入力の取得と自機の移動を行います。自機の移動は、移動しようとしている先に何もなければ(タイル状の床?なら)自機の座標を更新し画面情報配列に新しい座標を書いて以前の座標を消すだけです。こうしておけば、次回のメインループの処理の先頭で新しい位置に自機が描画されます。
 キー入力には、現在キーが押されているか調べるGetAsyncKeyStateを使いました。

  /* 自機の移動 */
  if (GetAsyncKeyState(VK_RIGHT)<0 && map[iX+1][iY]==0) { /* 右方向 */

      map[iX][iY]=0; /* 現在の座標をクリア */
      map[++iX][iY]=2; /* 新しい座標を設定 */

  }

  if (GetAsyncKeyState(VK_LEFT)<0 && map[iX-1][iY]==0) { /* 左方向 */

      map[iX][iY]=0; /* 現在の座標をクリア */
      map[--iX][iY]=2; /* 新しい座標を設定 */

  }

プログラム

VC++でビルド、実行してみてください。

カーソルキーで自機を左右に移動できます。キャラクタ単位(32ピクセル単位)の移動なので動きはかなり粗いですね。滑らかにするには、画面情報のグリッドを細かくする(例えば1/2キャラクタ単位とか)、あるいは動きのあるキャラクタは画面情報とは別管理とする、などの方法が考えられるでしょう。

プログラムソース表示

次回はボールの移動と当たり判定を入れてとりあえず「ブロック崩し」ができるようにしてみます。


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