■ 新・ゲーム開発講座




■ へっぽこプログラミング入門♪


■第13夜:裏画面&ちらつき防止

今回は、画面のちらつき防止についてです。うう・・・これも最初にやっておけば良かったなぁ(爆) しかしまあ、気を取直してインプリメントしましょう。描画系のスクリプトコマンドを充実させる前に、ぜひともやっておきたいところです。

さて、前回までのサンプルプログラムを実行してみて、画面の再描画がされないことにはお気づきでしたでしょうか? 別ウィンドウで覆われてしまうと画面は復帰しませんし、画面では良く分からないのですが実はちらつき防止対策が何もされていないので描画/再描画を高速に繰り返すと見るに耐えない状態になります。




カッコ悪〜〜〜っ!!!(^^)


さて、これを解決するには幾つかの方法がありますが、ここでは裏画面を用意して常時裏→表に画面内容を転送する方式を採用したいと思います。理由は・・・多分これが一番楽だと思いますので(爆笑)

■裏画面とは

下図に示すとおり、通常表示されている表画面に対して容易されているもう一つの画面です。「ちらつき」というのは描画プロセスの途中が見えてしまうことですから、最初に裏画面(非表示)に対して描画作業を行い、最後に裏画面全体を表画面に転送すれば原理的に防止できます。また、裏画面の内容を Mainloop を通るたびに常に表画面に転送し続けるようにすることで、他のウィンドウによって隠された部分もほぼリアルタイムで復帰できます。(_WM_PAINT メッセージをつかまえても良いのですが面倒なのでここでは割愛、割愛♪ ^0^;)





そんな訳で、裏画面を追加したサンプルソースをここに用意しましたのでご覧ください。以下は、そのソースの内容説明です。

■裏画面の生成

今回のソースでは、裏画面の生成部分を main.cpp の初期化関数 init_game() 内でコールしています。init_Back_Surface() というのがその本体です。

まず裏画面を管理するために、HDC Back_DC、HBITMAP hBack_Bitmap というグローバル変数を用意します。前者はデバイスコンテキストのハンドル、後者はBITMAP(画像本体)のハンドルです。



HDC
HBITMAP
Back_DC; //デバイスコンテキスト(裏画面用)
hBack_Bitmap; //裏画面本体のハンドル


void init_Back_Surface()
{

HDC work_hdc; //作業用のDC

//■ バック画面の初期化
work_hdc=GetDC(hwnd); //主(表)画面のDCの内容を取得
Back_DC=CreateCompatibleDC(work_hdc); //同じ設定でバック画面用のDCを生成
hBack_Bitmap=CreateCompatibleBitmap(work_hdc,640,480);//主(表)画面と同じ属性で画面生成
SelectObject(Back_DC,hBack_Bitmap); //DCと画面本体を関連付ける

Load_Bmp(Back_DC,"Back00.bmp"); //とりあえずデフォルトBMPでも読んでおきましょう

ReleaseDC(hwnd,work_hdc); //作業用DCを開放


}


関数内では、まず表画面 (ウィンドウハンドルはグローバル変数 hwnd で保持しています)のDCの内容を GetDC() で取得し、それと同じ内容の裏画面用DCを CreateCompatibleDC() で生成しています。このDC (デバイスコンテキスト) は画面のこまごまとした属性が記録されている戸籍みたいなものだと思って下さい。HDCはそのDC(複数作成することが出来る)に割り振られた背番号のようなもので、ここでは CreateCompatibleDC() の戻り値として返ってきた値をグローバル変数 Back_DC で受けています。WindowsのAPIはこの「ハンドル」が大好きで、何をするにも背番号指定が必要です(まあハンドル一つでいろいろなサービスを受けられるのですから文句は言わないようにしましょうか ^^;)。


work_hdc=GetDC(hwnd); //主(表)画面のDCの内容を取得
Back_DC=CreateCompatibleDC(work_hdc); //同じ設定でDCを生成


次に、そのDCの内容に沿って実際の画面を CreateCompatibleBitmap() で生成しています。生成すると、やはりその画面を管理するための背番号(ハンドル)が返されますので、hBack_Bitmap で受けておきます。


//主(表)画面と同じ属性で画面生成
hBack_Bitmap=CreateCompatibleBitmap(work_hdc,640,480);


さて生成しただけでは画面は使えません。DC と 画面本体を関連付けるために SelectObject() でその関係を明示的に結びつけておきます。


SelectObject(Back_DC,hBack_Bitmap); //DCと画面本体を関連付ける


・・・と、とりあえずこれで裏画面が確保できた訳ですね。最後に、ワーク用DCを消しておしまいです。この裏画面に対してなにか操作をするときは、ほとんどの場合デバイスコンテキストのハンドル(今回は Back_DC)を指定するだけで間に合う筈です。

なお、画面は生成しただけではメモリ内のゴミを含んだままですので、とりあえずここではデフォルトのBMPを読み込んでおくことにしました。「いちいちBMP読みなんて面倒だ〜」という方は、PatBlt() で単色塗りつぶしするというのも一手でしょう。

■描画先は基本的にすべて裏画面に変更

そして描画先をいままでの win_hdc(表画面)から裏画面(Back_DC)に変更します。幸い、現在のサンプルソースでは画面アクセスと言っても文字の表示しかしていませんので、TextEngine.cpp における Text_engine() の以下の部分の変更だけで済みます。



if ( (unsigned char)(*TEXT) < (unsigned char)0x80 ) {

//コントロール文字かどうかを判断し、該当しなければ画面に出力
if( Check_control_chr()==false ){
ChrPut3D(
Back_DC,TEXT_X ,TEXT_Y ,Font_Size,color1,color2,(char *)TEXT,1);

Increment_textp_pos(1); //文字表示位置のインクリメント=1byte
}
TEXT++; //テキストポインタを1バイト進める

}else{
//2バイト文字の場合は2バイトずつ出力
ChrPut3D(
Back_DC,TEXT_X ,TEXT_Y ,Font_Size,color1,color2,(char *)TEXT,2);

Increment_textp_pos(2); //文字表示位置のインクリメント=2byte

TEXT+=2; //テキストポインタを2バイト進める

}



■Mainloop で裏→表の転送を

最後に、Mainloop で裏画面→表画面への転送を行います。やり方は簡単。関数の最後に1行 BitBlt() を追加するだけです♪



void Mainloop(void)
{

//フラグによってMainloopから呼ぶ関数を切り替える
if (flag_delay == OFF) {
//テキストエンジンを呼ぶ
Text_engine();

} else {
//遅延フラグが立っていたら、遅延タスクを呼ぶ
Com_delay_task();

}

BitBlt(win_hdc,0,0,640,480,Back_DC,0,0,SRCCOPY); //裏画面 → 表画面にコピー


}


これで、別ウィンドウの影になっても画面は復帰しますし、ちらつきもとりあえず防止できましたね。