Windows95用ゲームでスプライトの表示がしたい

ゲームを作っているとスプライトの様な物が表示したくなる事が良くあります。これは、画面上に出ている様々な

効果だったり、或いはシューティングゲーム等の場合は自キャラや敵キャラだったりします。しかし、標準でスプライト

機能を持ってるパソコンはかなり珍しい部類に入り、特にWindows95の走るパソコンでスプライト機能を持ってる物は

(確か)無かった筈なので、ソフトウェアでこれを実現する必要があります。

ハードウェアのスプライトの場合、スプライト用のVRAMにキャラデータを置いておき、スプライト機能はそのVRAM

の画面への表示の仕方を制御します。画面に16*16Dotのキャラクタを表示したい場合、単純にはVRAMに16*16の

キャラクタを置いておき、それをスプライト機能を使って画面のどこそこに出せ、と命令するわけです。

一般にはスプライト機能はスプライト表示用のキューを持っており、単純な物ではキューの先頭からそれを解釈して

行きます。少し高度なハードになってくると、キューとキャラクタを両方VRAMに置き、それを一旦フレームバッファに

描画してから次の割り込みでそのフレームバッファを画面に表示するようにします。こうすると変なちらつき方が

起きにくくなります。

ソフトウェアでスプライトを実現する場合も、一旦フレームバッファに描画させる方式にするのが良いでしょう。

というか、Windowsでやる場合はそれ以外は殆ど考えられないでしょう。

で、フレームバッファですが、作るゲームの種類によってフレームバッファの作成方法も変えた方が良いかも

しれません。アクション系のゲームではやはりDirectDrawを使い、バックサーフェイスをフレームバッファとして

使う事になるでしょう。以下のコードをみてください。

 

  // イメージをサーフェイスに書き込む
  ddsd.dwSize = sizeof(DDSURFACEDESC) ;
  app.lpDDSHanger->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR, NULL) ;
  LPSTR fbuf ;
  fbuf = (LPSTR)ddsd.lpSurface ;
  {
    LPSTR destptr ;
    for(short y = 0 ; y < 32 ; y ++) {
      destptr = fbuf + (y * 640) ;
      for(short x = 0 ; x < 64 ; x ++) {
        *destptr = *tmpptr ;
        destptr ++ ;
        tmpptr ++ ;
      }
    }
  }
  app.lpDDSHanger->Unlock(ddsd.lpSurface) ;

このコードは私が今作っているゲームの一部分から抜き出してきた物ですが、あるサーフェイスをロックし、そこに

イメージを描画しようとしています。上のコードは実際にはスプライト用の描画部分では無いのでスプライトの表示に

必要なヌキの処理等が入っていませんが、基本的にはこんな所です。DirectDrawを使った場合は、ロックして描画、

或いはbltによる描画の何れかの方法になりますが(実際にはDCを得てBitBltという選択肢もあるが無視)、

bltの場合は抜きの処理に気を付けなければなりません。カラーキーを標準でサポートしないビデオカードの場合は

ロックして描画の方法に切り替えなければキャラクタが表示されません。(カラーキーはHELではサポートしてくれて

いないのかも)

DirectXを使わないゲームの場合はCreateDIBSectionでフレームバッファを起こすのが良いかもしれません。

この場合もポインタを得る方法等以外は基本的にDirectDrawのやり方と一緒です。

あるいは、自分でメモリを確保して、そのメモリに対してCreateDIBitmapをかける、という方法もあります。

この辺りは好みの問題でしょう。一応CreateDIBSectionでフレームバッファを確保する部分だけ書いておきます。

描画処理自体はDirectDrawとほぼ同じと考えていいので(場合によっては上下逆にする必要がある場合も

あるかもしれないけど)省略します。尚、画面の色数は256色とします。また、既に描画先のウィンドウ、

描画時に使用するパレットが決定している物とします。描画先のウィンドウのウィンドウハンドルはhwndに、

パレットデータはPALETTEENTRY型のpalette構造体に入っている物とします。

  HDC hdc = GetDC(hwnd) ;
  LPSTR bmiBuf = new char[siezeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256] ;
  ZeroMemory(bmiBuf, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256) ;
  BITMAPINFO *pbmi = (BITMAPINFO *)bmiBuf ;
  pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER) ;
  pbmi->bmiHeader.biWidth = 640 ;     //    横幅
  pbmi->bmiHeader.biHeight = 480 ;     //    高さ
  pbmi->bmiHeader.biPlanes = 1 ;
  pbmi->bmiHeader.biBitCount = 8 ;
  pbmi->bmiHeader.biCompression = BI_RGB  ;
  pbmi->bmiHeader.biSizeImage = 0 ;
  for(int i = 0 ; i < 256 ; i ++) {
    pbmi->bmiColors[i].rgbRed = palette[i].peRed;
    pbmi->bmiColors[i].rgbGreen = palette[i].peGreen ;
    pbmi->bmiColors[i].rgbRed = palette[i].peRed ;
  }
  LPSTR frameBuf ;
  HBITMAP hbmp = CreateDIBSection(hdc, (CONST BITMAPINFO *)pbmi, DIB_RGB_COLORS ,
             (VOID *)&frameBuf, NULL, 0) ;
  delete [] bmiBuf ;
  ReleaseDC(hwnd, hdc) ;

以上の初期化でframeBufに目的とする大きさのフレームバッファへのポインタが入ります。このバッファへ

何か描画した後にCreateDIBSectionで得られたhbmpを使用してBitBltするとその描画したイメージを

表示できます。

 

ここまでは単純な表示系の話です。次に、表示時の効果について考えます。取りあえず、割と要望の多そうな

物として拡大縮小が上げられます。実際にはこれの実現は簡単な部類に入ります。要するに、出力先のピクセル

に対する元のイメージの位置の計算をしっかりやれば簡単です。また、高速化の手法も色々あります。取りあえず、

簡単に解説すると、まず出力先の座標を(XD,YD)元のイメージの座標を(XS,.YS)として、X方向の拡大縮小は計算

式では XS = S*XD となります。この時、Sは拡大率で、1で等倍、1より小さい数値で拡大、1より大きい数値で

縮小という事になります。勿論、この拡大率に合わせてXDの最大値も(1/S)*XDm0 (XDmはXDの取りうる最大値)と

なります。これをこのままプログラムすれば取りあえず効果は出せますが、毎回掛け算の計算をするよりは、XSの

増分値を小数点化し、これを毎回のループで足してやる方が遥かに高速です。ちなみに少数も浮動小数点(floatや

double等)は使わないようにしましょう。それよりも、例えばlongの上位16bitと下位16bitで分けて、整数部を取る時

には右16bitシフトさせる、所謂固定小数点式でやる方が遥かに速いです。この辺りの細かい気遣いが結構出来に

響いてきますので出来れば忘れないでください。また、もしどうしても速度をもっと上げなければならない場合は

アセンブラの使用も考えなければならないかもしれません。まぁ最近のCコンパイラは凄まじい最適化を施すので

アセンブラにしたからと言って確実に速くなるわけではないのですが、やはり場合によっては有効な手段となります。

 

 

プログラミングTipsに戻る